Skip to content

Commit

Permalink
Consider curved geometries in marker line symbol layer
Browse files Browse the repository at this point in the history
  • Loading branch information
mhugent committed Sep 4, 2015
1 parent a5a963c commit 56316dd
Show file tree
Hide file tree
Showing 24 changed files with 386 additions and 3 deletions.
4 changes: 4 additions & 0 deletions python/core/geometry/qgsabstractgeometryv2.sip
Expand Up @@ -116,4 +116,8 @@ class QgsAbstractGeometryV2
virtual bool hasCurvedSegments() const;
/** Returns a geometry without curves. Caller takes ownership*/
virtual QgsAbstractGeometryV2* segmentize() const /Factory/;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
virtual double vertexAngle( const QgsVertexId& vertex ) const = 0;
};
4 changes: 4 additions & 0 deletions python/core/geometry/qgscircularstringv2.sip
Expand Up @@ -55,6 +55,10 @@ class QgsCircularStringV2: public QgsCurveV2

bool hasCurvedSegments() const;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const;

private:
void segmentize( const QgsPointV2& p1, const QgsPointV2& p2, const QgsPointV2& p3, QList<QgsPointV2>& points ) const;
};
4 changes: 4 additions & 0 deletions python/core/geometry/qgscompoundcurvev2.sip
Expand Up @@ -60,4 +60,8 @@ class QgsCompoundCurveV2: public QgsCurveV2
void sumUpArea( double& sum ) const;

bool hasCurvedSegments() const;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const;
};
4 changes: 4 additions & 0 deletions python/core/geometry/qgscurvepolygonv2.sip
Expand Up @@ -62,4 +62,8 @@ class QgsCurvePolygonV2: public QgsSurfaceV2

bool hasCurvedSegments() const;
QgsAbstractGeometryV2* segmentize() const /Factory/;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const;
};
4 changes: 4 additions & 0 deletions python/core/geometry/qgsgeometrycollectionv2.sip
Expand Up @@ -51,4 +51,8 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
virtual double area() const;

bool hasCurvedSegments() const;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const;
};
4 changes: 4 additions & 0 deletions python/core/geometry/qgslinestringv2.sip
Expand Up @@ -55,4 +55,8 @@ class QgsLineStringV2: public QgsCurveV2
bool pointAt( int i, QgsPointV2& vertex, QgsVertexId::VertexType& type ) const;

void sumUpArea( double& sum ) const;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const;
};
3 changes: 3 additions & 0 deletions python/core/geometry/qgspointv2.sip
Expand Up @@ -55,4 +55,7 @@ class QgsPointV2: public QgsAbstractGeometryV2

double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;

/** Angle undefined. Always returns 0.0*/
double vertexAngle( const QgsVertexId& vertex ) const;
};
4 changes: 4 additions & 0 deletions src/core/geometry/qgsabstractgeometryv2.h
Expand Up @@ -317,6 +317,10 @@ class CORE_EXPORT QgsAbstractGeometryV2
*/
virtual QgsAbstractGeometryV2* segmentize() const { return clone(); }

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
virtual double vertexAngle( const QgsVertexId& vertex ) const = 0;

protected:
QgsWKBTypes::Type mWkbType;
mutable QgsRectangle mBoundingBox;
Expand Down
55 changes: 55 additions & 0 deletions src/core/geometry/qgscircularstringv2.cpp
Expand Up @@ -953,3 +953,58 @@ void QgsCircularStringV2::insertVertexBetween( int after, int before, int pointO
mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
}
}

