Skip to content
Permalink
Browse files

[FEATURE][processing] Add "convert to curves" algorithm

Converts a linear geometry type to the corresponding curved
geometry type, attemping to identify segments in the original
geometries which can be replaced by arcs.

The distance tolerance parameter specifies the maximum distance
allowed between the original location of vertices and where they
would fall on the converted curved geometries.

This algorithm only consider a segments as suitable for
replacing with an arc if the points are all regularly spaced on
the candidate arc. The angle tolerance parameter specifies the
maximum angular deviation (in degrees) allowed when testing for
regular point spacing.

Already curved geometries will be retained without change.
  • Loading branch information
nyalldawson authored and olivierdalang committed Mar 5, 2018
1 parent 27b5dae commit 9939142ba9000f0526ae7196eec9e4f8e52ee77a
@@ -39,6 +39,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmclip.cpp
processing/qgsalgorithmconditionalbranch.cpp
processing/qgsalgorithmconstantraster.cpp
processing/qgsalgorithmconverttocurves.cpp
processing/qgsalgorithmconvexhull.cpp
processing/qgsalgorithmdbscanclustering.cpp
processing/qgsalgorithmdeleteduplicategeometries.cpp
@@ -0,0 +1,157 @@
/***************************************************************************
qgsalgorithmconverttocurves.cpp
---------------------
begin : March 2018
copyright : (C) 2018 by Nyall Dawson
email : nyall dot dawson 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 "qgsalgorithmconverttocurves.h"

///@cond PRIVATE

QString QgsConvertToCurvesAlgorithm::name() const
{
return QStringLiteral( "converttocurves" );
}

QString QgsConvertToCurvesAlgorithm::displayName() const
{
return QObject::tr( "Convert to curved geometries" );
}

QStringList QgsConvertToCurvesAlgorithm::tags() const
{
return QObject::tr( "straight,segmentize,curves,curved,circular" ).split( ',' );
}

QString QgsConvertToCurvesAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
}

QString QgsConvertToCurvesAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
}

QString QgsConvertToCurvesAlgorithm::outputName() const
{
return QObject::tr( "Curves" );
}

QString QgsConvertToCurvesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm converts a geometry into its curved geometry equivalent.\n\n"
"Already curved geometries will be retained without change." );
}

QgsConvertToCurvesAlgorithm *QgsConvertToCurvesAlgorithm::createInstance() const
{
return new QgsConvertToCurvesAlgorithm();
}

QList<int> QgsConvertToCurvesAlgorithm::inputLayerTypes() const
{
return QList<int>() << QgsProcessing::TypeVectorLine << QgsProcessing::TypeVectorPolygon;
}

void QgsConvertToCurvesAlgorithm::initParameters( const QVariantMap & )
{
std::unique_ptr< QgsProcessingParameterNumber > tolerance = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DISTANCE" ),
QObject::tr( "Maximum distance tolerance" ), QgsProcessingParameterNumber::Double,
0.000001, false, 0, 10000000.0 );
tolerance->setIsDynamic( true );
tolerance->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "DISTANCE" ), QObject::tr( "Maximum distance tolerance" ), QgsPropertyDefinition::DoublePositive ) );
tolerance->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
addParameter( tolerance.release() );

std::unique_ptr< QgsProcessingParameterNumber > angleTolerance = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "ANGLE" ),
QObject::tr( "Maximum angle tolerance" ), QgsProcessingParameterNumber::Double,
0.000001, false, 0, 45.0 );
angleTolerance->setIsDynamic( true );
angleTolerance->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "ANGLE" ), QObject::tr( "Maximum angle tolerance" ), QgsPropertyDefinition::DoublePositive ) );
angleTolerance->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
addParameter( angleTolerance.release() );
}

bool QgsConvertToCurvesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mTolerance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
mDynamicTolerance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
if ( mDynamicTolerance )
mToleranceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();

mAngleTolerance = parameterAsDouble( parameters, QStringLiteral( "ANGLE" ), context );
mDynamicAngleTolerance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "ANGLE" ) );
if ( mDynamicAngleTolerance )
mAngleToleranceProperty = parameters.value( QStringLiteral( "ANGLE" ) ).value< QgsProperty >();

return true;
}

QgsFeatureList QgsConvertToCurvesAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * )
{
QgsFeature f = feature;
if ( f.hasGeometry() )
{
QgsGeometry geometry = f.geometry();
double tolerance = mTolerance;
if ( mDynamicTolerance )
tolerance = mToleranceProperty.valueAsDouble( context.expressionContext(), tolerance );
double angleTolerance = mAngleTolerance;
if ( mDynamicAngleTolerance )
angleTolerance = mAngleToleranceProperty.valueAsDouble( context.expressionContext(), angleTolerance );

f.setGeometry( geometry.convertToCurves( tolerance, angleTolerance * M_PI / 180.0 ) );
}
return QgsFeatureList() << f;
}

QgsWkbTypes::Type QgsConvertToCurvesAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
{
if ( QgsWkbTypes::isCurvedType( inputWkbType ) )
return inputWkbType;

QgsWkbTypes::Type outType = QgsWkbTypes::Unknown;
switch ( QgsWkbTypes::geometryType( inputWkbType ) )
{
case QgsWkbTypes::PointGeometry:
case QgsWkbTypes::NullGeometry:
case QgsWkbTypes::UnknownGeometry:
return inputWkbType;

case QgsWkbTypes::LineGeometry:
outType = QgsWkbTypes::CompoundCurve;
break;

case QgsWkbTypes::PolygonGeometry:
outType = QgsWkbTypes::CurvePolygon;
break;
}

if ( QgsWkbTypes::isMultiType( inputWkbType ) )
outType = QgsWkbTypes::multiType( outType );

if ( QgsWkbTypes::hasZ( inputWkbType ) )
outType = QgsWkbTypes::addZ( outType );

if ( QgsWkbTypes::hasM( inputWkbType ) )
outType = QgsWkbTypes::addM( outType );

return outType;
}


///@endcond


@@ -0,0 +1,70 @@
/***************************************************************************
qgsalgorithmconverttocurves.h
---------------------
begin : March 2018
copyright : (C) 2018 by Nyall Dawson
email : nyall dot dawson 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 QGSALGORITHMSEGMENTIZE_H
#define QGSALGORITHMSEGMENTIZE_H

#define SIP_NO_FILE

#include "qgis.h"
#include "qgsprocessingalgorithm.h"
#include "qgsmaptopixelgeometrysimplifier.h"

///@cond PRIVATE

/**
* Native segmentize by maximum distance algorithm.
*/
class QgsConvertToCurvesAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{

public:

QgsConvertToCurvesAlgorithm() = default;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QgsConvertToCurvesAlgorithm *createInstance() const override SIP_FACTORY;
QList<int> inputLayerTypes() const override;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;

protected:
QString outputName() const override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback ) override;
QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override;

