@@ -20,6 +20,7 @@ email : marco.hugentobler at sourcepole dot com
2020#include " qgsgeometrycollection.h"
2121#include " qgslinestring.h"
2222#include " qgswkbptr.h"
23+ #include " qgslogger.h"
2324
2425#include < memory>
2526#include < QStringList>
@@ -631,25 +632,44 @@ double QgsGeometryUtils::circleTangentDirection( const QgsPoint &tangentPoint, c
631632
632633void QgsGeometryUtils::segmentizeArc ( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance, QgsAbstractGeometry::SegmentationToleranceType toleranceType, bool hasZ, bool hasM )
633634{
635+ bool reversed = false ;
634636 bool clockwise = false ;
635637 int segSide = segmentSide ( p1, p3, p2 );
636638 if ( segSide == -1 )
637639 {
638640 clockwise = true ;
639641 }
640642
641- QgsPoint circlePoint1 = clockwise ? p3 : p1;
642- QgsPoint circlePoint2 = p2;
643- QgsPoint circlePoint3 = clockwise ? p1 : p3 ;
643+ QgsPoint circlePoint1;
644+ const QgsPoint circlePoint2 = p2;
645+ QgsPoint circlePoint3;
646+
647+ if ( clockwise )
648+ {
649+ // Reverse !
650+ circlePoint1 = p3;
651+ circlePoint3 = p1;
652+ clockwise = false ;
653+ reversed = true ;
654+ }
655+ else
656+ {
657+ circlePoint1 = p1;
658+ circlePoint3 = p3;
659+ }
644660
645661 // adapted code from PostGIS
646662 double radius = 0 ;
647663 double centerX = 0 ;
648664 double centerY = 0 ;
649665 circleCenterRadius ( circlePoint1, circlePoint2, circlePoint3, radius, centerX, centerY );
650666
667+ QgsDebugMsg ( QString ( " Center: POINT(%1 %2) - Radius: %3 - Clockwise: %4" )
668+ .arg ( centerX ) .arg ( centerY ) .arg ( radius ) .arg ( clockwise ) );
669+
651670 if ( circlePoint1 != circlePoint3 && ( radius < 0 || qgsDoubleNear ( segSide, 0.0 ) ) ) // points are colinear
652671 {
672+ QgsDebugMsg ( QString ( " Collinear curve" ) );
653673 points.append ( p1 );
654674 points.append ( p2 );
655675 points.append ( p3 );
@@ -662,24 +682,65 @@ void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, co
662682 double halfAngle = std::acos ( -tolerance / radius + 1 );
663683 increment = 2 * halfAngle;
664684 }
685+ QgsDebugMsg ( QString ( " Increment: %1" ).arg ( increment ) );
665686
666687 // angles of pt1, pt2, pt3
667688 double a1 = std::atan2 ( circlePoint1.y () - centerY, circlePoint1.x () - centerX );
668689 double a2 = std::atan2 ( circlePoint2.y () - centerY, circlePoint2.x () - centerX );
669690 double a3 = std::atan2 ( circlePoint3.y () - centerY, circlePoint3.x () - centerX );
670691
671- /* Adjust a3 up so we can increment from a1 to a3 cleanly */
672- if ( a3 <= a1 )
673- a3 += 2.0 * M_PI;
674- if ( a2 < a1 )
675- a2 += 2.0 * M_PI;
692+ QgsDebugMsg ( QString ( " a1:%1 (%4) a2:%2 (%5) a3:%3 (%6)" )
693+ .arg ( a1 ).arg ( a2 ).arg ( a3 )
694+ .arg ( a1 * 180 / M_PI ).arg ( a2 * 180 / M_PI ).arg ( a3 * 180 / M_PI )
695+ );
696+
697+ // Make segmentation symmetric
698+ const bool symmetric = true ;
699+ if ( symmetric )
700+ {
701+ double angle = clockwise ? a1 - a3 : a3 - a1;
702+ if ( angle < 0 ) angle += M_PI * 2 ;
703+ QgsDebugMsg ( QString ( " total angle: %1 (%2)" )
704+ .arg ( angle ) .arg ( angle * 180 / M_PI )
705+ );
706+
707+ /* Number of segments in output */
708+ int segs = ceil ( angle / increment );
709+ /* Tweak increment to be regular for all the arc */
710+ increment = angle / segs;
711+
712+ QgsDebugMsg ( QString ( " symmetric adjusted increment:%1" ) .arg ( increment ) );
713+ }
714+
715+ if ( clockwise )
716+ {
717+ increment *= -1 ;
718+ /* Adjust a3 down so we can increment from a1 to a3 cleanly */
719+ if ( a3 > a1 )
720+ a3 -= 2.0 * M_PI;
721+ if ( a2 > a1 )
722+ a2 -= 2.0 * M_PI;
723+ }
724+ else
725+ {
726+ /* Adjust a3 up so we can increment from a1 to a3 cleanly */
727+ if ( a3 < a1 )
728+ a3 += 2.0 * M_PI;
729+ if ( a2 < a1 )
730+ a2 += 2.0 * M_PI;
731+ }
732+
733+ QgsDebugMsg ( QString ( " ADJUSTED - a1:%1 (%4) a2:%2 (%5) a3:%3 (%6)" )
734+ .arg ( a1 ).arg ( a2 ).arg ( a3 )
735+ .arg ( a1 * 180 / M_PI ).arg ( a2 * 180 / M_PI ).arg ( a3 * 180 / M_PI )
736+ );
676737
677738 double x, y;
678739 double z = 0 ;
679740 double m = 0 ;
680741
681742 QList<QgsPoint> stringPoints;
682- stringPoints.insert ( clockwise ? 0 : stringPoints. size () , circlePoint1 );
743+ stringPoints.insert ( 0 , circlePoint1 );
683744 if ( circlePoint2 != circlePoint3 && circlePoint1 != circlePoint2 ) // draw straight line segment if two points have the same position
684745 {
685746 QgsWkbTypes::Type pointWkbType = QgsWkbTypes::Point;
@@ -688,30 +749,58 @@ void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, co
688749 if ( hasM )
689750 pointWkbType = QgsWkbTypes::addM ( pointWkbType );
690751
752+ QgsDebugMsg ( QString ( " a1:%1 (%2), a3:%3 (%4), inc:%5, shi:?, cw:%6" )
753+ . arg ( a1 ). arg ( a1 * 180 / M_PI )
754+ . arg ( a3 ). arg ( a3 * 180 / M_PI )
755+ . arg ( increment ). arg ( clockwise )
756+ );
757+
691758 // make sure the curve point p2 is part of the segmentized vertices. But only if p1 != p3
759+ // TODO: make this a parameter
692760 bool addP2 = true ;
693761 if ( qgsDoubleNear ( circlePoint1.x (), circlePoint3.x () ) && qgsDoubleNear ( circlePoint1.y (), circlePoint3.y () ) )
694762 {
695763 addP2 = false ;
696764 }
697-
698- for ( double angle = a1 + increment; angle < a3; angle += increment )
765+ addP2 = false ;
766+
767+ // As we're adding the last point in any case, we'll avoid
768+ // including a point which is at less than 1% increment distance
769+ // from it (may happen to find them due to numbers approximation).
770+ // NOTE that this effectively allows in output some segments which
771+ // are more distant than requested. This is at most 1% off
772+ // from requested MaxAngle and less for MaxError.
773+ double tolError = increment / 100 ;
774+ double stopAngle = clockwise ? a3 - tolError : a3 - tolError;
775+ QgsDebugMsg ( QString ( " stopAngle: %1 (%2)" ) . arg ( stopAngle ) .arg ( stopAngle * 180 / M_PI ) );
776+ for ( double angle = a1 + increment; clockwise ? angle > stopAngle : angle < stopAngle; angle += increment )
699777 {
700- if ( ( addP2 && angle > a2 ) )
778+ if ( addP2 && angle > a2 )
701779 {
702- stringPoints.insert ( clockwise ? 0 : stringPoints.size (), circlePoint2 );
780+ if ( clockwise )
781+ {
782+ if ( *stringPoints.begin () != circlePoint2 )
783+ {
784+ QgsDebugMsg ( QString ( " Adding control point, with angle %1 (%2)" ) . arg ( a2 ) .arg ( a2 * 180 / M_PI ) );
785+ stringPoints.insert ( 0 , circlePoint2 );
786+ }
787+ }
788+ else
789+ {
790+ if ( *stringPoints.rbegin () != circlePoint2 )
791+ {
792+ QgsDebugMsg ( QString ( " Adding control point, with angle %1 (%2)" ) . arg ( a2 ) .arg ( a2 * 180 / M_PI ) );
793+ stringPoints.insert ( stringPoints.size (), circlePoint2 );
794+ }
795+ }
703796 addP2 = false ;
704797 }
705798
799+ QgsDebugMsg ( QString ( " SA - %1 (%2)" ) . arg ( angle ) .arg ( angle * 180 / M_PI ) );
800+
706801 x = centerX + radius * std::cos ( angle );
707802 y = centerY + radius * std::sin ( angle );
708803
709- if ( !hasZ && !hasM )
710- {
711- stringPoints.insert ( clockwise ? 0 : stringPoints.size (), QgsPoint ( x, y ) );
712- continue ;
713- }
714-
715804 if ( hasZ )
716805 {
717806 z = interpolateArcValue ( angle, a1, a2, a3, circlePoint1.z (), circlePoint2.z (), circlePoint3.z () );
@@ -721,10 +810,18 @@ void QgsGeometryUtils::segmentizeArc( const QgsPoint &p1, const QgsPoint &p2, co
721810 m = interpolateArcValue ( angle, a1, a2, a3, circlePoint1.m (), circlePoint2.m (), circlePoint3.m () );
722811 }
723812
724- stringPoints.insert ( clockwise ? 0 : stringPoints.size (), QgsPoint ( pointWkbType, x, y, z, m ) );
813+ QgsDebugMsg ( QString ( " -> POINT(%1 %2)" ) . arg ( x ) .arg ( y ) );
814+ stringPoints.insert ( stringPoints.size (), QgsPoint ( pointWkbType, x, y, z, m ) );
725815 }
726816 }
727- stringPoints.insert ( clockwise ? 0 : stringPoints.size (), circlePoint3 );
817+ QgsDebugMsg ( QString ( " appending last point -> POINT(%1 %2)" ) . arg ( circlePoint3.x () ) .arg ( circlePoint3.y () ) );
818+ stringPoints.insert ( stringPoints.size (), circlePoint3 );
819+
820+ // TODO: check if or implement QgsPointSequence directly taking an iterator to append
821+ if ( reversed )
822+ {
823+ std::reverse ( stringPoints.begin (), stringPoints.end () );
824+ }
728825 points.append ( stringPoints );
729826}
730827
0 commit comments