Skip to content

Commit

Permalink
Merge pull request #5195 from wonder-sk/tracing-with-offset
Browse files Browse the repository at this point in the history
Tracing with offset
  • Loading branch information
wonder-sk committed Oct 10, 2017
2 parents 217c0e7 + 863197f commit 9d8734c
Show file tree
Hide file tree
Showing 13 changed files with 610 additions and 8 deletions.
27 changes: 27 additions & 0 deletions python/core/qgstracer.sip
Expand Up @@ -60,6 +60,33 @@ Get extent to which graph's features will be limited (empty extent means no limi
void setExtent( const QgsRectangle &extent );
%Docstring
Set extent to which graph's features will be limited (empty extent means no limit)
%End

double offset() const;
%Docstring
Get offset in map units that should be applied to the traced paths returned from findShortestPath().
Positive offset for right side, negative offset for left side.
.. versionadded:: 3.0
:rtype: float
%End

void setOffset( double offset );
%Docstring
Set offset in map units that should be applied to the traced paths returned from findShortestPath().
Positive offset for right side, negative offset for left side.
.. versionadded:: 3.0
%End

void offsetParameters( int &quadSegments /Out/, int &joinStyle /Out/, double &miterLimit /Out/ );
%Docstring
Get extra parameters for offset curve algorithm (used when offset is non-zero)
.. versionadded:: 3.0
%End

void setOffsetParameters( int quadSegments, int joinStyle, double miterLimit );
%Docstring
Set extra parameters for offset curve algorithm (used when offset is non-zero)
.. versionadded:: 3.0
%End

int maxFeatureCount() const;
Expand Down
2 changes: 2 additions & 0 deletions python/gui/qgsmaptooladvanceddigitizing.sip
Expand Up @@ -108,6 +108,8 @@ Catch the mouse move event, filters it, transforms it to map coordinates and sen
.. versionadded:: 3.0
%End

public:

virtual void cadCanvasPressEvent( QgsMapMouseEvent *e );
%Docstring
Override this method when subclassing this class.
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -1246,6 +1246,7 @@ QgisApp::QgisApp()
mLayerTreeView = new QgsLayerTreeView( this );
mUndoWidget = new QgsUndoWidget( nullptr, mMapCanvas );
mInfoBar = new QgsMessageBar( centralWidget() );
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
// More tests may need more members to be initialized
}

Expand Down Expand Up @@ -2285,6 +2286,8 @@ void QgisApp::createToolBars()

mTracer = new QgsMapCanvasTracer( mMapCanvas, messageBar() );
mTracer->setActionEnableTracing( mSnappingWidget->enableTracingAction() );
connect( mSnappingWidget->tracingOffsetSpinBox(), static_cast< void ( QgsDoubleSpinBox::* )( double ) >( &QgsDoubleSpinBox::valueChanged ),
this, [ = ]( double v ) { mTracer->setOffset( v ); } );

QList<QAction *> toolbarMenuActions;
// Set action names so that they can be used in customization
Expand Down
20 changes: 19 additions & 1 deletion src/app/qgssnappingwidget.cpp
Expand Up @@ -16,15 +16,17 @@

#include <QAction>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QFont>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QMenu>
#include <QToolBar>
#include <QToolButton>
#include <QWidgetAction>

#include "qgsapplication.h"
#include "qgsdoublespinbox.h"
#include "qgslayertreegroup.h"
#include "qgslayertree.h"
#include "qgslayertreeview.h"
Expand Down Expand Up @@ -148,6 +150,22 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mEnableTracingAction->setShortcut( tr( "T", "Enable Tracing" ) );
mEnableTracingAction->setObjectName( QStringLiteral( "EnableTracingAction" ) );

mTracingOffsetSpinBox = new QgsDoubleSpinBox;
mTracingOffsetSpinBox->setRange( -1000000, 1000000 );
mTracingOffsetSpinBox->setDecimals( 6 );
mTracingOffsetSpinBox->setClearValue( 0 );
mTracingOffsetSpinBox->setClearValueMode( QgsDoubleSpinBox::CustomValue );
QMenu *tracingMenu = new QMenu( this );
QWidgetAction *widgetAction = new QWidgetAction( tracingMenu );
QVBoxLayout *tracingWidgetLayout = new QVBoxLayout;
tracingWidgetLayout->addWidget( new QLabel( "Offset" ) );
tracingWidgetLayout->addWidget( mTracingOffsetSpinBox );
QWidget *tracingWidget = new QWidget;
tracingWidget->setLayout( tracingWidgetLayout );
widgetAction->setDefaultWidget( tracingWidget );
tracingMenu->addAction( widgetAction );
mEnableTracingAction->setMenu( tracingMenu );