private:

double mTolerance = 0.000001;
bool mDynamicTolerance = false;
QgsProperty mToleranceProperty;

double mAngleTolerance = 0.000001;
bool mDynamicAngleTolerance = false;
QgsProperty mAngleToleranceProperty;

};

///@endcond PRIVATE

#endif // QGSALGORITHMSEGMENTIZE_H


@@ -34,6 +34,7 @@
#include "qgsalgorithmclip.h"
#include "qgsalgorithmconditionalbranch.h"
#include "qgsalgorithmconstantraster.h"
#include "qgsalgorithmconverttocurves.h"
#include "qgsalgorithmconvexhull.h"
#include "qgsalgorithmdbscanclustering.h"
#include "qgsalgorithmdeleteduplicategeometries.h"
@@ -231,6 +232,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsCombineStylesAlgorithm() );
addAlgorithm( new QgsConditionalBranchAlgorithm() );
addAlgorithm( new QgsConstantRasterAlgorithm() );
addAlgorithm( new QgsConvertToCurvesAlgorithm() );
addAlgorithm( new QgsConvexHullAlgorithm() );
addAlgorithm( new QgsDbscanClusteringAlgorithm() );
addAlgorithm( new QgsDeleteDuplicateGeometriesAlgorithm() );
@@ -2129,6 +2129,13 @@ QgsGeometry QgsGeometry::densifyByDistance( double distance ) const
return engine.densifyByDistance( distance );
}

QgsGeometry QgsGeometry::convertToCurves( double distanceTolerance, double angleTolerance ) const
{
QgsInternalGeometryEngine engine( *this );

return engine.convertToCurves( distanceTolerance, angleTolerance );
}