double QgsCircularStringV2::vertexAngle( const QgsVertexId& vId ) const
{
int before = vId.vertex - 1;
int vertex = vId.vertex;
int after = vId.vertex + 1;

if ( vId.vertex % 2 != 0 ) // a curve vertex
{
if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
{
return QgsGeometryUtils::circleTangentDirection( QgsPointV2( mX[vertex], mY[vertex] ), QgsPointV2( mX[before], mY[before] ),
QgsPointV2( mX[vertex], mY[vertex] ), QgsPointV2( mX[after], mY[after] ) );
}
}
else //a point vertex
{
if ( vId.vertex == 0 )
{
return QgsGeometryUtils::circleTangentDirection( QgsPointV2( mX[0], mY[0] ), QgsPointV2( mX[0], mY[0] ),
QgsPointV2( mX[1], mY[1] ), QgsPointV2( mX[2], mY[2] ) );
}
if ( vId.vertex >= numPoints() - 1 )
{
if ( numPoints() < 3 )
{
return 0.0;
}
int a = numPoints() - 3;
int b = numPoints() - 2;
int c = numPoints() - 1;
return QgsGeometryUtils::circleTangentDirection( QgsPointV2( mX[c], mY[c] ), QgsPointV2( mX[a], mY[a] ),
QgsPointV2( mX[b], mY[b] ), QgsPointV2( mX[c], mY[c] ) );
}
else
{
if ( vId.vertex + 2 > numPoints() - 1 )
{
return 0.0;
}

int vertex1 = vId.vertex - 2;
int vertex2 = vId.vertex - 1;
int vertex3 = vId.vertex;
double angle1 = QgsGeometryUtils::circleTangentDirection( QgsPointV2( mX[vertex3], mY[vertex3] ),
QgsPointV2( mX[vertex1], mY[vertex1] ), QgsPointV2( mX[vertex2], mY[vertex2] ), QgsPointV2( mX[vertex3], mY[vertex3] ) );
int vertex4 = vId.vertex + 1;
int vertex5 = vId.vertex + 2;
double angle2 = QgsGeometryUtils::circleTangentDirection( QgsPointV2( mX[vertex3], mY[vertex3] ),
QgsPointV2( mX[vertex3], mY[vertex3] ), QgsPointV2( mX[vertex4], mY[vertex4] ), QgsPointV2( mX[vertex5], mY[vertex5] ) );
return QgsGeometryUtils::averageAngle( angle1, angle2 );
}
}
return 0.0;
}
4 changes: 4 additions & 0 deletions src/core/geometry/qgscircularstringv2.h
Expand Up @@ -91,6 +91,10 @@ class CORE_EXPORT QgsCircularStringV2: public QgsCurveV2

bool hasCurvedSegments() const override { return true; }

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const override;

private:
QVector<double> mX;
QVector<double> mY;
Expand Down
22 changes: 22 additions & 0 deletions src/core/geometry/qgscompoundcurvev2.cpp
Expand Up @@ -586,3 +586,25 @@ bool QgsCompoundCurveV2::hasCurvedSegments() const
return false;
}

double QgsCompoundCurveV2::vertexAngle( const QgsVertexId& vertex ) const
{
QList< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
if ( curveIds.size() == 1 )
{
QgsCurveV2* curve = mCurves[curveIds.at( 0 ).first];
return curve->vertexAngle( curveIds.at( 0 ).second );
}
else if ( curveIds.size() > 1 )
{
QgsCurveV2* curve1 = mCurves[curveIds.at( 0 ).first];
QgsCurveV2* curve2 = mCurves[curveIds.at( 1 ).first];
double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
return QgsGeometryUtils::averageAngle( angle1, angle2 );
}
else
{
return 0.0;
}
}

4 changes: 4 additions & 0 deletions src/core/geometry/qgscompoundcurvev2.h
Expand Up @@ -104,6 +104,10 @@ class CORE_EXPORT QgsCompoundCurveV2: public QgsCurveV2

bool hasCurvedSegments() const override;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const override;