// layout
if ( mDisplayMode == ToolBar )
{
Expand Down
5 changes: 5 additions & 0 deletions src/app/qgssnappingwidget.h
Expand Up @@ -24,6 +24,7 @@ class QFont;
class QToolButton;
class QTreeView;

class QgsDoubleSpinBox;
class QgsLayerTreeGroup;
class QgsLayerTreeNode;
class QgsLayerTreeView;
Expand Down Expand Up @@ -74,6 +75,9 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
*/
QAction *enableTracingAction() { return mEnableTracingAction; }

//! Returns spin box used to set offset for tracing
QgsDoubleSpinBox *tracingOffsetSpinBox() { return mTracingOffsetSpinBox; }

signals:
void snappingConfigChanged();

Expand Down Expand Up @@ -136,6 +140,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
QAction *mTopologicalEditingAction = nullptr;
QAction *mIntersectionSnappingAction = nullptr;
QAction *mEnableTracingAction = nullptr;
QgsDoubleSpinBox *mTracingOffsetSpinBox = nullptr;
QTreeView *mLayerTreeView = nullptr;

void cleanGroup( QgsLayerTreeNode *node );
Expand Down
43 changes: 43 additions & 0 deletions src/core/qgstracer.cpp
Expand Up @@ -615,6 +615,25 @@ void QgsTracer::setExtent( const QgsRectangle &extent )
invalidateGraph();
}

void QgsTracer::setOffset( double offset )
{
mOffset = offset;
}

void QgsTracer::offsetParameters( int &quadSegments, int &joinStyle, double &miterLimit )
{
quadSegments = mOffsetSegments;
joinStyle = mOffsetJoinStyle;
miterLimit = mOffsetMiterLimit;
}

void QgsTracer::setOffsetParameters( int quadSegments, int joinStyle, double miterLimit )
{
mOffsetSegments = quadSegments;
mOffsetJoinStyle = joinStyle;
mOffsetMiterLimit = miterLimit;
}

