Skip to content
Permalink
Browse files
Move private arc segmentization routines to QgsGeometryUtils
  • Loading branch information
wonder-sk committed Apr 2, 2017
1 parent d0a36d3 commit ab5841395f89be985a79d30fc9bc2347fd850d56
@@ -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 );
@@ -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() );
@@ -145,10 +145,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 );
@@ -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
*/

0 comments on commit ab58413

Please sign in to comment.