private:
QList< QgsCurveV2* > mCurves;
/** Turns a vertex id for the compound curve into one or more ids for the subcurves
Expand Down
11 changes: 11 additions & 0 deletions src/core/geometry/qgscurvepolygonv2.cpp
Expand Up @@ -665,3 +665,14 @@ QgsAbstractGeometryV2* QgsCurvePolygonV2::segmentize() const
{
return toPolygon();
}

double QgsCurvePolygonV2::vertexAngle( const QgsVertexId& vertex ) const
{
if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
{
return false;
}

QgsCurveV2* ring = vertex.ring == 0 ? mExteriorRing : mInteriorRings[vertex.ring - 1];
return ring->vertexAngle( vertex );
}
4 changes: 4 additions & 0 deletions src/core/geometry/qgscurvepolygonv2.h
Expand Up @@ -94,6 +94,10 @@ class CORE_EXPORT QgsCurvePolygonV2: public QgsSurfaceV2
bool hasCurvedSegments() const override;
QgsAbstractGeometryV2* segmentize() const override;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const override;

protected:

QgsCurveV2* mExteriorRing;
Expand Down
16 changes: 16 additions & 0 deletions src/core/geometry/qgsgeometrycollectionv2.cpp
Expand Up @@ -509,3 +509,19 @@ QgsAbstractGeometryV2* QgsGeometryCollectionV2::segmentize() const
}
return geomCollection;
}

double QgsGeometryCollectionV2::vertexAngle( const QgsVertexId& vertex ) const
{
if ( vertex.part >= mGeometries.size() )
{
return 0.0;
}

QgsAbstractGeometryV2* geom = mGeometries[vertex.part];
if ( !geom )
{
return 0.0;
}

return geom->vertexAngle( vertex );
}
4 changes: 4 additions & 0 deletions src/core/geometry/qgsgeometrycollectionv2.h
Expand Up @@ -102,6 +102,10 @@ class CORE_EXPORT QgsGeometryCollectionV2: public QgsAbstractGeometryV2
/** Returns a geometry without curves. Caller takes ownership*/
QgsAbstractGeometryV2* segmentize() const override;

/** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
@return rotation in radians, clockwise from north*/
double vertexAngle( const QgsVertexId& vertex ) const override;

protected:
QVector< QgsAbstractGeometryV2* > mGeometries;

Expand Down
88 changes: 88 additions & 0 deletions src/core/geometry/qgsgeometryutils.cpp
Expand Up @@ -357,6 +357,26 @@ bool QgsGeometryUtils::segmentMidPoint( const QgsPointV2& p1, const QgsPointV2&
return true;
}

double QgsGeometryUtils::circleTangentDirection( const QgsPointV2& tangentPoint, const QgsPointV2& cp1,
const QgsPointV2& cp2, const QgsPointV2& cp3 )
{
//calculate circle midpoint
double mX, mY, radius;
circleCenterRadius( cp1, cp2, cp3, radius, mX, mY );

double p1Angle = QgsGeometryUtils::ccwAngle( cp1.y() - mY, cp1.x() - mX );
double p2Angle = QgsGeometryUtils::ccwAngle( cp2.y() - mY, cp2.x() - mX );
double p3Angle = QgsGeometryUtils::ccwAngle( cp3.y() - mY, cp3.x() - mX );
if ( circleClockwise( p1Angle, p2Angle, p3Angle ) )
{
return lineAngle( tangentPoint.x(), tangentPoint.y(), mX, mY );
}
else
{
return lineAngle( mX, mY, tangentPoint.x(), tangentPoint.y() );
}
}

QList<QgsPointV2> QgsGeometryUtils::pointsFromWKT( const QString &wktCoordinateList, bool is3D, bool isMeasure )
{
int dim = 2 + is3D + isMeasure;
Expand Down Expand Up @@ -525,3 +545,71 @@ QStringList QgsGeometryUtils::wktGetChildBlocks( const QString &wkt, const QStri
}
return blocks;
}

double QgsGeometryUtils::lineAngle( double x1, double y1, double x2, double y2 )
{
double at = atan2( y2 - y1, x2 - x1 );
double a = -at + M_PI / 2.0;
if ( a < 0 )
{
a = 2 * M_PI + a;
}
if ( a >= 2 * M_PI )
{
a -= 2 * M_PI;
}
return a;
}

