mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-19 00:04:52 -04:00
Refactor CAD editing alignment logic to new QgsCadUtils class
This commit is contained in:
parent
68bb68d176
commit
878dfddd3b
@ -476,6 +476,7 @@ QgsAdvancedDigitizingDockWidget {#qgis_api_break_3_0_QgsAdvancedDigitizin
|
|||||||
|
|
||||||
- canvasPressEvent(), canvasReleaseEvent(), canvasMoveEvent() were removed. Handling of events is done in QgsMapToolAdvancedDigitizing.
|
- canvasPressEvent(), canvasReleaseEvent(), canvasMoveEvent() were removed. Handling of events is done in QgsMapToolAdvancedDigitizing.
|
||||||
- snappingMode() was removed. Advanced digitizing now always uses project's snapping configuration.
|
- snappingMode() was removed. Advanced digitizing now always uses project's snapping configuration.
|
||||||
|
- lineCircleIntersection() was removed
|
||||||
|
|
||||||
|
|
||||||
QgsApplication {#qgis_api_break_3_0_QgsApplication}
|
QgsApplication {#qgis_api_break_3_0_QgsApplication}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QgsAdvancedDigitizingDockWidget : QgsDockWidget
|
class QgsAdvancedDigitizingDockWidget : QgsDockWidget
|
||||||
{
|
{
|
||||||
%Docstring
|
%Docstring
|
||||||
@ -151,14 +150,6 @@ class QgsAdvancedDigitizingDockWidget : QgsDockWidget
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool lineCircleIntersection( const QgsPointXY ¢er, const double radius, const QList<QgsPointXY> &segment, QgsPointXY &intersection );
|
|
||||||
%Docstring
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
from the two solutions, the intersection will be set to the closest point
|
|
||||||
:rtype: bool
|
|
||||||
%End
|
|
||||||
|
|
||||||
explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0 );
|
explicit QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent = 0 );
|
||||||
%Docstring
|
%Docstring
|
||||||
Create an advanced digitizing dock widget
|
Create an advanced digitizing dock widget
|
||||||
|
@ -139,6 +139,7 @@ SET(QGIS_CORE_SRCS
|
|||||||
qgscachedfeatureiterator.cpp
|
qgscachedfeatureiterator.cpp
|
||||||
qgscacheindex.cpp
|
qgscacheindex.cpp
|
||||||
qgscacheindexfeatureid.cpp
|
qgscacheindexfeatureid.cpp
|
||||||
|
qgscadutils.cpp
|
||||||
qgsclipper.cpp
|
qgsclipper.cpp
|
||||||
qgscolorramp.cpp
|
qgscolorramp.cpp
|
||||||
qgscolorscheme.cpp
|
qgscolorscheme.cpp
|
||||||
@ -792,6 +793,7 @@ SET(QGIS_CORE_HDRS
|
|||||||
qgscachedfeatureiterator.h
|
qgscachedfeatureiterator.h
|
||||||
qgscacheindex.h
|
qgscacheindex.h
|
||||||
qgscacheindexfeatureid.h
|
qgscacheindexfeatureid.h
|
||||||
|
qgscadutils.h
|
||||||
qgsclipper.h
|
qgsclipper.h
|
||||||
qgscolorramp.h
|
qgscolorramp.h
|
||||||
qgscolorscheme.h
|
qgscolorscheme.h
|
||||||
|
368
src/core/qgscadutils.cpp
Normal file
368
src/core/qgscadutils.cpp
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgscadutils.cpp
|
||||||
|
-------------------
|
||||||
|
begin : September 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. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include "qgscadutils.h"
|
||||||
|
|
||||||
|
#include "qgslogger.h"
|
||||||
|
#include "qgssnappingutils.h"
|
||||||
|
|
||||||
|
// tolerances for soft constraints (last values, and common angles)
|
||||||
|
// for angles, both tolerance in pixels and degrees are used for better performance
|
||||||
|
static const double SOFT_CONSTRAINT_TOLERANCE_PIXEL = 15;
|
||||||
|
static const double SOFT_CONSTRAINT_TOLERANCE_DEGREES = 10;
|
||||||
|
|
||||||
|
|
||||||
|
/// @cond PRIVATE
|
||||||
|
struct EdgesOnlyFilter : public QgsPointLocator::MatchFilter
|
||||||
|
{
|
||||||
|
bool acceptMatch( const QgsPointLocator::Match &m ) override { return m.hasEdge(); }
|
||||||
|
};
|
||||||
|
/// @endcond
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: move to geometry utils (if not already there)
|
||||||
|
static bool lineCircleIntersection( const QgsPointXY ¢er, const double radius, const QgsPointXY &edgePt0, const QgsPointXY &edgePt1, QgsPointXY &intersection )
|
||||||
|
{
|
||||||
|
// formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html
|
||||||
|
|
||||||
|
const double x1 = edgePt0.x() - center.x();
|
||||||
|
const double y1 = edgePt0.y() - center.y();
|
||||||
|
const double x2 = edgePt1.x() - center.x();
|
||||||
|
const double y2 = edgePt1.y() - center.y();
|
||||||
|
const double dx = x2 - x1;
|
||||||
|
const double dy = y2 - y1;
|
||||||
|
|
||||||
|
const double dr = std::sqrt( std::pow( dx, 2 ) + std::pow( dy, 2 ) );
|
||||||
|
const double d = x1 * y2 - x2 * y1;
|
||||||
|
|
||||||
|
const double disc = std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 );
|
||||||
|
|
||||||
|
if ( disc < 0 )
|
||||||
|
{
|
||||||
|
//no intersection or tangent
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// two solutions
|
||||||
|
const int sgnDy = dy < 0 ? -1 : 1;
|
||||||
|
|
||||||
|
const double ax = center.x() + ( d * dy + sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
||||||
|
const double ay = center.y() + ( -d * dx + std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
||||||
|
const QgsPointXY p1( ax, ay );
|
||||||
|
|
||||||
|
const double bx = center.x() + ( d * dy - sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
||||||
|
const double by = center.y() + ( -d * dx - std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
||||||
|
const QgsPointXY p2( bx, by );
|
||||||
|
|
||||||
|
// snap to nearest intersection
|
||||||
|
|
||||||
|
if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) )
|
||||||
|
{
|
||||||
|
intersection.set( p1.x(), p1.y() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intersection.set( p2.x(), p2.y() );
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx )
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointOutput res;
|
||||||
|
res.valid = true;
|
||||||
|
res.softLockCommonAngle = -1;
|
||||||
|
|
||||||
|
// try to snap to anything
|
||||||
|
QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint );
|
||||||
|
QgsPointXY point = snapMatch.isValid() ? snapMatch.point() : originalMapPoint;
|
||||||
|
|
||||||
|
// try to snap explicitly to a segment - useful for some constraints
|
||||||
|
QgsPointXY edgePt0, edgePt1;
|
||||||
|
EdgesOnlyFilter edgesOnlyFilter;
|
||||||
|
QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter );
|
||||||
|
if ( edgeMatch.hasEdge() )
|
||||||
|
edgeMatch.edgePoints( edgePt0, edgePt1 );
|
||||||
|
|
||||||
|
res.edgeMatch = edgeMatch;
|
||||||
|
|
||||||
|
QgsPointXY previousPt, penultimatePt;
|
||||||
|
if ( ctx.cadPointList.count() >= 2 )
|
||||||
|
previousPt = ctx.cadPointList.at( 1 );
|
||||||
|
if ( ctx.cadPointList.count() >= 3 )
|
||||||
|
penultimatePt = ctx.cadPointList.at( 2 );
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- X constraint
|
||||||
|
if ( ctx.xConstraint.locked )
|
||||||
|
{
|
||||||
|
if ( !ctx.xConstraint.relative )
|
||||||
|
{
|
||||||
|
point.setX( ctx.xConstraint.value );
|
||||||
|
}
|
||||||
|
else if ( ctx.cadPointList.count() >= 2 )
|
||||||
|
{
|
||||||
|
point.setX( previousPt.x() + ctx.xConstraint.value );
|
||||||
|
}
|
||||||
|
if ( edgeMatch.hasEdge() && !ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
// intersect with snapped segment line at X ccordinate
|
||||||
|
const double dx = edgePt1.x() - edgePt0.x();
|
||||||
|
if ( dx == 0 )
|
||||||
|
{
|
||||||
|
point.setY( edgePt0.y() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double dy = edgePt1.y() - edgePt0.y();
|
||||||
|
point.setY( edgePt0.y() + ( dy * ( point.x() - edgePt0.x() ) ) / dx );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- Y constraint
|
||||||
|
if ( ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
if ( !ctx.yConstraint.relative )
|
||||||
|
{
|
||||||
|
point.setY( ctx.yConstraint.value );
|
||||||
|
}
|
||||||
|
else if ( ctx.cadPointList.count() >= 2 )
|
||||||
|
{
|
||||||
|
point.setY( previousPt.y() + ctx.yConstraint.value );
|
||||||
|
}
|
||||||
|
if ( edgeMatch.hasEdge() && !ctx.xConstraint.locked )
|
||||||
|
{
|
||||||
|
// intersect with snapped segment line at Y ccordinate
|
||||||
|
const double dy = edgePt1.y() - edgePt0.y();
|
||||||
|
if ( dy == 0 )
|
||||||
|
{
|
||||||
|
point.setX( edgePt0.x() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double dx = edgePt1.x() - edgePt0.x();
|
||||||
|
point.setX( edgePt0.x() + ( dx * ( point.y() - edgePt0.y() ) ) / dy );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- Common Angle constraint
|
||||||
|
if ( !ctx.angleConstraint.locked && ctx.cadPointList.count() >= 2 && ctx.commonAngleConstraint.locked && ctx.commonAngleConstraint.value != 0 )
|
||||||
|
{
|
||||||
|
double commonAngle = ctx.commonAngleConstraint.value * M_PI / 180;
|
||||||
|
// see if soft common angle constraint should be performed
|
||||||
|
// only if not in HardLock mode
|
||||||
|
double softAngle = std::atan2( point.y() - previousPt.y(),
|
||||||
|
point.x() - previousPt.x() );
|
||||||
|
double deltaAngle = 0;
|
||||||
|
if ( ctx.commonAngleConstraint.relative && ctx.cadPointList.count() >= 3 )
|
||||||
|
{
|
||||||
|
// compute the angle relative to the last segment (0° is aligned with last segment)
|
||||||
|
deltaAngle = std::atan2( previousPt.y() - penultimatePt.y(),
|
||||||
|
previousPt.x() - penultimatePt.x() );
|
||||||
|
softAngle -= deltaAngle;
|
||||||
|
}
|
||||||
|
int quo = std::round( softAngle / commonAngle );
|
||||||
|
if ( std::fabs( softAngle - quo * commonAngle ) * 180.0 * M_1_PI <= SOFT_CONSTRAINT_TOLERANCE_DEGREES )
|
||||||
|
{
|
||||||
|
// also check the distance in pixel to the line, otherwise it's too sticky at long ranges
|
||||||
|
softAngle = quo * commonAngle;
|
||||||
|
// http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
|
||||||
|
// use the direction vector (cos(a),sin(a)) from previous point. |x2-x1|=1 since sin2+cos2=1
|
||||||
|
const double dist = std::fabs( std::cos( softAngle + deltaAngle ) * ( previousPt.y() - point.y() )
|
||||||
|
- std::sin( softAngle + deltaAngle ) * ( previousPt.x() - point.x() ) );
|
||||||
|
if ( dist / ctx.mapUnitsPerPixel < SOFT_CONSTRAINT_TOLERANCE_PIXEL )
|
||||||
|
{
|
||||||
|
res.softLockCommonAngle = 180.0 / M_PI * softAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// angle can be locked in one of the two ways:
|
||||||
|
// 1. "hard" lock defined by the user
|
||||||
|
// 2. "soft" lock from common angle (e.g. 45 degrees)
|
||||||
|
bool angleLocked = false, angleRelative = false;
|
||||||
|
int angleValueDeg = 0;
|
||||||
|
if ( ctx.angleConstraint.locked )
|
||||||
|
{
|
||||||
|
angleLocked = true;
|
||||||
|
angleRelative = ctx.angleConstraint.relative;
|
||||||
|
angleValueDeg = ctx.angleConstraint.value;
|
||||||
|
}
|
||||||
|
else if ( res.softLockCommonAngle != -1 )
|
||||||
|
{
|
||||||
|
angleLocked = true;
|
||||||
|
angleRelative = ctx.commonAngleConstraint.relative;
|
||||||
|
angleValueDeg = res.softLockCommonAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- Angle constraint
|
||||||
|
// input angles are in degrees
|
||||||
|
if ( angleLocked )
|
||||||
|
{
|
||||||
|
double angleValue = angleValueDeg * M_PI / 180;
|
||||||
|
if ( angleRelative && ctx.cadPointList.count() >= 3 )
|
||||||
|
{
|
||||||
|
// compute the angle relative to the last segment (0° is aligned with last segment)
|
||||||
|
angleValue += std::atan2( previousPt.y() - penultimatePt.y(),
|
||||||
|
previousPt.x() - penultimatePt.x() );
|
||||||
|
}
|
||||||
|
|
||||||
|
double cosa = std::cos( angleValue );
|
||||||
|
double sina = std::sin( angleValue );
|
||||||
|
double v = ( point.x() - previousPt.x() ) * cosa + ( point.y() - previousPt.y() ) * sina;
|
||||||
|
if ( ctx.xConstraint.locked && ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
// do nothing if both X,Y are already locked
|
||||||
|
}
|
||||||
|
else if ( ctx.xConstraint.locked )
|
||||||
|
{
|
||||||
|
if ( qgsDoubleNear( cosa, 0.0 ) )
|
||||||
|
{
|
||||||
|
res.valid = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double x = ctx.xConstraint.value;
|
||||||
|
if ( !ctx.xConstraint.relative )
|
||||||
|
{
|
||||||
|
x -= previousPt.x();
|
||||||
|
}
|
||||||
|
point.setY( previousPt.y() + x * sina / cosa );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
if ( qgsDoubleNear( sina, 0.0 ) )
|
||||||
|
{
|
||||||
|
res.valid = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double y = ctx.yConstraint.value;
|
||||||
|
if ( !ctx.yConstraint.relative )
|
||||||
|
{
|
||||||
|
y -= previousPt.y();
|
||||||
|
}
|
||||||
|
point.setX( previousPt.x() + y * cosa / sina );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
point.setX( previousPt.x() + cosa * v );
|
||||||
|
point.setY( previousPt.y() + sina * v );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( edgeMatch.hasEdge() && !ctx.distanceConstraint.locked )
|
||||||
|
{
|
||||||
|
// magnetize to the intersection of the snapped segment and the lockedAngle
|
||||||
|
|
||||||
|
// line of previous point + locked angle
|
||||||
|
const double x1 = previousPt.x();
|
||||||
|
const double y1 = previousPt.y();
|
||||||
|
const double x2 = previousPt.x() + cosa;
|
||||||
|
const double y2 = previousPt.y() + sina;
|
||||||
|
// line of snapped segment
|
||||||
|
const double x3 = edgePt0.x();
|
||||||
|
const double y3 = edgePt0.y();
|
||||||
|
const double x4 = edgePt1.x();
|
||||||
|
const double y4 = edgePt1.y();
|
||||||
|
|
||||||
|
const double d = ( x1 - x2 ) * ( y3 - y4 ) - ( y1 - y2 ) * ( x3 - x4 );
|
||||||
|
|
||||||
|
// do not compute intersection if lines are almost parallel
|
||||||
|
// this threshold might be adapted
|
||||||
|
if ( std::fabs( d ) > 0.01 )
|
||||||
|
{
|
||||||
|
point.setX( ( ( x3 - x4 ) * ( x1 * y2 - y1 * x2 ) - ( x1 - x2 ) * ( x3 * y4 - y3 * x4 ) ) / d );
|
||||||
|
point.setY( ( ( y3 - y4 ) * ( x1 * y2 - y1 * x2 ) - ( y1 - y2 ) * ( x3 * y4 - y3 * x4 ) ) / d );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- Distance constraint
|
||||||
|
if ( ctx.distanceConstraint.locked && ctx.cadPointList.count() >= 2 )
|
||||||
|
{
|
||||||
|
if ( ctx.xConstraint.locked || ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
// perform both to detect errors in constraints
|
||||||
|
if ( ctx.xConstraint.locked )
|
||||||
|
{
|
||||||
|
QgsPointXY verticalPt0( ctx.xConstraint.value, point.y() );
|
||||||
|
QgsPointXY verticalPt1( ctx.xConstraint.value, point.y() + 1 );
|
||||||
|
res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, verticalPt0, verticalPt1, point );
|
||||||
|
}
|
||||||
|
if ( ctx.yConstraint.locked )
|
||||||
|
{
|
||||||
|
QgsPointXY horizontalPt0( point.x(), ctx.yConstraint.value );
|
||||||
|
QgsPointXY horizontalPt1( point.x() + 1, ctx.yConstraint.value );
|
||||||
|
res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, horizontalPt0, horizontalPt1, point );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double dist = std::sqrt( point.sqrDist( previousPt ) );
|
||||||
|
if ( dist == 0 )
|
||||||
|
{
|
||||||
|
// handle case where mouse is over origin and distance constraint is enabled
|
||||||
|
// take arbitrary horizontal line
|
||||||
|
point.set( previousPt.x() + ctx.distanceConstraint.value, previousPt.y() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double vP = ctx.distanceConstraint.value / dist;
|
||||||
|
point.set( previousPt.x() + ( point.x() - previousPt.x() ) * vP,
|
||||||
|
previousPt.y() + ( point.y() - previousPt.y() ) * vP );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( edgeMatch.hasEdge() && !ctx.angleConstraint.locked )
|
||||||
|
{
|
||||||
|
// we will magnietize to the intersection of that segment and the lockedDistance !
|
||||||
|
res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, edgePt0, edgePt1, point );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************
|
||||||
|
// ---- calculate CAD values
|
||||||
|
QgsDebugMsgLevel( QString( "point: %1 %2" ).arg( point.x() ).arg( point.y() ), 4 );
|
||||||
|
QgsDebugMsgLevel( QString( "previous point: %1 %2" ).arg( previousPt.x() ).arg( previousPt.y() ), 4 );
|
||||||
|
QgsDebugMsgLevel( QString( "penultimate point: %1 %2" ).arg( penultimatePt.x() ).arg( penultimatePt.y() ), 4 );
|
||||||
|
//QgsDebugMsg( QString( "dx: %1 dy: %2" ).arg( point.x() - previousPt.x() ).arg( point.y() - previousPt.y() ) );
|
||||||
|
//QgsDebugMsg( QString( "ddx: %1 ddy: %2" ).arg( previousPt.x() - penultimatePt.x() ).arg( previousPt.y() - penultimatePt.y() ) );
|
||||||
|
|
||||||
|
res.finalMapPoint = point;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCadUtils::AlignMapPointContext::dump() const
|
||||||
|
{
|
||||||
|
QgsDebugMsg( "Constraints (locked / relative / value" );
|
||||||
|
QgsDebugMsg( QString( "Angle: %1 %2 %3" ).arg( angleConstraint.locked ).arg( angleConstraint.relative ).arg( angleConstraint.value ) );
|
||||||
|
QgsDebugMsg( QString( "Distance: %1 %2 %3" ).arg( distanceConstraint.locked ).arg( distanceConstraint.relative ).arg( distanceConstraint.value ) );
|
||||||
|
QgsDebugMsg( QString( "X: %1 %2 %3" ).arg( xConstraint.locked ).arg( xConstraint.relative ).arg( xConstraint.value ) );
|
||||||
|
QgsDebugMsg( QString( "Y: %1 %2 %3" ).arg( yConstraint.locked ).arg( yConstraint.relative ).arg( yConstraint.value ) );
|
||||||
|
}
|
104
src/core/qgscadutils.h
Normal file
104
src/core/qgscadutils.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgscadutils.h
|
||||||
|
-------------------
|
||||||
|
begin : September 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 QGSCADUTILS_H
|
||||||
|
#define QGSCADUTILS_H
|
||||||
|
|
||||||
|
#include "qgis_core.h"
|
||||||
|
|
||||||
|
#include "qgspointlocator.h"
|
||||||
|
|
||||||
|
class QgsSnappingUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The QgsCadUtils class provides routines for CAD editing.
|
||||||
|
*
|
||||||
|
* \since QGIS 3.0
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsCadUtils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! Structure with details of one constraint
|
||||||
|
struct AlignMapPointConstraint
|
||||||
|
{
|
||||||
|
AlignMapPointConstraint( bool locked = false, bool relative = false, double value = 0 )
|
||||||
|
: locked( locked )
|
||||||
|
, relative( relative )
|
||||||
|
, value( value )
|
||||||
|
{}
|
||||||
|
|
||||||
|
//! Whether the constraint is active, i.e. should be considered
|
||||||
|
bool locked;
|
||||||
|
//! Whether the value is relative to previous value
|
||||||
|
bool relative;
|
||||||
|
//! Numeric value of the constraint (coordinate/distance in map units or angle in degrees)
|
||||||
|
double value;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Structure defining all constraints for alignMapPoint() method
|
||||||
|
struct AlignMapPointContext
|
||||||
|
{
|
||||||
|
//! Snapping utils that will be used to snap point to map. Must not be null
|
||||||
|
QgsSnappingUtils *snappingUtils;
|
||||||
|
//! Map units/pixel ratio from map canvas. Needed for
|
||||||
|
double mapUnitsPerPixel;
|
||||||
|
|
||||||
|
//! Constraint for X coordinate
|
||||||
|
AlignMapPointConstraint xConstraint;
|
||||||
|
//! Constraint for Y coordinate
|
||||||
|
AlignMapPointConstraint yConstraint;
|
||||||
|
//! Constraint for distance
|
||||||
|
AlignMapPointConstraint distanceConstraint;
|
||||||
|
//! Constraint for angle
|
||||||
|
AlignMapPointConstraint angleConstraint;
|
||||||
|
//! Constraint for soft lock to a common angle
|
||||||
|
AlignMapPointConstraint commonAngleConstraint;
|
||||||
|
|
||||||
|
//! List of recent CAD points in map coordinates. These are used to turn relative constraints to absolute.
|
||||||
|
//! First point is the most recent point. Currently using only "previous" point (index 1) and "penultimate"
|
||||||
|
//! point (index 2) for alignment purposes.
|
||||||
|
QList<QgsPointXY> cadPointList;
|
||||||
|
|
||||||
|
void dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Structure returned from alignMapPoint() method
|
||||||
|
struct AlignMapPointOutput
|
||||||
|
{
|
||||||
|
//! Whether the combination of constraints is actually valid
|
||||||
|
bool valid;
|
||||||
|
|
||||||
|
//! map point aligned according to the constraints
|
||||||
|
QgsPointXY finalMapPoint;
|
||||||
|
|
||||||
|
//! Snapped segment - only valid if actually used for something
|
||||||
|
QgsPointLocator::Match edgeMatch;
|
||||||
|
|
||||||
|
//! Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
|
||||||
|
int softLockCommonAngle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies X/Y/angle/distance constraints from the given context to a map point.
|
||||||
|
* Returns a structure containing aligned map point, whether the constraints are valid and
|
||||||
|
* some extra information.
|
||||||
|
*/
|
||||||
|
static AlignMapPointOutput alignMapPoint( const QgsPointXY &originalMapPoint, const AlignMapPointContext &ctx );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QGSCADUTILS_H
|
@ -20,6 +20,7 @@
|
|||||||
#include "qgsadvanceddigitizingdockwidget.h"
|
#include "qgsadvanceddigitizingdockwidget.h"
|
||||||
#include "qgsadvanceddigitizingcanvasitem.h"
|
#include "qgsadvanceddigitizingcanvasitem.h"
|
||||||
#include "qgsapplication.h"
|
#include "qgsapplication.h"
|
||||||
|
#include "qgscadutils.h"
|
||||||
#include "qgsexpression.h"
|
#include "qgsexpression.h"
|
||||||
#include "qgslogger.h"
|
#include "qgslogger.h"
|
||||||
#include "qgsmapcanvas.h"
|
#include "qgsmapcanvas.h"
|
||||||
@ -33,64 +34,6 @@
|
|||||||
#include "qgssnappingutils.h"
|
#include "qgssnappingutils.h"
|
||||||
#include "qgsproject.h"
|
#include "qgsproject.h"
|
||||||
|
|
||||||
/// @cond PRIVATE
|
|
||||||
struct EdgesOnlyFilter : public QgsPointLocator::MatchFilter
|
|
||||||
{
|
|
||||||
bool acceptMatch( const QgsPointLocator::Match &m ) override { return m.hasEdge(); }
|
|
||||||
};
|
|
||||||
/// @endcond
|
|
||||||
|
|
||||||
|
|
||||||
bool QgsAdvancedDigitizingDockWidget::lineCircleIntersection( const QgsPointXY ¢er, const double radius, const QList<QgsPointXY> &segment, QgsPointXY &intersection )
|
|
||||||
{
|
|
||||||
Q_ASSERT( segment.count() == 2 );
|
|
||||||
|
|
||||||
// formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html
|
|
||||||
|
|
||||||
const double x1 = segment[0].x() - center.x();
|
|
||||||
const double y1 = segment[0].y() - center.y();
|
|
||||||
const double x2 = segment[1].x() - center.x();
|
|
||||||
const double y2 = segment[1].y() - center.y();
|
|
||||||
const double dx = x2 - x1;
|
|
||||||
const double dy = y2 - y1;
|
|
||||||
|
|
||||||
const double dr = std::sqrt( std::pow( dx, 2 ) + std::pow( dy, 2 ) );
|
|
||||||
const double d = x1 * y2 - x2 * y1;
|
|
||||||
|
|
||||||
const double disc = std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 );
|
|
||||||
|
|
||||||
if ( disc < 0 )
|
|
||||||
{
|
|
||||||
//no intersection or tangent
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// two solutions
|
|
||||||
const int sgnDy = dy < 0 ? -1 : 1;
|
|
||||||
|
|
||||||
const double ax = center.x() + ( d * dy + sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
|
||||||
const double ay = center.y() + ( -d * dx + std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
|
||||||
const QgsPointXY p1( ax, ay );
|
|
||||||
|
|
||||||
const double bx = center.x() + ( d * dy - sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
|
||||||
const double by = center.y() + ( -d * dx - std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) );
|
|
||||||
const QgsPointXY p2( bx, by );
|
|
||||||
|
|
||||||
// snap to nearest intersection
|
|
||||||
|
|
||||||
if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) )
|
|
||||||
{
|
|
||||||
intersection.set( p1.x(), p1.y() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
intersection.set( p2.x(), p2.y() );
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent )
|
QgsAdvancedDigitizingDockWidget::QgsAdvancedDigitizingDockWidget( QgsMapCanvas *canvas, QWidget *parent )
|
||||||
: QgsDockWidget( parent )
|
: QgsDockWidget( parent )
|
||||||
@ -563,251 +506,53 @@ void QgsAdvancedDigitizingDockWidget::updateCapacity( bool updateUIwithoutChange
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static QgsCadUtils::AlignMapPointConstraint _constraint( QgsAdvancedDigitizingDockWidget::CadConstraint *c )
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointConstraint constr;
|
||||||
|
constr.locked = c->lockMode() == QgsAdvancedDigitizingDockWidget::CadConstraint::HardLock;
|
||||||
|
constr.relative = c->relative();
|
||||||
|
constr.value = c->value();
|
||||||
|
return constr;
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
||||||
{
|
{
|
||||||
bool res = true;
|
QgsCadUtils::AlignMapPointContext context;
|
||||||
|
context.snappingUtils = mMapCanvas->snappingUtils();
|
||||||
|
context.mapUnitsPerPixel = mMapCanvas->mapUnitsPerPixel();
|
||||||
|
context.xConstraint = _constraint( mXConstraint.get() );
|
||||||
|
context.yConstraint = _constraint( mYConstraint.get() );
|
||||||
|
context.distanceConstraint = _constraint( mDistanceConstraint.get() );
|
||||||
|
context.angleConstraint = _constraint( mAngleConstraint.get() );
|
||||||
|
context.cadPointList = mCadPointList;
|
||||||
|
|
||||||
QgsDebugMsgLevel( "Constraints (locked / relative / value", 4 );
|
context.commonAngleConstraint.locked = true;
|
||||||
QgsDebugMsgLevel( QString( "Angle: %1 %2 %3" ).arg( mAngleConstraint->isLocked() ).arg( mAngleConstraint->relative() ).arg( mAngleConstraint->value() ), 4 );
|
context.commonAngleConstraint.relative = context.angleConstraint.relative;
|
||||||
QgsDebugMsgLevel( QString( "Distance: %1 %2 %3" ).arg( mDistanceConstraint->isLocked() ).arg( mDistanceConstraint->relative() ).arg( mDistanceConstraint->value() ), 4 );
|
context.commonAngleConstraint.value = mCommonAngleConstraint;
|
||||||
QgsDebugMsgLevel( QString( "X: %1 %2 %3" ).arg( mXConstraint->isLocked() ).arg( mXConstraint->relative() ).arg( mXConstraint->value() ), 4 );
|
|
||||||
QgsDebugMsgLevel( QString( "Y: %1 %2 %3" ).arg( mYConstraint->isLocked() ).arg( mYConstraint->relative() ).arg( mYConstraint->value() ), 4 );
|
|
||||||
|
|
||||||
QgsPointXY point = e->snapPoint();
|
QgsCadUtils::AlignMapPointOutput output = QgsCadUtils::alignMapPoint( e->originalMapPoint(), context );
|
||||||
|
|
||||||
mSnappedSegment = snapSegment( e->originalMapPoint() );
|
bool res = output.valid;
|
||||||
|
QgsPointXY point = output.finalMapPoint;
|
||||||
bool previousPointExist, penulPointExist;
|
mSnappedSegment.clear();
|
||||||
QgsPointXY previousPt = previousPoint( &previousPointExist );
|
if ( output.edgeMatch.hasEdge() )
|
||||||
QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
|
|
||||||
|
|
||||||
// *****************************
|
|
||||||
// ---- X constraint
|
|
||||||
if ( mXConstraint->isLocked() )
|
|
||||||
{
|
{
|
||||||
if ( !mXConstraint->relative() )
|
QgsPointXY edgePt0, edgePt1;
|
||||||
{
|
output.edgeMatch.edgePoints( edgePt0, edgePt1 );
|
||||||
point.setX( mXConstraint->value() );
|
mSnappedSegment << edgePt0 << edgePt1;
|
||||||
}
|
}
|
||||||
else if ( mCapacities.testFlag( RelativeCoordinates ) )
|
if ( mAngleConstraint->lockMode() != CadConstraint::HardLock )
|
||||||
{
|
{
|
||||||
point.setX( previousPt.x() + mXConstraint->value() );
|
if ( output.softLockCommonAngle != -1 )
|
||||||
}
|
|
||||||
if ( !mSnappedSegment.isEmpty() && !mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// intersect with snapped segment line at X ccordinate
|
|
||||||
const double dx = mSnappedSegment.at( 1 ).x() - mSnappedSegment.at( 0 ).x();
|
|
||||||
if ( dx == 0 )
|
|
||||||
{
|
|
||||||
point.setY( mSnappedSegment.at( 0 ).y() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const double dy = mSnappedSegment.at( 1 ).y() - mSnappedSegment.at( 0 ).y();
|
|
||||||
point.setY( mSnappedSegment.at( 0 ).y() + ( dy * ( point.x() - mSnappedSegment.at( 0 ).x() ) ) / dx );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// *****************************
|
|
||||||
// ---- Y constraint
|
|
||||||
if ( mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
if ( !mYConstraint->relative() )
|
|
||||||
{
|
|
||||||
point.setY( mYConstraint->value() );
|
|
||||||
}
|
|
||||||
else if ( mCapacities.testFlag( RelativeCoordinates ) )
|
|
||||||
{
|
|
||||||
point.setY( previousPt.y() + mYConstraint->value() );
|
|
||||||
}
|
|
||||||
if ( !mSnappedSegment.isEmpty() && !mXConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// intersect with snapped segment line at Y ccordinate
|
|
||||||
const double dy = mSnappedSegment.at( 1 ).y() - mSnappedSegment.at( 0 ).y();
|
|
||||||
if ( dy == 0 )
|
|
||||||
{
|
|
||||||
point.setX( mSnappedSegment.at( 0 ).x() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const double dx = mSnappedSegment.at( 1 ).x() - mSnappedSegment.at( 0 ).x();
|
|
||||||
point.setX( mSnappedSegment.at( 0 ).x() + ( dx * ( point.y() - mSnappedSegment.at( 0 ).y() ) ) / dy );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// *****************************
|
|
||||||
// ---- Angle constraint
|
|
||||||
// input angles are in degrees
|
|
||||||
if ( mAngleConstraint->lockMode() == CadConstraint::SoftLock )
|
|
||||||
{
|
|
||||||
// reset the lock
|
|
||||||
mAngleConstraint->setLockMode( CadConstraint::NoLock );
|
|
||||||
}
|
|
||||||
if ( !mAngleConstraint->isLocked() && mCapacities.testFlag( AbsoluteAngle ) && mCommonAngleConstraint != 0 )
|
|
||||||
{
|
|
||||||
double commonAngle = mCommonAngleConstraint * M_PI / 180;
|
|
||||||
// see if soft common angle constraint should be performed
|
|
||||||
// only if not in HardLock mode
|
|
||||||
double softAngle = std::atan2( point.y() - previousPt.y(),
|
|
||||||
point.x() - previousPt.x() );
|
|
||||||
double deltaAngle = 0;
|
|
||||||
if ( mAngleConstraint->relative() && mCapacities.testFlag( RelativeAngle ) )
|
|
||||||
{
|
|
||||||
// compute the angle relative to the last segment (0° is aligned with last segment)
|
|
||||||
deltaAngle = std::atan2( previousPt.y() - penultimatePt.y(),
|
|
||||||
previousPt.x() - penultimatePt.x() );
|
|
||||||
softAngle -= deltaAngle;
|
|
||||||
}
|
|
||||||
int quo = std::round( softAngle / commonAngle );
|
|
||||||
if ( std::fabs( softAngle - quo * commonAngle ) * 180.0 * M_1_PI <= SOFT_CONSTRAINT_TOLERANCE_DEGREES )
|
|
||||||
{
|
|
||||||
// also check the distance in pixel to the line, otherwise it's too sticky at long ranges
|
|
||||||
softAngle = quo * commonAngle;
|
|
||||||
// http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
|
|
||||||
// use the direction vector (cos(a),sin(a)) from previous point. |x2-x1|=1 since sin2+cos2=1
|
|
||||||
const double dist = std::fabs( std::cos( softAngle + deltaAngle ) * ( previousPt.y() - point.y() )
|
|
||||||
- std::sin( softAngle + deltaAngle ) * ( previousPt.x() - point.x() ) );
|
|
||||||
if ( dist / mMapCanvas->mapSettings().mapUnitsPerPixel() < SOFT_CONSTRAINT_TOLERANCE_PIXEL )
|
|
||||||
{
|
{
|
||||||
mAngleConstraint->setLockMode( CadConstraint::SoftLock );
|
mAngleConstraint->setLockMode( CadConstraint::SoftLock );
|
||||||
mAngleConstraint->setValue( 180.0 / M_PI * softAngle );
|
mAngleConstraint->setValue( output.softLockCommonAngle );
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( mAngleConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
double angleValue = mAngleConstraint->value() * M_PI / 180;
|
|
||||||
if ( mAngleConstraint->relative() && mCapacities.testFlag( RelativeAngle ) )
|
|
||||||
{
|
|
||||||
// compute the angle relative to the last segment (0° is aligned with last segment)
|
|
||||||
angleValue += std::atan2( previousPt.y() - penultimatePt.y(),
|
|
||||||
previousPt.x() - penultimatePt.x() );
|
|
||||||
}
|
|
||||||
|
|
||||||
double cosa = std::cos( angleValue );
|
|
||||||
double sina = std::sin( angleValue );
|
|
||||||
double v = ( point.x() - previousPt.x() ) * cosa + ( point.y() - previousPt.y() ) * sina;
|
|
||||||
if ( mXConstraint->isLocked() && mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// do nothing if both X,Y are already locked
|
|
||||||
}
|
|
||||||
else if ( mXConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
if ( qgsDoubleNear( cosa, 0.0 ) )
|
|
||||||
{
|
|
||||||
res = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double x = mXConstraint->value();
|
mAngleConstraint->setLockMode( CadConstraint::NoLock );
|
||||||
if ( !mXConstraint->relative() )
|
|
||||||
{
|
|
||||||
x -= previousPt.x();
|
|
||||||
}
|
|
||||||
point.setY( previousPt.y() + x * sina / cosa );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
if ( qgsDoubleNear( sina, 0.0 ) )
|
|
||||||
{
|
|
||||||
res = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double y = mYConstraint->value();
|
|
||||||
if ( !mYConstraint->relative() )
|
|
||||||
{
|
|
||||||
y -= previousPt.y();
|
|
||||||
}
|
|
||||||
point.setX( previousPt.x() + y * cosa / sina );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
point.setX( previousPt.x() + cosa * v );
|
|
||||||
point.setY( previousPt.y() + sina * v );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !mSnappedSegment.isEmpty() && !mDistanceConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// magnetize to the intersection of the snapped segment and the lockedAngle
|
|
||||||
|
|
||||||
// line of previous point + locked angle
|
|
||||||
const double x1 = previousPt.x();
|
|
||||||
const double y1 = previousPt.y();
|
|
||||||
const double x2 = previousPt.x() + cosa;
|
|
||||||
const double y2 = previousPt.y() + sina;
|
|
||||||
// line of snapped segment
|
|
||||||
const double x3 = mSnappedSegment.at( 0 ).x();
|
|
||||||
const double y3 = mSnappedSegment.at( 0 ).y();
|
|
||||||
const double x4 = mSnappedSegment.at( 1 ).x();
|
|
||||||
const double y4 = mSnappedSegment.at( 1 ).y();
|
|
||||||
|
|
||||||
const double d = ( x1 - x2 ) * ( y3 - y4 ) - ( y1 - y2 ) * ( x3 - x4 );
|
|
||||||
|
|
||||||
// do not compute intersection if lines are almost parallel
|
|
||||||
// this threshold might be adapted
|
|
||||||
if ( std::fabs( d ) > 0.01 )
|
|
||||||
{
|
|
||||||
point.setX( ( ( x3 - x4 ) * ( x1 * y2 - y1 * x2 ) - ( x1 - x2 ) * ( x3 * y4 - y3 * x4 ) ) / d );
|
|
||||||
point.setY( ( ( y3 - y4 ) * ( x1 * y2 - y1 * x2 ) - ( y1 - y2 ) * ( x3 * y4 - y3 * x4 ) ) / d );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// *****************************
|
|
||||||
// ---- Distance constraint
|
|
||||||
if ( mDistanceConstraint->isLocked() && previousPointExist )
|
|
||||||
{
|
|
||||||
if ( mXConstraint->isLocked() || mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// perform both to detect errors in constraints
|
|
||||||
if ( mXConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
const QList<QgsPointXY> verticalSegment = QList<QgsPointXY>()
|
|
||||||
<< QgsPointXY( mXConstraint->value(), point.y() )
|
|
||||||
<< QgsPointXY( mXConstraint->value(), point.y() + 1 );
|
|
||||||
res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), verticalSegment, point );
|
|
||||||
}
|
|
||||||
if ( mYConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
const QList<QgsPointXY> horizontalSegment = QList<QgsPointXY>()
|
|
||||||
<< QgsPointXY( point.x(), mYConstraint->value() )
|
|
||||||
<< QgsPointXY( point.x() + 1, mYConstraint->value() );
|
|
||||||
res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), horizontalSegment, point );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const double dist = std::sqrt( point.sqrDist( previousPt ) );
|
|
||||||
if ( dist == 0 )
|
|
||||||
{
|
|
||||||
// handle case where mouse is over origin and distance constraint is enabled
|
|
||||||
// take arbitrary horizontal line
|
|
||||||
point.set( previousPt.x() + mDistanceConstraint->value(), previousPt.y() );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const double vP = mDistanceConstraint->value() / dist;
|
|
||||||
point.set( previousPt.x() + ( point.x() - previousPt.x() ) * vP,
|
|
||||||
previousPt.y() + ( point.y() - previousPt.y() ) * vP );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !mSnappedSegment.isEmpty() && !mAngleConstraint->isLocked() )
|
|
||||||
{
|
|
||||||
// we will magnietize to the intersection of that segment and the lockedDistance !
|
|
||||||
res &= lineCircleIntersection( previousPt, mDistanceConstraint->value(), snappedSegment(), point );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// *****************************
|
|
||||||
// ---- calculate CAD values
|
|
||||||
QgsDebugMsgLevel( QString( "point: %1 %2" ).arg( point.x() ).arg( point.y() ), 4 );
|
|
||||||
QgsDebugMsgLevel( QString( "previous point: %1 %2" ).arg( previousPt.x() ).arg( previousPt.y() ), 4 );
|
|
||||||
QgsDebugMsgLevel( QString( "penultimate point: %1 %2" ).arg( penultimatePt.x() ).arg( penultimatePt.y() ), 4 );
|
|
||||||
//QgsDebugMsg( QString( "dx: %1 dy: %2" ).arg( point.x() - previousPt.x() ).arg( point.y() - previousPt.y() ) );
|
|
||||||
//QgsDebugMsg( QString( "ddx: %1 ddy: %2" ).arg( previousPt.x() - penultimatePt.x() ).arg( previousPt.y() - penultimatePt.y() ) );
|
|
||||||
|
|
||||||
// set the point coordinates in the map event
|
// set the point coordinates in the map event
|
||||||
e->setMapPoint( point );
|
e->setMapPoint( point );
|
||||||
@ -815,8 +560,27 @@ bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
|||||||
// update the point list
|
// update the point list
|
||||||
updateCurrentPoint( point );
|
updateCurrentPoint( point );
|
||||||
|
|
||||||
// *****************************
|
updateUnlockedConstraintValues( point );
|
||||||
// ---- update the GUI with the values
|
|
||||||
|
if ( res )
|
||||||
|
{
|
||||||
|
emit popWarning();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point )
|
||||||
|
{
|
||||||
|
bool previousPointExist, penulPointExist;
|
||||||
|
QgsPointXY previousPt = previousPoint( &previousPointExist );
|
||||||
|
QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
|
||||||
|
|
||||||
// --- angle
|
// --- angle
|
||||||
if ( !mAngleConstraint->isLocked() && previousPointExist )
|
if ( !mAngleConstraint->isLocked() && previousPointExist )
|
||||||
{
|
{
|
||||||
@ -863,36 +627,15 @@ bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
|||||||
mYConstraint->setValue( point.y() );
|
mYConstraint->setValue( point.y() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( res )
|
|
||||||
{
|
|
||||||
emit popWarning();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
emit pushWarning( tr( "Some constraints are incompatible. Resulting point might be incorrect." ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const
|
||||||
QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegment( const QgsPointXY &originalMapPoint, bool *snapped, bool allLayers ) const
|
|
||||||
{
|
{
|
||||||
QList<QgsPointXY> segment;
|
QList<QgsPointXY> segment;
|
||||||
QgsPointXY pt1, pt2;
|
QgsPointXY pt1, pt2;
|
||||||
QgsPointLocator::Match match;
|
QgsPointLocator::Match match;
|
||||||
|
|
||||||
if ( !allLayers )
|
|
||||||
{
|
|
||||||
// run snapToMap with only segments
|
|
||||||
EdgesOnlyFilter filter;
|
|
||||||
match = mMapCanvas->snappingUtils()->snapToMap( originalMapPoint, &filter );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// run snapToMap with only edges on all layers
|
|
||||||
QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
|
QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils();
|
||||||
|
|
||||||
QgsSnappingConfig canvasConfig = snappingUtils->config();
|
QgsSnappingConfig canvasConfig = snappingUtils->config();
|
||||||
@ -905,7 +648,7 @@ QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegment( const QgsPointXY
|
|||||||
match = snappingUtils->snapToMap( originalMapPoint );
|
match = snappingUtils->snapToMap( originalMapPoint );
|
||||||
|
|
||||||
snappingUtils->setConfig( canvasConfig );
|
snappingUtils->setConfig( canvasConfig );
|
||||||
}
|
|
||||||
if ( match.isValid() && match.hasEdge() )
|
if ( match.isValid() && match.hasEdge() )
|
||||||
{
|
{
|
||||||
match.edgePoints( pt1, pt2 );
|
match.edgePoints( pt1, pt2 );
|
||||||
@ -930,7 +673,7 @@ bool QgsAdvancedDigitizingDockWidget::alignToSegment( QgsMapMouseEvent *e, CadCo
|
|||||||
bool previousPointExist, penulPointExist, snappedSegmentExist;
|
bool previousPointExist, penulPointExist, snappedSegmentExist;
|
||||||
QgsPointXY previousPt = previousPoint( &previousPointExist );
|
QgsPointXY previousPt = previousPoint( &previousPointExist );
|
||||||
QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
|
QgsPointXY penultimatePt = penultimatePoint( &penulPointExist );
|
||||||
mSnappedSegment = snapSegment( e->originalMapPoint(), &snappedSegmentExist, true );
|
mSnappedSegment = snapSegmentToAllLayers( e->originalMapPoint(), &snappedSegmentExist );
|
||||||
|
|
||||||
if ( !previousPointExist || !snappedSegmentExist )
|
if ( !previousPointExist || !snappedSegmentExist )
|
||||||
{
|
{
|
||||||
|
@ -32,11 +32,6 @@ class QgsMapTool;
|
|||||||
class QgsMapToolAdvancedDigitizing;
|
class QgsMapToolAdvancedDigitizing;
|
||||||
class QgsPointXY;
|
class QgsPointXY;
|
||||||
|
|
||||||
// tolerances for soft constraints (last values, and common angles)
|
|
||||||
// for angles, both tolerance in pixels and degrees are used for better performance
|
|
||||||
static const double SOFT_CONSTRAINT_TOLERANCE_PIXEL = 15 SIP_SKIP;
|
|
||||||
static const double SOFT_CONSTRAINT_TOLERANCE_DEGREES = 10 SIP_SKIP;
|
|
||||||
|
|
||||||
/** \ingroup gui
|
/** \ingroup gui
|
||||||
* \brief The QgsAdvancedDigitizingDockWidget class is a dockable widget
|
* \brief The QgsAdvancedDigitizingDockWidget class is a dockable widget
|
||||||
* used to handle the CAD tools on top of a selection of map tools.
|
* used to handle the CAD tools on top of a selection of map tools.
|
||||||
@ -188,10 +183,6 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private
|
|||||||
double mValue;
|
double mValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! performs the intersection of a circle and a line
|
|
||||||
//! \note from the two solutions, the intersection will be set to the closest point
|
|
||||||
static bool lineCircleIntersection( const QgsPointXY ¢er, const double radius, const QList<QgsPointXY> &segment, QgsPointXY &intersection );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an advanced digitizing dock widget
|
* Create an advanced digitizing dock widget
|
||||||
* \param canvas The map canvas on which the widget operates
|
* \param canvas The map canvas on which the widget operates
|
||||||
@ -393,15 +384,12 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private
|
|||||||
//! defines the additional constraint to be used (no/parallel/perpendicular)
|
//! defines the additional constraint to be used (no/parallel/perpendicular)
|
||||||
void lockAdditionalConstraint( AdditionalConstraint constraint );
|
void lockAdditionalConstraint( AdditionalConstraint constraint );
|
||||||
|
|
||||||
QList<QgsPointXY> snapSegment( const QgsPointLocator::Match &snapMatch );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first snapped segment. Will try to snap a segment according to the event's snapping mode.
|
* Returns the first snapped segment. Will try to snap a segment using all layers
|
||||||
* \param originalMapPoint point to be snapped (in map coordinates)
|
* \param originalMapPoint point to be snapped (in map coordinates)
|
||||||
* \param snapped if given, determines if a segment has been snapped
|
* \param snapped if given, determines if a segment has been snapped
|
||||||
* \param allLayers if true, override snapping mode
|
|
||||||
*/
|
*/
|
||||||
QList<QgsPointXY> snapSegment( const QgsPointXY &originalMapPoint, bool *snapped = nullptr, bool allLayers = false ) const;
|
QList<QgsPointXY> snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped = nullptr ) const;
|
||||||
|
|
||||||
//! update the current point in the CAD point list
|
//! update the current point in the CAD point list
|
||||||
void updateCurrentPoint( const QgsPointXY &point );
|
void updateCurrentPoint( const QgsPointXY &point );
|
||||||
@ -432,6 +420,9 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private
|
|||||||
*/
|
*/
|
||||||
void updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression = false );
|
void updateConstraintValue( CadConstraint *constraint, const QString &textValue, bool convertExpression = false );
|
||||||
|
|
||||||
|
//! Updates values of constraints that are not locked based on the current point
|
||||||
|
void updateUnlockedConstraintValues( const QgsPointXY &point );
|
||||||
|
|
||||||
QgsMapCanvas *mMapCanvas = nullptr;
|
QgsMapCanvas *mMapCanvas = nullptr;
|
||||||
QgsAdvancedDigitizingCanvasItem *mCadPaintItem = nullptr;
|
QgsAdvancedDigitizingCanvasItem *mCadPaintItem = nullptr;
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ SET(TESTS
|
|||||||
testqgsauthconfig.cpp
|
testqgsauthconfig.cpp
|
||||||
testqgsauthmanager.cpp
|
testqgsauthmanager.cpp
|
||||||
testqgsblendmodes.cpp
|
testqgsblendmodes.cpp
|
||||||
|
testqgscadutils.cpp
|
||||||
testqgsclipper.cpp
|
testqgsclipper.cpp
|
||||||
testqgscolorscheme.cpp
|
testqgscolorscheme.cpp
|
||||||
testqgscolorschemeregistry.cpp
|
testqgscolorschemeregistry.cpp
|
||||||
|
259
tests/src/core/testqgscadutils.cpp
Normal file
259
tests/src/core/testqgscadutils.cpp
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
testqgscadutils.cpp
|
||||||
|
--------------------------------------
|
||||||
|
Date : September 2017
|
||||||
|
Copyright : (C) 2017 by Martin Dobias
|
||||||
|
Email : wonder.sk at gmail.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 "qgscadutils.h"
|
||||||
|
#include "qgsproject.h"
|
||||||
|
#include "qgssnappingutils.h"
|
||||||
|
#include "qgsvectorlayer.h"
|
||||||
|
|
||||||
|
/** \ingroup UnitTests
|
||||||
|
* This is a unit test for the QgsCadUtils class.
|
||||||
|
*/
|
||||||
|
class TestQgsCadUtils : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
TestQgsCadUtils()
|
||||||
|
{}
|
||||||
|
~TestQgsCadUtils()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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 init() {} // will be called before each testfunction is executed.
|
||||||
|
void cleanup() {} // will be called after every testfunction.
|
||||||
|
|
||||||
|
void testBasic();
|
||||||
|
void testXY();
|
||||||
|
void testAngle();
|
||||||
|
void testCommonAngle();
|
||||||
|
void testDistance();
|
||||||
|
void testEdge();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QgsCadUtils::AlignMapPointContext baseContext()
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointContext context;
|
||||||
|
context.snappingUtils = mSnappingUtils;
|
||||||
|
context.mapUnitsPerPixel = mMapSettings.mapUnitsPerPixel();
|
||||||
|
context.cadPointList << QgsPointXY() << QgsPointXY( 30, 20 ) << QgsPointXY( 30, 30 );
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString mTestDataDir;
|
||||||
|
QgsVectorLayer *mLayerPolygon = nullptr;
|
||||||
|
QgsSnappingUtils *mSnappingUtils = nullptr;
|
||||||
|
QgsMapSettings mMapSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//runs before all tests
|
||||||
|
void TestQgsCadUtils::initTestCase()
|
||||||
|
{
|
||||||
|
// init QGIS's paths - true means that all path will be inited from prefix
|
||||||
|
QgsApplication::init();
|
||||||
|
QgsApplication::initQgis();
|
||||||
|
|
||||||
|
mLayerPolygon = new QgsVectorLayer( "Polygon?crs=EPSG:27700", "layer polygon", "memory" );
|
||||||
|
QVERIFY( mLayerPolygon->isValid() );
|
||||||
|
|
||||||
|
QgsPolygon polygon1;
|
||||||
|
QgsPolyline polygon1exterior;
|
||||||
|
polygon1exterior << QgsPointXY( 10, 10 ) << QgsPointXY( 30, 10 ) << QgsPointXY( 10, 20 ) << QgsPointXY( 10, 10 );
|
||||||
|
polygon1 << polygon1exterior;
|
||||||
|
QgsFeature polygonF1;
|
||||||
|
polygonF1.setGeometry( QgsGeometry::fromPolygon( polygon1 ) );
|
||||||
|
|
||||||
|
mLayerPolygon->startEditing();
|
||||||
|
mLayerPolygon->addFeature( polygonF1 );
|
||||||
|
|
||||||
|
QgsProject::instance()->addMapLayer( mLayerPolygon );
|
||||||
|
|
||||||
|
QgsSnappingConfig snapConfig;
|
||||||
|
snapConfig.setEnabled( true );
|
||||||
|
snapConfig.setMode( QgsSnappingConfig::AllLayers );
|
||||||
|
snapConfig.setType( QgsSnappingConfig::VertexAndSegment );
|
||||||
|
snapConfig.setTolerance( 1.0 );
|
||||||
|
|
||||||
|
mMapSettings.setExtent( QgsRectangle( 0, 0, 100, 100 ) );
|
||||||
|
mMapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||||
|
mMapSettings.setLayers( QList<QgsMapLayer *>() << mLayerPolygon );
|
||||||
|
|
||||||
|
mSnappingUtils = new QgsSnappingUtils;
|
||||||
|
mSnappingUtils->setConfig( snapConfig );
|
||||||
|
mSnappingUtils->setMapSettings( mMapSettings );
|
||||||
|
}
|
||||||
|
|
||||||
|
//runs after all tests
|
||||||
|
void TestQgsCadUtils::cleanupTestCase()
|
||||||
|
{
|
||||||
|
delete mSnappingUtils;
|
||||||
|
|
||||||
|
QgsApplication::exitQgis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testBasic()
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointContext context( baseContext() );
|
||||||
|
|
||||||
|
// no snap
|
||||||
|
QgsCadUtils::AlignMapPointOutput res0 = QgsCadUtils::alignMapPoint( QgsPointXY( 5, 5 ), context );
|
||||||
|
QVERIFY( res0.valid );
|
||||||
|
QCOMPARE( res0.finalMapPoint, QgsPointXY( 5, 5 ) );
|
||||||
|
|
||||||
|
// simple snap to vertex
|
||||||
|
QgsCadUtils::AlignMapPointOutput res1 = QgsCadUtils::alignMapPoint( QgsPointXY( 9.5, 9.5 ), context );
|
||||||
|
QVERIFY( res1.valid );
|
||||||
|
QCOMPARE( res1.finalMapPoint, QgsPointXY( 10, 10 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testXY()
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointContext context( baseContext() );
|
||||||
|
|
||||||
|
// x absolute
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 20 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res0 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res0.valid );
|
||||||
|
QCOMPARE( res0.finalMapPoint, QgsPointXY( 20, 29 ) );
|
||||||
|
|
||||||
|
// x relative
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, -5 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res1 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res1.valid );
|
||||||
|
QCOMPARE( res1.finalMapPoint, QgsPointXY( 25, 29 ) );
|
||||||
|
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint();
|
||||||
|
|
||||||
|
// y absolute
|
||||||
|
context.yConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 20 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res2 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res2.valid );
|
||||||
|
QCOMPARE( res2.finalMapPoint, QgsPointXY( 29, 20 ) );
|
||||||
|
|
||||||
|
// y relative
|
||||||
|
context.yConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, -5 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res3 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res3.valid );
|
||||||
|
QCOMPARE( res3.finalMapPoint, QgsPointXY( 29, 15 ) );
|
||||||
|
|
||||||
|
// x and y (relative)
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 32 );
|
||||||
|
context.yConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 22 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res4 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res4.valid );
|
||||||
|
QCOMPARE( res4.finalMapPoint, QgsPointXY( 32, 22 ) );
|
||||||
|
|
||||||
|
// x and y (relative)
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, -2 );
|
||||||
|
context.yConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, -2 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res5 = QgsCadUtils::alignMapPoint( QgsPointXY( 29, 29 ), context );
|
||||||
|
QVERIFY( res5.valid );
|
||||||
|
QCOMPARE( res5.finalMapPoint, QgsPointXY( 28, 18 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testAngle()
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointContext context( baseContext() );
|
||||||
|
|
||||||
|
// angle abs
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 45 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res0 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20 ), context );
|
||||||
|
QVERIFY( res0.valid );
|
||||||
|
QCOMPARE( res0.finalMapPoint, QgsPointXY( 35, 25 ) );
|
||||||
|
|
||||||
|
// angle rel
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, 45 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res1 = QgsCadUtils::alignMapPoint( QgsPointXY( 30, 30 ), context );
|
||||||
|
QVERIFY( res1.valid );
|
||||||
|
QCOMPARE( res1.finalMapPoint, QgsPointXY( 25, 25 ) );
|
||||||
|
|
||||||
|
// angle + x abs
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 45 );
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 38 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res2 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20 ), context );
|
||||||
|
QVERIFY( res2.valid );
|
||||||
|
QCOMPARE( res2.finalMapPoint, QgsPointXY( 38, 28 ) );
|
||||||
|
|
||||||
|
// angle + y rel
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 45 );
|
||||||
|
context.xConstraint = QgsCadUtils::AlignMapPointConstraint();
|
||||||
|
context.yConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 17 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res3 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20 ), context );
|
||||||
|
QVERIFY( res3.valid );
|
||||||
|
QCOMPARE( res3.finalMapPoint, QgsPointXY( 27, 17 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testCommonAngle()
|
||||||
|
{
|
||||||
|
QgsCadUtils::AlignMapPointContext context( baseContext() );
|
||||||
|
|
||||||
|
// without common angle
|
||||||
|
QgsCadUtils::AlignMapPointOutput res0 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20.1 ), context );
|
||||||
|
QVERIFY( res0.valid );
|
||||||
|
QCOMPARE( res0.softLockCommonAngle, -1 );
|
||||||
|
QCOMPARE( res0.finalMapPoint, QgsPointXY( 40, 20.1 ) );
|
||||||
|
|
||||||
|
// common angle
|
||||||
|
context.commonAngleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 90 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res1 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20.1 ), context );
|
||||||
|
QVERIFY( res1.valid );
|
||||||
|
QCOMPARE( res1.softLockCommonAngle, 0 );
|
||||||
|
QCOMPARE( res1.finalMapPoint, QgsPointXY( 40, 20 ) );
|
||||||
|
|
||||||
|
// common angle + angle (make sure that angle constraint has priority)
|
||||||
|
context.commonAngleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 90 );
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint( true, false, 45 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res2 = QgsCadUtils::alignMapPoint( QgsPointXY( 40, 20.1 ), context );
|
||||||
|
QVERIFY( res2.valid );
|
||||||
|
QCOMPARE( res2.softLockCommonAngle, -1 );
|
||||||
|
QCOMPARE( res2.finalMapPoint, QgsPointXY( 35.05, 25.05 ) );
|
||||||
|
|
||||||
|
// common angle rel
|
||||||
|
context.angleConstraint = QgsCadUtils::AlignMapPointConstraint();
|
||||||
|
context.commonAngleConstraint = QgsCadUtils::AlignMapPointConstraint( true, true, 90 );
|
||||||
|
context.cadPointList[1] = QgsPointXY( 40, 20 );
|
||||||
|
QgsCadUtils::AlignMapPointOutput res3 = QgsCadUtils::alignMapPoint( QgsPointXY( 50.1, 29.9 ), context );
|
||||||
|
QVERIFY( res3.valid );
|
||||||
|
QCOMPARE( res3.softLockCommonAngle, 90 );
|
||||||
|
QCOMPARE( res3.finalMapPoint, QgsPointXY( 50, 30 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testDistance()
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
// dist
|
||||||
|
// dist+x / dist+y
|
||||||
|
// dist+angle
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestQgsCadUtils::testEdge()
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
// x+edge / y+edge
|
||||||
|
// angle+edge
|
||||||
|
// distance+edge
|
||||||
|
}
|
||||||
|
|
||||||
|
QGSTEST_MAIN( TestQgsCadUtils )
|
||||||
|
|
||||||
|
#include "testqgscadutils.moc"
|
Loading…
x
Reference in New Issue
Block a user