QgsGeometry QgsGeometry::centroid() const
{
if ( !d->geometry )
@@ -1243,6 +1243,21 @@ class CORE_EXPORT QgsGeometry
*/
QgsGeometry densifyByDistance( double distance ) const;

/**
* Attempts to convert a non-curved geometry into a curved geometry type (e.g.
* LineString to CompoundCurve, Polygon to CurvePolygon).
*
* The \a distanceTolerance specifies the maximum deviation allowed between the original location
* of vertices and where they would fall on the candidate curved geometry.
*
* This method only consider a segments as suitable for replacing with an arc if the points are all
* regularly spaced on the candidate arc. The \a pointSpacingAngleTolerance parameter specifies the maximum
* angular deviation (in radians) allowed when testing for regular point spacing.
*
* \since QGIS 3.2
*/
QgsGeometry convertToCurves( double distanceTolerance = 1e-8, double angleTolerance = 1e-8 ) const;

/**
* Returns the center of mass of a geometry.
*
@@ -871,6 +871,66 @@ double QgsGeometryUtils::circleTangentDirection( const QgsPoint &tangentPoint, c
return angle;
}

// Ported from PostGIS' pt_continues_arc
bool QgsGeometryUtils::pointContinuesArc( const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance, double pointSpacingAngleTolerance )
{
double centerX = 0;
double centerY = 0;
double radius = 0;
circleCenterRadius( a1, a2, a3, radius, centerX, centerY );

// Co-linear a1/a2/a3
if ( radius < 0.0 )
return false;

// distance of candidate point to center of arc a1-a2-a3
double bDistance = std::sqrt( ( b.x() - centerX ) * ( b.x() - centerX ) +
( b.y() - centerY ) * ( b.y() - centerY ) );

double diff = std::fabs( radius - bDistance );

auto arcAngle = []( const QgsPoint & a, const QgsPoint & b, const QgsPoint & c )->double
{
double abX = b.x() - a.x();
double abY = b.y() - a.y();

double cbX = b.x() - c.x();
double cbY = b.y() - c.y();

double dot = ( abX * cbX + abY * cbY ); /* dot product */
double cross = ( abX * cbY - abY * cbX ); /* cross product */

double alpha = std::atan2( cross, dot );

return alpha;
};

// Is the point b on the circle?
if ( diff < distanceTolerance )
{
double angle1 = arcAngle( a1, a2, a3 );
double angle2 = arcAngle( a2, a3, b );

// Is the sweep angle similar to the previous one?
// We only consider a segment replacable by an arc if the points within
// it are regularly spaced
diff = std::fabs( angle1 - angle2 );
if ( diff > pointSpacingAngleTolerance )
{
return false;
}

int a2Side = leftOfLine( a2.x(), a2.y(), a1.x(), a1.y(), a3.x(), a3.y() );
int bSide = leftOfLine( b.x(), b.y(), a1.x(), a1.y(), a3.x(), a3.y() );

// Is the point b on the same side of a1/a3 as the mid-point a2 is?
// If not, it's in the unbounded part of the circle, so it continues the arc, return true.
if ( bSide != a2Side )
return true;
}
return false;
}

void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance, QgsAbstractGeometry::SegmentationToleranceType toleranceType, bool hasZ, bool hasM )
{
bool reversed = false;
@@ -262,7 +262,7 @@ class CORE_EXPORT QgsGeometryUtils
static QVector<SelfIntersection> selfIntersections( const QgsAbstractGeometry *geom, int part, int ring, double tolerance ) SIP_SKIP;

/**
* Returns a value < 0 if the point (\a x, \a y) is left of the line from (\a x1, \a y1) -> ( \a x2, \a y2).
* Returns a value < 0 if the point (\a x, \a y) is left of the line from (\a x1, \a y1) -> (\a x2, \a y2).
* A positive return value indicates the point is to the right of the line.
*
* If the return value is 0, then the test was unsuccessful (e.g. due to testing a point exactly
@@ -372,6 +372,22 @@ class CORE_EXPORT QgsGeometryUtils
QgsAbstractGeometry::SegmentationToleranceType toleranceType = QgsAbstractGeometry::MaximumAngle,
bool hasZ = false, bool hasM = false );

/**
* Returns true if point \a b is on the arc formed by points \a a1, \a a2, and \a a3, but not within
* that arc portion already described by \a a1, \a a2 and \a a3.
*
* The \a distanceTolerance specifies the maximum deviation allowed between the original location
* of point \b and where it would fall on the candidate arc.
*
* This method only consider a segments as continuing an arc if the points are all regularly spaced
* on the candidate arc. The \a pointSpacingAngleTolerance parameter specifies the maximum
* angular deviation (in radians) allowed when testing for regular point spacing.
*
* \since QGIS 3.2
*/
static bool pointContinuesArc( const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance,
double pointSpacingAngleTolerance );

/**
* 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.

0 comments on commit 9939142

Please sign in to comment.
You can’t perform that action at this time.