bool QgsTracer::init()
{
if ( mGraph )
Expand Down Expand Up @@ -695,6 +714,30 @@ QVector<QgsPointXY> QgsTracer::findShortestPath( const QgsPointXY &p1, const Qgs

resetGraph( *mGraph );

if ( !points.isEmpty() && mOffset != 0 )
{
QList<QgsPointXY> pointsInput( points.toList() );
QgsLineString linestring( pointsInput );
std::unique_ptr<QgsGeometryEngine> linestringEngine( QgsGeometry::createGeometryEngine( &linestring ) );
std::unique_ptr<QgsAbstractGeometry> linestringOffset( linestringEngine->offsetCurve( mOffset, mOffsetSegments, mOffsetJoinStyle, mOffsetMiterLimit ) );
if ( QgsLineString *ls2 = qgsgeometry_cast<QgsLineString *>( linestringOffset.get() ) )
{
points.clear();
for ( int i = 0; i < ls2->numPoints(); ++i )
points << QgsPointXY( ls2->pointN( i ) );

// sometimes (with negative offset?) the resulting curve is reversed
if ( points.count() >= 2 )
{
QgsPointXY res1 = points.first(), res2 = points.last();
double diffNormal = res1.distance( p1 ) + res2.distance( p2 );
double diffReversed = res1.distance( p2 ) + res2.distance( p1 );
if ( diffReversed < diffNormal )
std::reverse( points.begin(), points.end() );
}
}
}

if ( error )
*error = points.isEmpty() ? ErrNoPath : ErrNone;

Expand Down
35 changes: 35 additions & 0 deletions src/core/qgstracer.h
Expand Up @@ -63,6 +63,32 @@ class CORE_EXPORT QgsTracer : public QObject
//! Set extent to which graph's features will be limited (empty extent means no limit)
void setExtent( const QgsRectangle &extent );

/**
* Get offset in map units that should be applied to the traced paths returned from findShortestPath().
* Positive offset for right side, negative offset for left side.
* \since QGIS 3.0
*/
double offset() const { return mOffset; }

/**
* Set offset in map units that should be applied to the traced paths returned from findShortestPath().
* Positive offset for right side, negative offset for left side.
* \since QGIS 3.0
*/
void setOffset( double offset );

/**
* Get extra parameters for offset curve algorithm (used when offset is non-zero)
* \since QGIS 3.0
*/
void offsetParameters( int &quadSegments SIP_OUT, int &joinStyle SIP_OUT, double &miterLimit SIP_OUT );

/**
* Set extra parameters for offset curve algorithm (used when offset is non-zero)
* \since QGIS 3.0
*/
void setOffsetParameters( int quadSegments, int joinStyle, double miterLimit );

//! Get maximum possible number of features in graph. If the number is exceeded, graph is not created.
int maxFeatureCount() const { return mMaxFeatureCount; }
//! Get maximum possible number of features in graph. If the number is exceeded, graph is not created.
Expand Down Expand Up @@ -138,6 +164,15 @@ class CORE_EXPORT QgsTracer : public QObject
//! Extent for graph building (empty extent means no limit)
QgsRectangle mExtent;

//! Offset in map units that should be applied to the traced paths
double mOffset = 0;
//! Offset parameter: Number of segments (approximation of circle quarter) when using round join style
int mOffsetSegments = 8;
//! Offset parameter: Join style (1 = round, 2 = miter, 3 = bevel)
int mOffsetJoinStyle = 2;
//! Offset parameter: Limit for miter join style
double mOffsetMiterLimit = 5.;

/**
* Limit of how many features can be in the graph (0 means no limit).
* This is to avoid possibly long graph preparation for complicated layers
Expand Down
2 changes: 2 additions & 0 deletions src/gui/qgsmaptooladvanceddigitizing.h
Expand Up @@ -106,6 +106,8 @@ class GUI_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapToolEdit
*/
void setAutoSnapEnabled( bool enabled ) { mAutoSnapEnabled = enabled; }

public:

/**
* Override this method when subclassing this class.
* This will receive adapted events from the cad system whenever a
Expand Down
81 changes: 74 additions & 7 deletions src/gui/qgsmaptoolcapture.cpp
Expand Up @@ -142,6 +142,12 @@ QgsPointXY QgsMapToolCapture::tracingStartPoint()
QgsMapLayer *layer = mCanvas->currentLayer();
if ( !layer )
return QgsPointXY();

// if we have starting point from previous trace, then preferably use that one
// (useful when tracing with offset)
if ( mTracingStartPoint != QgsPointXY() )
return mTracingStartPoint;

QgsPoint v = mCaptureCurve.endPoint();
return toMapCoordinates( layer, QgsPointXY( v.x(), v.y() ) );
}
Expand Down Expand Up @@ -179,6 +185,26 @@ bool QgsMapToolCapture::tracingMouseMove( QgsMapMouseEvent *e )
if ( mCaptureMode == CapturePolygon )
mTempRubberBand->addPoint( *mRubberBand->getPoint( 0, 0 ), false );

// if there is offset, we need to fix the rubber bands to make sure they are aligned correctly.
// There are two cases we need to sort out:
// 1. the last point of mRubberBand may need to be moved off the traced curve to respect the offset
// 2. extra first point of mTempRubberBand may be needed if there is gap between where mRubberBand ends and trace starts
if ( mRubberBand->numberOfVertices() != 0 )
{
QgsPointXY lastPoint = *mRubberBand->getPoint( 0, mRubberBand->numberOfVertices() - 1 );
if ( lastPoint == pt0 && points[0] != lastPoint )
{
// if rubber band had just one point, for some strange reason it contains the point twice
// we only want to move the last point if there are multiple points already
if ( mRubberBand->numberOfVertices() > 2 || ( mRubberBand->numberOfVertices() == 2 && *mRubberBand->getPoint( 0, 0 ) != *mRubberBand->getPoint( 0, 1 ) ) )
mRubberBand->movePoint( points[0] );
}
else
{
mTempRubberBand->addPoint( lastPoint, false );
}
}

// update rubberband
for ( int i = 0; i < points.count(); ++i )
mTempRubberBand->addPoint( points.at( i ), i == points.count() - 1 );
Expand Down Expand Up @@ -225,22 +251,53 @@ bool QgsMapToolCapture::tracingAddVertex( const QgsPointXY &point )
if ( points.isEmpty() )
return false; // ignore the vertex - can't find path to the end point!

if ( !mCaptureCurve.isEmpty() )
{
QgsPoint lp; // in layer coords
if ( nextPoint( QgsPoint( pt0 ), lp ) != 0 )
return false;
QgsPoint last;
QgsVertexId::VertexType type;
mCaptureCurve.pointAt( mCaptureCurve.numPoints() - 1, last, type );
if ( last == lp )
{
// remove the last point in the curve if it is the same as our first point
if ( mCaptureCurve.numPoints() != 2 )
mCaptureCurve.deleteVertex( QgsVertexId( 0, 0, mCaptureCurve.numPoints() - 1 ) );
else
{
// there is a strange behavior in deleteVertex() that with just two points
// the whole curve is cleared - so we need to do this little dance to work it around
QgsPoint first = mCaptureCurve.startPoint();
mCaptureCurve.clear();
mCaptureCurve.addVertex( first );
}
// for unknown reasons, rubber band has 2 points even if only one point has been added - handle that case
if ( mRubberBand->numberOfVertices() == 2 && *mRubberBand->getPoint( 0, 0 ) == *mRubberBand->getPoint( 0, 1 ) )
mRubberBand->removeLastPoint();
mRubberBand->removeLastPoint();
mSnappingMatches.removeLast();
}
}

// transform points
QgsPointSequence layerPoints;
QgsPoint lp; // in layer coords
for ( int i = 1; i < points.count(); ++i )
for ( int i = 0; i < points.count(); ++i )
{
if ( nextPoint( QgsPoint( points[i] ), lp ) != 0 )
return false;
layerPoints << lp;
}

for ( int i = 1; i < points.count(); ++i )
for ( int i = 0; i < points.count(); ++i )
{
if ( points[i] == points[i - 1] )
if ( i == 0 && !mCaptureCurve.isEmpty() && mCaptureCurve.endPoint() == layerPoints[0] )
continue; // avoid duplicate of the first vertex
if ( i > 0 && points[i] == points[i - 1] )
continue; // avoid duplicate vertices if there are any
mRubberBand->addPoint( points[i], i == points.count() - 1 );
mCaptureCurve.addVertex( layerPoints[i - 1] );
mCaptureCurve.addVertex( layerPoints[i] );
mSnappingMatches.append( QgsPointLocator::Match() );
}

Expand Down Expand Up @@ -291,9 +348,7 @@ void QgsMapToolCapture::cadCanvasMoveEvent( QgsMapMouseEvent *e )

if ( !hasTrace )
{
if ( mCaptureCurve.numPoints() > 0 &&
( ( mCaptureMode == CaptureLine && mTempRubberBand->numberOfVertices() != 2 ) ||
( mCaptureMode == CapturePolygon && mTempRubberBand->numberOfVertices() != 3 ) ) )
if ( mCaptureCurve.numPoints() > 0 )
{
// fix temporary rubber band after tracing which may have added multiple points
mTempRubberBand->reset( mCaptureMode == CapturePolygon ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::LineGeometry );
Expand All @@ -303,6 +358,10 @@ void QgsMapToolCapture::cadCanvasMoveEvent( QgsMapMouseEvent *e )
QgsPointXY mapPt = toMapCoordinates( qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ), QgsPointXY( pt.x(), pt.y() ) );
mTempRubberBand->addPoint( mapPt );
mTempRubberBand->addPoint( point );

// fix existing rubber band after tracing - the last point may have been moved if using offset
if ( mRubberBand->numberOfVertices() )
mRubberBand->movePoint( mapPt );
}
else
mTempRubberBand->movePoint( point );
Expand Down Expand Up @@ -419,6 +478,10 @@ int QgsMapToolCapture::addVertex( const QgsPointXY &point, const QgsPointLocator
traceCreated = tracingAddVertex( point );
}

// keep new tracing start point if we created a trace. This is useful when tracing with
// offset so that the user stays "snapped"
mTracingStartPoint = traceCreated ? point : QgsPointXY();

if ( !traceCreated )
{
// ordinary digitizing
Expand Down Expand Up @@ -499,6 +562,8 @@ QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const

void QgsMapToolCapture::undo()
{
mTracingStartPoint = QgsPointXY();

if ( mRubberBand )
{
int rubberBandSize = mRubberBand->numberOfVertices();
Expand Down Expand Up @@ -585,6 +650,8 @@ void QgsMapToolCapture::stopCapturing()

mGeomErrors.clear();

mTracingStartPoint = QgsPointXY();

#ifdef Q_OS_WIN
Q_FOREACH ( QWidget *w, qApp->topLevelWidgets() )
{
Expand Down

0 comments on commit 9d8734c

Please sign in to comment.