double QgsGeometryUtils::linePerpendicularAngle( double x1, double y1, double x2, double y2 )
{
double a = lineAngle( x1, y1, x2, y2 );
a += ( M_PI / 2.0 );
if ( a >= 2 * M_PI )
{
a -= ( 2 * M_PI );
}
return a;
}

double QgsGeometryUtils::averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 )
{
// calc average angle between the previous and next point
double a1 = linePerpendicularAngle( x1, y1, x2, y2 );
double a2 = linePerpendicularAngle( x2, y2, x3, y3 );
return averageAngle( a1, a2 );
}

double QgsGeometryUtils::averageAngle( double a1, double a2 )
{
double clockwiseDiff = 0.0;
if ( a2 >= a1 )
{
clockwiseDiff = a2 - a1;
}
else
{
clockwiseDiff = a2 + ( 2 * M_PI - a1 );
}
double counterClockwiseDiff = 2 * M_PI - clockwiseDiff;

double resultAngle = 0;
if ( clockwiseDiff <= counterClockwiseDiff )
{
resultAngle = a1 + clockwiseDiff / 2.0;
}
else
{
resultAngle = a1 - counterClockwiseDiff / 2.0;
}

if ( resultAngle >= 2 * M_PI )
{
resultAngle -= 2 * M_PI;
}
else if ( resultAngle < 0 )
{
resultAngle = 2 * M_PI - resultAngle;
}
return resultAngle;
}
9 changes: 9 additions & 0 deletions src/core/geometry/qgsgeometryutils.h
Expand Up @@ -79,6 +79,9 @@ class CORE_EXPORT QgsGeometryUtils
/** Calculates midpoint on circle passing through p1 and p2, closest to given coordinate*/
static bool segmentMidPoint( const QgsPointV2& p1, const QgsPointV2& p2, QgsPointV2& result, double radius, const QgsPointV2& mousePos );

/** 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 );

/** Returns a list of points contained in a WKT string.
*/
static QList<QgsPointV2> pointsFromWKT( const QString& wktCoordinateList, bool is3D, bool isMeasure );
Expand All @@ -93,6 +96,12 @@ class CORE_EXPORT QgsGeometryUtils
/** Returns a geoJSON coordinates string */
static QString pointsToJSON( const QList<QgsPointV2>& points, int precision );

static double lineAngle( double x1, double y1, double x2, double y2 );
static double linePerpendicularAngle( double x1, double y1, double x2, double y2 );
static double averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 );
static double averageAngle( double a1, double a2 );


/** Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents ("Pair(wkbType, "contents")")
*/
static QPair<QgsWKBTypes::Type, QString> wktReadBlock( const QString& wkt );
Expand Down
31 changes: 31 additions & 0 deletions src/core/geometry/qgslinestringv2.cpp
Expand Up @@ -500,3 +500,34 @@ void QgsLineStringV2::close()
}
addVertex( startPoint() );
}

double QgsLineStringV2::vertexAngle( const QgsVertexId& vertex ) const
{
if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
{
if ( isClosed() )
{
QPointF previous = mCoords[numPoints() - 1 ];
QPointF current = mCoords[0];
QPointF after = mCoords[1];
return QgsGeometryUtils::averageAngle( previous.x(), previous.y(), current.x(), current.y(), after.x(), after.y() );
}
else if ( vertex.vertex == 0 )
{
return QgsGeometryUtils::linePerpendicularAngle( mCoords[0].x(), mCoords[0].y(), mCoords[1].x(), mCoords[1].y() );
}
else
{
int a = numPoints() - 2;
int b = numPoints() - 1;
return QgsGeometryUtils::linePerpendicularAngle( mCoords[a].x(), mCoords[a].y(), mCoords[b].x(), mCoords[b].y() );
}
}
else
{
QPointF previous = mCoords[vertex.vertex - 1 ];
QPointF current = mCoords[vertex.vertex];
QPointF after = mCoords[vertex.vertex + 1];
return QgsGeometryUtils::averageAngle( previous.x(), previous.y(), current.x(), current.y(), after.x(), after.y() );
}
}

0 comments on commit 56316dd

Please sign in to comment.