mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-30 00:29:39 -05:00
[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.
This commit is contained in:
parent
27b5dae4bf
commit
9939142ba9
@ -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
|
||||
|
||||
157
src/analysis/processing/qgsalgorithmconverttocurves.cpp
Normal file
157
src/analysis/processing/qgsalgorithmconverttocurves.cpp
Normal file
@ -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 ¶meters, 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
|
||||
|
||||
|
||||
70
src/analysis/processing/qgsalgorithmconverttocurves.h
Normal file
70
src/analysis/processing/qgsalgorithmconverttocurves.h
Normal file
@ -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 ¶meters, 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.
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "qgsmultipolygon.h"
|
||||
#include "qgspolygon.h"
|
||||
#include "qgsmulticurve.h"
|
||||
#include "qgscircularstring.h"
|
||||
#include "qgsgeometry.h"
|
||||
#include "qgsgeometryutils.h"
|
||||
#include "qgslinesegment.h"
|
||||
@ -1167,3 +1168,256 @@ QVector<QgsPointXY> QgsInternalGeometryEngine::randomPointsInPolygon( const QgsG
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ported from PostGIS' lwgeom pta_unstroke
|
||||
|
||||
std::unique_ptr< QgsCompoundCurve > lineToCurve( const QgsLineString *lineString, double distanceTolerance,
|
||||
double pointSpacingAngleTolerance )
|
||||
{
|
||||
std::unique_ptr< QgsCompoundCurve > out = qgis::make_unique< QgsCompoundCurve >();
|
||||
|
||||
/* Minimum number of edges, per quadrant, required to define an arc */
|
||||
const unsigned int minQuadEdges = 2;
|
||||
|
||||
/* Die on null input */
|
||||
if ( !lineString )
|
||||
return nullptr;
|
||||
|
||||
/* Null on empty input? */
|
||||
if ( lineString->nCoordinates() == 0 )
|
||||
return nullptr;
|
||||
|
||||
/* We can't desegmentize anything shorter than four points */
|
||||
if ( lineString->nCoordinates() < 4 )
|
||||
{
|
||||
out->addCurve( lineString->clone() );
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Allocate our result array of vertices that are part of arcs */
|
||||
int numEdges = lineString->nCoordinates() - 1;
|
||||
QVector< int > edgesInArcs( numEdges + 1, 0 );
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/* We make a candidate arc of the first two edges, */
|
||||
/* And then see if the next edge follows it */
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
int currentArc = 1;
|
||||
QgsPoint a1;
|
||||
QgsPoint a2;
|
||||
QgsPoint a3;
|
||||
QgsPoint b;
|
||||
double centerX = 0.0;
|
||||
double centerY = 0.0;
|
||||
double radius = 0;
|
||||
|
||||
while ( i < numEdges - 2 )
|
||||
{
|
||||
unsigned int arcEdges = 0;
|
||||
double numQuadrants = 0;
|
||||
double angle;
|
||||
|
||||
bool foundArc = false;
|
||||
/* Make candidate arc */
|
||||
a1 = lineString->pointN( i );
|
||||
a2 = lineString->pointN( i + 1 );
|
||||
a3 = lineString->pointN( i + 2 );
|
||||
QgsPoint first = a1;
|
||||
|
||||
for ( j = i + 3; j < numEdges + 1; j++ )
|
||||
{
|
||||
b = lineString->pointN( j );
|
||||
|
||||
/* Does this point fall on our candidate arc? */
|
||||
if ( QgsGeometryUtils::pointContinuesArc( a1, a2, a3, b, distanceTolerance, pointSpacingAngleTolerance ) )
|
||||
{
|
||||
/* Yes. Mark this edge and the two preceding it as arc components */
|
||||
foundArc = true;
|
||||
for ( k = j - 1; k > j - 4; k-- )
|
||||
edgesInArcs[k] = currentArc;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No. So we're done with this candidate arc */
|
||||
currentArc++;
|
||||
break;
|
||||
}
|
||||
|
||||
a1 = a2;
|
||||
a2 = a3;
|
||||
a3 = b;
|
||||
}
|
||||
/* Jump past all the edges that were added to the arc */
|
||||
if ( foundArc )
|
||||
{
|
||||
/* Check if an arc was composed by enough edges to be
|
||||
* really considered an arc
|
||||
* See http://trac.osgeo.org/postgis/ticket/2420
|
||||
*/
|
||||
arcEdges = j - 1 - i;
|
||||
if ( first.x() == b.x() && first.y() == b.y() )
|
||||
{
|
||||
numQuadrants = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsGeometryUtils::circleCenterRadius( first, b, a1, radius, centerX, centerY );
|
||||
|
||||
angle = arcAngle( first, QgsPoint( centerX, centerY ), b );
|
||||
int p2Side = QgsGeometryUtils::leftOfLine( b.x(), b.y(), first.x(), first.y(), a1.x(), a1.y() );
|
||||
if ( p2Side >= 0 )
|
||||
angle = -angle;
|
||||
|
||||
if ( angle < 0 )
|
||||
angle = 2 * M_PI + angle;
|
||||
numQuadrants = ( 4 * angle ) / ( 2 * M_PI );
|
||||
}
|
||||
/* a1 is first point, b is last point */
|
||||
if ( arcEdges < minQuadEdges * numQuadrants )
|
||||
{
|
||||
// LWDEBUGF( 4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants );
|
||||
for ( k = j - 1; k >= i; k-- )
|
||||
edgesInArcs[k] = 0;
|
||||
}
|
||||
|
||||
i = j - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Mark this edge as a linear edge */
|
||||
edgesInArcs[i] = 0;
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
/* non-zero if edge is part of an arc */
|
||||
int edgeType = edgesInArcs[0];
|
||||
|
||||
auto addPointsToCurve = [ lineString, &out ]( int start, int end, int type )
|
||||
{
|
||||
if ( type == 0 )
|
||||
{
|
||||
// straight segment
|
||||
QVector< QgsPoint > points;
|
||||
for ( int j = start; j < end + 2; ++ j )
|
||||
{
|
||||
points.append( lineString->pointN( j ) );
|
||||
}
|
||||
std::unique_ptr< QgsCurve > straightSegment = qgis::make_unique< QgsLineString >( points );
|
||||
out->addCurve( straightSegment.release() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// curved segment
|
||||
QVector< QgsPoint > points;
|
||||
points.append( lineString->pointN( start ) );
|
||||
points.append( lineString->pointN( ( start + end + 1 ) / 2 ) );
|
||||
points.append( lineString->pointN( end + 1 ) );
|
||||
std::unique_ptr< QgsCircularString > curvedSegment = qgis::make_unique< QgsCircularString >();
|
||||
curvedSegment->setPoints( points );
|
||||
out->addCurve( curvedSegment.release() );
|
||||
}
|
||||
};
|
||||
|
||||
for ( int i = 1; i < numEdges; i++ )
|
||||
{
|
||||
if ( edgeType != edgesInArcs[i] )
|
||||
{
|
||||
end = i - 1;
|
||||
addPointsToCurve( start, end, edgeType );
|
||||
start = i;
|
||||
edgeType = edgesInArcs[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Roll out last item */
|
||||
end = numEdges - 1;
|
||||
addPointsToCurve( start, end, edgeType );
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::unique_ptr< QgsAbstractGeometry > convertGeometryToCurves( const QgsAbstractGeometry *geom, double distanceTolerance, double angleTolerance )
|
||||
{
|
||||
if ( QgsWkbTypes::geometryType( geom->wkbType() ) == QgsWkbTypes::LineGeometry )
|
||||
{
|
||||
return lineToCurve( static_cast< const QgsLineString * >( geom ), distanceTolerance, angleTolerance );
|
||||
}
|
||||
else
|
||||
{
|
||||
// polygon
|
||||
const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
|
||||
std::unique_ptr< QgsCurvePolygon > result = qgis::make_unique< QgsCurvePolygon>();
|
||||
|
||||
result->setExteriorRing( lineToCurve( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
|
||||
distanceTolerance, angleTolerance ).release() );
|
||||
for ( int i = 0; i < polygon->numInteriorRings(); ++i )
|
||||
{
|
||||
result->addInteriorRing( lineToCurve( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
|
||||
distanceTolerance, angleTolerance ).release() );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
QgsGeometry QgsInternalGeometryEngine::convertToCurves( double distanceTolerance, double angleTolerance ) const
|
||||
{
|
||||
if ( !mGeometry )
|
||||
{
|
||||
return QgsGeometry();
|
||||
}
|
||||
|
||||
if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == QgsWkbTypes::PointGeometry )
|
||||
{
|
||||
return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
|
||||
}
|
||||
|
||||
if ( QgsWkbTypes::isCurvedType( mGeometry->wkbType() ) )
|
||||
{
|
||||
// already curved. In future we may want to allow this, and convert additional candidate segments
|
||||
// in an already curved geometry to curves
|
||||
return QgsGeometry( mGeometry->clone() );
|
||||
}
|
||||
|
||||
if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
|
||||
{
|
||||
int numGeom = gc->numGeometries();
|
||||
QVector< QgsAbstractGeometry * > geometryList;
|
||||
geometryList.reserve( numGeom );
|
||||
for ( int i = 0; i < numGeom; ++i )
|
||||
{
|
||||
geometryList << convertGeometryToCurves( gc->geometryN( i ), distanceTolerance, angleTolerance ).release();
|
||||
}
|
||||
|
||||
QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
|
||||
for ( QgsAbstractGeometry *g : qgis::as_const( geometryList ) )
|
||||
{
|
||||
first.addPart( g );
|
||||
}
|
||||
return first;
|
||||
}
|
||||
else
|
||||
{
|
||||
return QgsGeometry( convertGeometryToCurves( mGeometry, distanceTolerance, angleTolerance ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,6 +166,21 @@ class QgsInternalGeometryEngine
|
||||
static QVector< QgsPointXY > randomPointsInPolygon( const QgsGeometry &polygon, int count,
|
||||
const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed = 0, QgsFeedback *feedback = nullptr );
|
||||
|
||||
/**
|
||||
* 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, double angleTolerance ) const;
|
||||
|
||||
private:
|
||||
const QgsAbstractGeometry *mGeometry = nullptr;
|
||||
};
|
||||
|
||||
@ -80,6 +80,7 @@ class TestQgsGeometryUtils: public QObject
|
||||
void testTriangleArea();
|
||||
void testWeightedPointInTriangle_data();
|
||||
void testWeightedPointInTriangle();
|
||||
void testPointContinuesArc();
|
||||
};
|
||||
|
||||
|
||||
@ -1470,5 +1471,31 @@ void TestQgsGeometryUtils::testWeightedPointInTriangle()
|
||||
QGSCOMPARENEAR( y, expectedY, 0.0000001 );
|
||||
}
|
||||
|
||||
void TestQgsGeometryUtils::testPointContinuesArc()
|
||||
{
|
||||
// normal arcs
|
||||
QVERIFY( QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 1, -1 ), 0.000000001, 0.000001 ) );
|
||||
QVERIFY( QgsGeometryUtils::pointContinuesArc( QgsPoint( 2, 0 ), QgsPoint( 1, 1 ), QgsPoint( 0, 0 ), QgsPoint( 1, -1 ), 0.000000001, 0.000001 ) );
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 3, 0 ), 0.000000001, 0.000001 ) );
|
||||
QVERIFY( QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.29289321881, 0.707106781 ), QgsPoint( 1, 1 ), QgsPoint( 1.707106781, 0.707106781 ), 0.00001, 0.00001 ) );
|
||||
|
||||
// irregular spacing
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.29289321881, 0.707106781 ), QgsPoint( 1, 1 ), QgsPoint( 1, -1 ), 0.00001, 0.00001 ) );
|
||||
|
||||
// inside current arc
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.29289321881, 0.707106781 ), QgsPoint( 1, 1 ), QgsPoint( 0.29289321881, 0.707106781 ), 0.00001, 0.00001 ) );
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.29289321881, 0.707106781 ), QgsPoint( 1, 1 ), QgsPoint( 1, 1 ), 0.00001, 0.00001 ) );
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.29289321881, 0.707106781 ), QgsPoint( 1, 1 ), QgsPoint( 0, 0 ), 0.00001, 0.00001 ) );
|
||||
|
||||
// colinear points
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 0.5, 0.5 ), QgsPoint( 1, 1 ), QgsPoint( 1.5, 1.5 ), 0.00001, 0.00001 ) );
|
||||
|
||||
// with a bit more tolerance
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 1.01, -1 ), 0.000000001, 0.05 ) );
|
||||
QVERIFY( QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 1.01, -1 ), 0.1, 0.05 ) );
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 1.01, -1 ), 0.1, 0.000001 ) );
|
||||
QVERIFY( !QgsGeometryUtils::pointContinuesArc( QgsPoint( 0, 0 ), QgsPoint( 1, 1 ), QgsPoint( 2, 0 ), QgsPoint( 1.01, -1 ), 0.000000001, 0.05 ) );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsGeometryUtils )
|
||||
#include "testqgsgeometryutils.moc"
|
||||
|
||||
@ -5183,6 +5183,32 @@ class TestQgsGeometry(unittest.TestCase):
|
||||
self.assertAlmostEqual(o, exp, 5,
|
||||
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))
|
||||
|
||||
def testConvertToCurves(self):
|
||||
tests = [
|
||||
["LINESTRING Z (3 3 3,2.4142135623731 1.58578643762691 3,1 1 3,-0.414213562373092 1.5857864376269 3,-1 2.99999999999999 3,-0.414213562373101 4.41421356237309 3,0.999999999999991 5 3,2.41421356237309 4.4142135623731 3,3 3 3)",
|
||||
"CompoundCurveZ (CircularStringZ (3 3 3, -1 2.99999999999998979 3, 3 3 3))", 0.00000001, 0.0000001],
|
||||
["LINESTRING(0 0,10 0,10 10,0 10,0 0)", "CompoundCurve((0 0,10 0,10 10,0 10,0 0))", 0.00000001, 0.00000001],
|
||||
["LINESTRING(0 0,10 0,10 10,0 10)", "CompoundCurve((0 0,10 0,10 10,0 10))", 0.00000001, 0.00000001],
|
||||
["LINESTRING(10 10,0 10,0 0,10 0)", "CompoundCurve((10 10,0 10,0 0,10 0))", 0.0000001, 0.00000001],
|
||||
["LINESTRING(0 0, 1 1)", "CompoundCurve((0 0, 1 1))", 0.00000001, 0.00000001],
|
||||
["GEOMETRYCOLLECTION(LINESTRING(10 10,10 11),LINESTRING(10 11,11 11),LINESTRING(11 11,10 10))",
|
||||
"MultiCurve (CompoundCurve ((10 10, 10 11)),CompoundCurve ((10 11, 11 11)),CompoundCurve ((11 11, 10 10)))", 0.000001, 0.000001],
|
||||
["GEOMETRYCOLLECTION(LINESTRING(4 4,4 8),CIRCULARSTRING(4 8,6 10,8 8),LINESTRING(8 8,8 4))",
|
||||
"MultiCurve (CompoundCurve ((4 4, 4 8)),CompoundCurve (CircularString (4 8, 6 10, 8 8)),CompoundCurve ((8 8, 8 4)))", 0.0000001, 0.0000001],
|
||||
["LINESTRING(-13151357.927248 3913656.64539871,-13151419.0845266 3913664.12016378,-13151441.323537 3913666.61175286,-13151456.8908442 3913666.61175286,-13151476.9059536 3913666.61175286,-13151496.921063 3913666.61175287,-13151521.3839744 3913666.61175287,-13151591.4368571 3913665.36595828)",
|
||||
"CompoundCurve ((-13151357.92724799923598766 3913656.64539870992302895, -13151419.08452660031616688 3913664.12016378017142415, -13151441.32353699952363968 3913666.61175285978242755, -13151456.8908441998064518 3913666.61175285978242755, -13151476.90595359914004803 3913666.61175285978242755, -13151496.92106300033628941 3913666.61175287002697587, -13151521.38397439941763878 3913666.61175287002697587, -13151591.43685710057616234 3913665.36595827993005514))", 0.000001, 0.0000001],
|
||||
["Point( 1 2 )", "Point( 1 2 )", 0.00001, 0.00001],
|
||||
["MultiPoint( 1 2, 3 4 )", "MultiPoint( (1 2 ), (3 4 ))", 0.00001, 0.00001]
|
||||
|
||||
]
|
||||
for t in tests:
|
||||
g1 = QgsGeometry.fromWkt(t[0])
|
||||
distance_tolerance = t[2]
|
||||
angle_tolerance = t[3]
|
||||
o = g1.convertToCurves(distance_tolerance, angle_tolerance)
|
||||
self.assertTrue(compareWkt(o.asWkt(), t[1], 0.00001),
|
||||
"clipped: mismatch Expected:\n{}\nGot:\n{}\n".format(t[1], o.asWkt()))
|
||||
|
||||
def testBoundingBoxIntersects(self):
|
||||
tests = [
|
||||
["LINESTRING (0 0, 100 100)", "LINESTRING (90 0, 100 0)", True],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user