QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsnurbscurve.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnurbscurve.cpp
3 -----------------
4 begin : September 2025
5 copyright : (C) 2025 by Loïc Bartoletti
6 email : loic dot bartoletti at oslandia dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsnurbscurve.h"
19
20#include <algorithm>
21#include <cmath>
22#include <limits>
23#include <memory>
24#include <nlohmann/json.hpp>
25#include <vector>
26
27#include "qgsapplication.h"
28#include "qgsbox3d.h"
30#include "qgsfeedback.h"
32#include "qgsgeometryutils.h"
34#include "qgslinestring.h"
35#include "qgspoint.h"
36#include "qgsrectangle.h"
37#include "qgswkbptr.h"
38#include "qgswkbtypes.h"
39
40#include <QPainterPath>
41#include <QString>
42
43using namespace Qt::StringLiterals;
44
45using namespace nlohmann;
46
51
52QgsNurbsCurve::QgsNurbsCurve( const QVector<QgsPoint> &controlPoints, int degree, const QVector<double> &knots, const QVector<double> &weights )
53 : mControlPoints( controlPoints )
54 , mKnots( knots )
55 , mWeights( weights )
56 , mDegree( degree )
57{
59
60 // Update WKB type based on coordinate dimensions
61 if ( !mControlPoints.isEmpty() )
62 {
63 const QgsPoint &firstPoint = mControlPoints.first();
64 if ( firstPoint.is3D() )
65 mWkbType = QgsWkbTypes::addZ( mWkbType );
66 if ( firstPoint.isMeasure() )
67 mWkbType = QgsWkbTypes::addM( mWkbType );
68 }
69}
70
72{
73 return new QgsNurbsCurve( *this );
74}
75
77{
78 const int n = mControlPoints.size();
79 if ( n < 2 || mDegree < 1 )
80 return false;
81
82 if ( mDegree != n - 1 )
83 return false;
84
85 if ( !isBSpline() )
86 return false;
87
88 if ( mKnots.size() != n + mDegree + 1 )
89 return false;
90
91 for ( int i = 0; i <= mDegree; ++i )
92 {
93 if ( !qgsDoubleNear( mKnots[i], 0.0 ) )
94 return false;
95 }
96 for ( int i = n; i < mKnots.size(); ++i )
97 {
98 if ( !qgsDoubleNear( mKnots[i], 1.0 ) )
99 return false;
100 }
101
102 return true;
103}
104
106{
107 for ( const double w : mWeights )
108 {
109 if ( !qgsDoubleNear( w, 1.0 ) )
110 return false;
111 }
112 return true;
113}
114
116{
117 return !isBSpline();
118}
119
121{
122 const int n = mControlPoints.size();
123 return mDegree == 3 && n >= 4 && ( n - 1 ) % 3 == 0;
124}
125
140static int findKnotSpan( const int degree, const double u, const QVector<double> &knots, const int nPoints )
141{
142 // Special case: u at or beyond end of parameter range
143 if ( u >= knots[nPoints] )
144 return nPoints - 1;
145
146 // Special case: u at or before start of parameter range
147 if ( u <= knots[degree] )
148 return degree;
149
150 // Binary search for the knot span
151 int low = degree;
152 int high = nPoints;
153 int mid = ( low + high ) / 2;
154
155 while ( u < knots[mid] || u >= knots[mid + 1] )
156 {
157 if ( u < knots[mid] )
158 high = mid;
159 else
160 low = mid;
161 mid = ( low + high ) / 2;
162 }
163
164 return mid;
165}
166
167// Evaluates the NURBS curve at parameter t using De Boor's algorithm.
168// Implements Algorithm A4.1 from "The NURBS Book" (Piegl & Tiller).
170{
171 const int n = mControlPoints.size();
172 if ( n == 0 )
173 {
174 return QgsPoint();
175 }
176
177 QString error;
178 if ( !isValid( error, Qgis::GeometryValidityFlags() ) )
179 {
180 return QgsPoint();
181 }
182
183 // Clamp parameter t to valid range [0,1]
184 if ( t <= 0.0 )
185 return mControlPoints.first();
186 if ( t >= 1.0 )
187 return mControlPoints.last();
188
189 const bool hasZ = !mControlPoints.isEmpty() && mControlPoints.first().is3D();
190 const bool hasM = !mControlPoints.isEmpty() && mControlPoints.first().isMeasure();
191
192 // Remap parameter from [0,1] to knot vector range [knots[degree], knots[n]]
193 const double u = mKnots[mDegree] + t * ( mKnots[n] - mKnots[mDegree] );
194
195 // Find the knot span containing parameter u (Algorithm A2.1)
196 const int span = findKnotSpan( mDegree, u, mKnots, n );
197
198 // Temporary arrays for De Boor iteration (degree+1 points)
199 // Using homogeneous coordinates: (w*x, w*y, w*z, w) for rational curves
200 // Use std::vector for local temp arrays - no need for Qt's implicit sharing overhead
201 std::vector<double> tempX( mDegree + 1 );
202 std::vector<double> tempY( mDegree + 1 );
203 std::vector<double> tempZ( mDegree + 1 );
204 std::vector<double> tempM( mDegree + 1 );
205 std::vector<double> tempW( mDegree + 1 );
206
207 // Initialize temp arrays with control points and weights
208 for ( int j = 0; j <= mDegree; ++j )
209 {
210 const int cpIdx = span - mDegree + j;
211 const QgsPoint &cp = mControlPoints[cpIdx];
212 const double w = ( cpIdx < mWeights.size() ) ? mWeights[cpIdx] : 1.0;
213
214 // Store in homogeneous coordinates (w * P)
215 tempX[j] = cp.x() * w;
216 tempY[j] = cp.y() * w;
217 tempZ[j] = hasZ ? cp.z() * w : 0.0;
218 tempM[j] = hasM ? cp.m() : 0.0; // M is not weighted
219 tempW[j] = w;
220 }
221
222 // De Boor iteration (Algorithm A4.1) in homogeneous space
223 for ( int k = 1; k <= mDegree; ++k )
224 {
225 for ( int j = mDegree; j >= k; --j )
226 {
227 const int knotIdx = span - mDegree + j;
228 const double denom = mKnots[knotIdx + mDegree - k + 1] - mKnots[knotIdx];
229
230 if ( !qgsDoubleNear( denom, 0.0 ) )
231 {
232 const double alpha = ( u - mKnots[knotIdx] ) / denom;
233 const double oneMinusAlpha = 1.0 - alpha;
234
235 // Linear interpolation in homogeneous space
236 tempX[j] = oneMinusAlpha * tempX[j - 1] + alpha * tempX[j];
237 tempY[j] = oneMinusAlpha * tempY[j - 1] + alpha * tempY[j];
238 if ( hasZ )
239 tempZ[j] = oneMinusAlpha * tempZ[j - 1] + alpha * tempZ[j];
240 if ( hasM )
241 tempM[j] = oneMinusAlpha * tempM[j - 1] + alpha * tempM[j];
242
243 // Interpolate weights
244 tempW[j] = oneMinusAlpha * tempW[j - 1] + alpha * tempW[j];
245 }
246 }
247 }
248
249 // Result is in temp[degree], stored in homogeneous coordinates
250 // Project back to Cartesian by dividing by weight
251 double x = tempX[mDegree];
252 double y = tempY[mDegree];
253 double z = tempZ[mDegree];
254 double m = tempM[mDegree];
255 const double w = tempW[mDegree];
256
257 if ( !qgsDoubleNear( w, 0.0 ) && !qgsDoubleNear( w, 1.0 ) )
258 {
259 x /= w;
260 y /= w;
261 if ( hasZ )
262 z /= w;
263 // M is not divided by weight (it's not in homogeneous space)
264 }
265
266 // Create point with appropriate dimensionality
267 if ( hasZ && hasM )
268 return QgsPoint( x, y, z, m );
269 else if ( hasZ )
270 return QgsPoint( x, y, z );
271 else if ( hasM )
272 return QgsPoint( x, y, std::numeric_limits<double>::quiet_NaN(), m );
273 else
274 return QgsPoint( x, y );
275}
276
278{
279 if ( mControlPoints.size() < 2 )
280 return false;
281
282 // Check if curve endpoints are the same by evaluating at t=0 and t=1
283 const QgsPoint startPt = evaluate( 0.0 );
284 const QgsPoint endPt = evaluate( 1.0 );
285
286 bool closed = qgsDoubleNear( startPt.x(), endPt.x() ) && qgsDoubleNear( startPt.y(), endPt.y() );
287
288 if ( is3D() && closed )
289 closed &= qgsDoubleNear( startPt.z(), endPt.z() ) || ( std::isnan( startPt.z() ) && std::isnan( endPt.z() ) );
290
291 return closed;
292}
293
295{
296 if ( mControlPoints.size() < 2 )
297 return false;
298
299 // Check if curve endpoints are the same in 2D
300 const QgsPoint startPt = evaluate( 0.0 );
301 const QgsPoint endPt = evaluate( 1.0 );
302
303 return qgsDoubleNear( startPt.x(), endPt.x() ) && qgsDoubleNear( startPt.y(), endPt.y() );
304}
305
307{
308 Q_UNUSED( toleranceType );
309
310 // Determine number of segments based on tolerance (angular approximation)
311 // For NURBS curves, we use uniform parameterization as a first approximation
312 const int steps = std::max( 2, static_cast<int>( 2 * M_PI / tolerance ) );
313
314 auto line = new QgsLineString();
315 for ( int i = 0; i <= steps; ++i )
316 {
317 const double t = static_cast<double>( i ) / steps;
318 const QgsPoint pt = evaluate( t );
319 line->addVertex( pt );
320 }
321
322 return line;
323}
324
325void QgsNurbsCurve::draw( QPainter &p ) const
326{
327 std::unique_ptr<QgsLineString> line( curveToLine() );
328 if ( line )
329 line->draw( p );
330}
331
332void QgsNurbsCurve::drawAsPolygon( QPainter &p ) const
333{
334 std::unique_ptr<QgsLineString> line( curveToLine() );
335 if ( line )
336 line->drawAsPolygon( p );
337}
338
340{
341 std::unique_ptr<QgsLineString> line( curveToLine() );
342 return line ? line->asQPolygonF() : QPolygonF();
343}
344
346{
347 return mControlPoints.isEmpty() ? QgsPoint() : mControlPoints.last();
348}
349
350bool QgsNurbsCurve::equals( const QgsCurve &other ) const
351{
352 if ( geometryType() != other.geometryType() )
353 {
354 return false;
355 }
356
358 if ( !o )
359 return false;
360
361 if ( o->mDegree != mDegree )
362 {
363 return false;
364 }
365
366 if ( mControlPoints != o->mControlPoints )
367 return false;
368
369 if ( mWeights != o->mWeights )
370 return false;
371
372 if ( mKnots != o->mKnots )
373 return false;
374
375 return true;
376}
377
378int QgsNurbsCurve::indexOf( const QgsPoint &point ) const
379{
380 for ( int i = 0; i < mControlPoints.size(); ++i )
381 {
382 if ( qgsDoubleNear( mControlPoints[i].distance( point ), 0.0 ) )
383 {
384 return i;
385 }
386 }
387 return -1;
388}
389
391{
392 std::unique_ptr<QgsLineString> line( curveToLine() );
393 if ( !line )
394 {
395 return nullptr;
396 }
397 return line->interpolatePoint( distance );
398}
399
401{
402 return mControlPoints.size();
403}
404
405bool QgsNurbsCurve::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
406{
407 if ( node < 0 || node >= mControlPoints.size() )
408 {
409 return false;
410 }
411 point = mControlPoints[node];
413 return true;
414}
415
417{
418 pts.reserve( pts.size() + mControlPoints.size() );
419 for ( const QgsPoint &p : mControlPoints )
420 {
421 pts.append( p );
422 }
423}
424
426{
427 auto rev = new QgsNurbsCurve( *this );
428 std::reverse( rev->mControlPoints.begin(), rev->mControlPoints.end() );
429 std::reverse( rev->mWeights.begin(), rev->mWeights.end() );
430
431 // Reverse and remap knot vector: new_knot[i] = max_knot + min_knot - old_knot[n-1-i]
432 if ( !rev->mKnots.isEmpty() )
433 {
434 const double maxKnot = rev->mKnots.last();
435 const double minKnot = rev->mKnots.first();
436 std::reverse( rev->mKnots.begin(), rev->mKnots.end() );
437 for ( double &knot : rev->mKnots )
438 {
439 knot = maxKnot + minKnot - knot;
440 }
441 }
442
443 return rev;
444}
445
446void QgsNurbsCurve::scroll( int firstVertexIndex )
447{
448 // Scrolling only makes sense for closed curves
449 if ( !isClosed() || firstVertexIndex <= 0 || firstVertexIndex >= mControlPoints.size() )
450 {
451 return;
452 }
453
454 // Rotate control points and weights
455 std::rotate( mControlPoints.begin(), mControlPoints.begin() + firstVertexIndex, mControlPoints.end() );
456 std::rotate( mWeights.begin(), mWeights.begin() + firstVertexIndex, mWeights.end() );
457
458 // Rotate knot vector and adjust values to preserve parameter domain
459 if ( !mKnots.isEmpty() && firstVertexIndex < mKnots.size() )
460 {
461 const double delta = mKnots[firstVertexIndex] - mKnots[0];
462 std::rotate( mKnots.begin(), mKnots.begin() + firstVertexIndex, mKnots.end() );
463 // Shift all knot values by -delta to preserve the start parameter
464 for ( double &knot : mKnots )
465 {
466 knot -= delta;
467 }
468 }
469
470 clearCache();
471}
472
473std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve>>
475{
476 std::unique_ptr<QgsLineString> line( curveToLine() );
477 if ( !line )
478 {
479 return std::make_tuple( nullptr, nullptr );
480 }
481 return line->splitCurveAtVertex( index );
482}
483
485{
486 return mControlPoints.isEmpty() ? QgsPoint() : mControlPoints.first();
487}
488
489void QgsNurbsCurve::sumUpArea( double &sum ) const
490{
491 // TODO - investigate whether this can be calculated directly
492 std::unique_ptr<QgsLineString> line( curveToLine() );
493 if ( line )
494 line->sumUpArea( sum );
495}
496
497void QgsNurbsCurve::sumUpArea3D( double &sum ) const
498{
499 std::unique_ptr<QgsLineString> line( curveToLine() );
500 if ( line )
501 line->sumUpArea3D( sum );
502}
503
504double QgsNurbsCurve::xAt( int index ) const
505{
506 if ( index < 0 || index >= mControlPoints.size() )
507 return 0.0;
508 return mControlPoints[index].x();
509}
510
511double QgsNurbsCurve::yAt( int index ) const
512{
513 if ( index < 0 || index >= mControlPoints.size() )
514 return 0.0;
515 return mControlPoints[index].y();
516}
517
518double QgsNurbsCurve::zAt( int index ) const
519{
520 if ( index < 0 || index >= mControlPoints.size() )
521 return 0.0;
522 return mControlPoints[index].is3D() ? mControlPoints[index].z() : std::numeric_limits<double>::quiet_NaN();
523}
524
525double QgsNurbsCurve::mAt( int index ) const
526{
527 if ( index < 0 || index >= mControlPoints.size() )
528 return 0.0;
529 return mControlPoints[index].isMeasure() ? mControlPoints[index].m() : std::numeric_limits<double>::quiet_NaN();
530}
531
532bool QgsNurbsCurve::addZValue( double zValue )
533{
535 return false;
536
537 clearCache();
539
540 for ( QgsPoint &p : mControlPoints )
541 {
542 p.addZValue( zValue );
543 }
544
545 return true;
546}
547
548bool QgsNurbsCurve::addMValue( double mValue )
549{
551 return false;
552
553 clearCache();
555
556 for ( QgsPoint &p : mControlPoints )
557 {
558 p.addMValue( mValue );
559 }
560
561 return true;
562}
563
565{
566 if ( !is3D() )
567 return false;
568
569 for ( QgsPoint &p : mControlPoints )
570 {
571 p.setZ( std::numeric_limits<double>::quiet_NaN() );
572 }
573
575 clearCache();
576 return true;
577}
578
580{
581 if ( !isMeasure() )
582 return false;
583
584 for ( QgsPoint &p : mControlPoints )
585 {
586 p.setM( std::numeric_limits<double>::quiet_NaN() );
587 }
588
590 clearCache();
591 return true;
592}
593
595{
596 if ( position.part != 0 || position.ring != 0 )
597 {
598 return false;
599 }
600 const int idx = position.vertex;
601 if ( idx < 0 || idx >= mControlPoints.size() )
602 {
603 return false;
604 }
605 mControlPoints.remove( idx );
606 if ( idx < mWeights.size() )
607 {
608 mWeights.remove( idx );
609 }
610
612
613 clearCache();
614 return true;
615}
616
617void QgsNurbsCurve::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
618{
619 QVector<QgsPoint> newPts;
620 QVector<double> newWeights;
621 for ( int i = 0; i < mControlPoints.size(); ++i )
622 {
623 if ( filter( mControlPoints[i] ) )
624 {
625 newPts.append( mControlPoints[i] );
626 if ( i < mWeights.size() )
627 newWeights.append( mWeights[i] );
628 }
629 }
630 mControlPoints = newPts;
631 mWeights = newWeights;
632
634
635 clearCache();
636}
637
638QVector<double> QgsNurbsCurve::generateUniformKnots( int numControlPoints, int degree )
639{
640 Q_ASSERT( numControlPoints > degree );
641
642 const int knotsSize = numControlPoints + degree + 1;
643 QVector<double> knots;
644 knots.reserve( knotsSize );
645 for ( int i = 0; i < knotsSize; ++i )
646 {
647 if ( i <= degree )
648 knots.append( 0.0 );
649 else if ( i >= numControlPoints )
650 knots.append( 1.0 );
651 else
652 knots.append( static_cast<double>( i - degree ) / ( numControlPoints - degree ) );
653 }
654 return knots;
655}
656
658{
659 mKnots = generateUniformKnots( mControlPoints.size(), mDegree );
660}
661
663{
664 clear();
665
666 if ( !wkb )
667 return false;
668
669 // Store header endianness
670 const unsigned char headerEndianness = *static_cast<const unsigned char *>( wkb );
671
672 Qgis::WkbType type = wkb.readHeader();
673 if ( !QgsWkbTypes::isNurbsType( type ) )
674 return false;
675
676 mWkbType = type;
677 const bool is3D = QgsWkbTypes::hasZ( type );
678 const bool isMeasure = QgsWkbTypes::hasM( type );
679
680 // Read degree (4 bytes uint32)
681 quint32 degree;
682 wkb >> degree;
683
684 // Validate degree before casting to int
685 if ( degree < 1 || degree > static_cast<quint32>( std::numeric_limits<int>::max() ) )
686 return false;
687
688 mDegree = static_cast<int>( degree );
689
690 // Read number of control points (4 bytes uint32)
691 quint32 numControlPoints;
692 wkb >> numControlPoints;
693
694 // Sanity check: numControlPoints should be reasonable given the WKB blob size
695 // Each control point needs at least:
696 // - 1 byte (endianness)
697 // - 16 bytes (x,y)
698 // - 8 bytes (z) if 3D
699 // - 8 bytes (m) if measure
700 // - 1 byte (weight flag)
701 // Minimum: 18 bytes (2D) to 34 bytes (ZM)
702 const int minBytesPerPoint = 18 + ( is3D ? 8 : 0 ) + ( isMeasure ? 8 : 0 );
703 if ( numControlPoints > static_cast<quint32>( wkb.remaining() / minBytesPerPoint + 1 ) )
704 return false;
705
706 mControlPoints.clear();
707 mWeights.clear();
708 mControlPoints.reserve( numControlPoints );
709 mWeights.reserve( numControlPoints );
710
711 // Read control points
712 for ( quint32 i = 0; i < numControlPoints; ++i )
713 {
714 // Read byte order for this point (1 byte)
715 char pointEndianness;
716 wkb >> pointEndianness;
717
718 // Validate endianness: must be 0 (big-endian) or 1 (little-endian)
719 // and must match the WKB header endianness
720 if ( static_cast<unsigned char>( pointEndianness ) != headerEndianness )
721 return false;
722
723 // Read coordinates
724 double x, y, z = 0.0, m = 0.0;
725 wkb >> x >> y;
726
727 if ( is3D )
728 wkb >> z;
729 if ( isMeasure )
730 wkb >> m;
731
732 // Read weight flag (1 byte)
733 char weightFlag;
734 wkb >> weightFlag;
735
736 double weight = 1.0;
737 if ( weightFlag == 1 )
738 {
739 // Read custom weight (8 bytes double)
740 wkb >> weight;
741 }
742
743 // Create point with appropriate dimensionality
744 QgsPoint point;
745 if ( is3D && isMeasure )
746 point = QgsPoint( x, y, z, m );
747 else if ( is3D )
748 point = QgsPoint( x, y, z );
749 else if ( isMeasure )
750 point = QgsPoint( x, y, std::numeric_limits<double>::quiet_NaN(), m );
751 else
752 point = QgsPoint( x, y );
753
754 mControlPoints.append( point );
755 mWeights.append( weight );
756 }
757
758 // Read number of knots (4 bytes uint32)
759 quint32 numKnots;
760 wkb >> numKnots;
761
762 // Sanity check: numKnots should be numControlPoints + degree + 1
763 const quint32 expectedKnots = numControlPoints + degree + 1;
764 if ( numKnots != expectedKnots )
765 return false;
766
767 // Sanity check: remaining WKB should have enough bytes for knots
768 if ( numKnots * sizeof( double ) > static_cast<quint32>( wkb.remaining() ) )
769 return false;
770
771 mKnots.clear();
772 mKnots.reserve( numKnots );
773
774 // Read knot values (8 bytes double each)
775 for ( quint32 i = 0; i < numKnots; ++i )
776 {
777 double knot;
778 wkb >> knot;
779 mKnots.append( knot );
780 }
781
782 return true;
783}
784
785bool QgsNurbsCurve::fromWkt( const QString &wkt )
786{
787 clear();
788
789 const QString geomTypeStr = wkt.split( '(' )[0].trimmed().toUpper();
790
791 if ( !geomTypeStr.startsWith( "NURBSCURVE"_L1 ) )
792 {
793 return false;
794 }
795
796 // Determine dimensionality from the geometry type string
797 // Handle both "NURBSCURVEZM" and "NURBSCURVE ZM" formats
798 if ( geomTypeStr.contains( "ZM"_L1 ) )
800 else if ( geomTypeStr.endsWith( 'Z' ) || geomTypeStr.endsWith( " Z"_L1 ) )
802 else if ( geomTypeStr.endsWith( 'M' ) || geomTypeStr.endsWith( " M"_L1 ) )
804 else
806
807 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
808
809 if ( parts.second.compare( "EMPTY"_L1, Qt::CaseInsensitive ) == 0 || parts.second.isEmpty() )
810 return true;
811
812 // Split the content by commas at parentheses level 0
813 QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, QString() );
814
815 if ( blocks.isEmpty() )
816 return false;
817
818 // First block should be the degree
819 bool ok = true;
820 int degree = blocks[0].trimmed().toInt( &ok );
821 if ( !ok || degree < 1 )
822 return false;
823
824 if ( blocks.size() < 2 )
825 return false;
826
827 // Second block should be the control points
828 QString pointsStr = blocks[1].trimmed();
829
830 // Validate control points block starts with '(' and ends with ')'
831 if ( !pointsStr.startsWith( '('_L1 ) || !pointsStr.endsWith( ')'_L1 ) )
832 return false;
833
834 pointsStr = pointsStr.mid( 1, pointsStr.length() - 2 ).trimmed();
835
836 // Parse control points
837 QStringList pointsCoords = pointsStr.split( ',', Qt::SkipEmptyParts );
838 QVector<QgsPoint> controlPoints;
839
840 const thread_local QRegularExpression rx( u"\\s+"_s );
841
842 for ( const QString &pointStr : pointsCoords )
843 {
844 QStringList coords = pointStr.trimmed().split( rx, Qt::SkipEmptyParts );
845
846 if ( coords.size() < 2 )
847 return false;
848
849 QgsPoint point;
850 bool ok = true;
851
852 double x = coords[0].toDouble( &ok );
853 if ( !ok )
854 return false;
855
856 double y = coords[1].toDouble( &ok );
857 if ( !ok )
858 return false;
859
860 // Handle different coordinate patterns based on declared geometry type
861 if ( coords.size() >= 3 )
862 {
863 if ( isMeasure() && !is3D() && coords.size() == 3 )
864 {
865 // NURBSCURVE M pattern: (x y m) - third coordinate is M, not Z
866 double m = coords[2].toDouble( &ok );
867 if ( !ok )
868 return false;
869 point = QgsPoint( x, y, std::numeric_limits<double>::quiet_NaN(), m );
870 }
871 else if ( is3D() && !isMeasure() && coords.size() >= 3 )
872 {
873 // NURBSCURVE Z pattern: (x y z)
874 double z = coords[2].toDouble( &ok );
875 if ( !ok )
876 return false;
877 point = QgsPoint( x, y, z );
878 }
879 else if ( is3D() && isMeasure() && coords.size() >= 4 )
880 {
881 // NURBSCURVE ZM pattern: (x y z m)
882 double z = coords[2].toDouble( &ok );
883 if ( !ok )
884 return false;
885 double m = coords[3].toDouble( &ok );
886 if ( !ok )
887 return false;
888 point = QgsPoint( x, y, z, m );
889 }
890 else if ( isMeasure() && coords.size() >= 4 )
891 {
892 // NURBSCURVE M pattern with 4 coords: (x y z m) - upgrade to ZM
893 double z = coords[2].toDouble( &ok );
894 if ( !ok )
895 return false;
896 double m = coords[3].toDouble( &ok );
897 if ( !ok )
898 return false;
899 point = QgsPoint( x, y, z, m );
900 if ( !is3D() )
902 }
903 else if ( !is3D() && !isMeasure() && coords.size() == 3 )
904 {
905 // No explicit dimension - auto-upgrade to 3D: (x y z)
906 double z = coords[2].toDouble( &ok );
907 if ( !ok )
908 return false;
909 point = QgsPoint( x, y, z );
911 }
912 else if ( !is3D() && !isMeasure() && coords.size() >= 4 )
913 {
914 // No explicit dimension - auto-upgrade to ZM: (x y z m)
915 double z = coords[2].toDouble( &ok );
916 if ( !ok )
917 return false;
918 double m = coords[3].toDouble( &ok );
919 if ( !ok )
920 return false;
921 point = QgsPoint( x, y, z, m );
924 }
925 else
926 {
927 // Only 2 coordinates but declared type may require Z or M
928 if ( is3D() && isMeasure() )
929 point = QgsPoint( Qgis::WkbType::PointZM, x, y, 0.0, 0.0 );
930 else if ( is3D() )
931 point = QgsPoint( Qgis::WkbType::PointZ, x, y, 0.0 );
932 else if ( isMeasure() )
933 point = QgsPoint( Qgis::WkbType::PointM, x, y, std::numeric_limits<double>::quiet_NaN(), 0.0 );
934 else
935 point = QgsPoint( x, y );
936 }
937 }
938 else
939 {
940 // Only 2 coordinates - create point matching declared type
941 if ( is3D() && isMeasure() )
942 point = QgsPoint( Qgis::WkbType::PointZM, x, y, 0.0, 0.0 );
943 else if ( is3D() )
944 point = QgsPoint( Qgis::WkbType::PointZ, x, y, 0.0 );
945 else if ( isMeasure() )
946 point = QgsPoint( Qgis::WkbType::PointM, x, y, std::numeric_limits<double>::quiet_NaN(), 0.0 );
947 else
948 point = QgsPoint( x, y );
949 }
950
951 controlPoints.append( point );
952 }
953
954 mControlPoints = controlPoints;
955
956 // Initialize weights to 1.0 (non-rational by default)
957 mWeights.clear();
958 for ( int i = 0; i < controlPoints.size(); ++i )
959 {
960 mWeights.append( 1.0 );
961 }
962
963 // Parse additional parameters (degree already parsed at the beginning)
964 bool hasWeights = false;
965 bool hasKnots = false;
966
967 // Process remaining blocks (starting from index 2 since 0=degree, 1=control points)
968 for ( int i = 2; i < blocks.size(); ++i )
969 {
970 QString block = blocks[i].trimmed();
971
972 if ( block.startsWith( '('_L1 ) )
973 {
974 // Validate block ends with ')'
975 if ( !block.endsWith( ')'_L1 ) )
976 return false;
977
978 // This could be weights or knots vector
979 block = block.mid( 1, block.length() - 2 ).trimmed();
980 QStringList values = block.split( ',', Qt::SkipEmptyParts );
981
982 QVector<double> parsedValues;
983 for ( const QString &valueStr : values )
984 {
985 bool ok = true;
986 double value = valueStr.trimmed().toDouble( &ok );
987 if ( !ok )
988 return false;
989 parsedValues.append( value );
990 }
991
992 if ( !hasWeights && parsedValues.size() == controlPoints.size() )
993 {
994 // This is the weights vector
995 mWeights = parsedValues;
996 hasWeights = true;
997 }
998 else if ( !hasKnots )
999 {
1000 // This is the knots vector
1001 mKnots = parsedValues;
1002 hasKnots = true;
1003 }
1004 }
1005 else
1006 {
1007 // Invalid block - doesn't start with '('
1008 return false;
1009 }
1010 }
1011
1012 mDegree = degree;
1013
1014 // Validate: need at least (degree + 1) control points
1015 if ( controlPoints.size() <= degree )
1016 return false;
1017
1018 // If no knots were provided, create default knots (open uniform)
1019 if ( !hasKnots )
1020 {
1022 }
1023
1024 return true;
1025}
1026
1027bool QgsNurbsCurve::fuzzyEqual( const QgsAbstractGeometry &other, double epsilon ) const
1028{
1030 if ( !o )
1031 return false;
1032
1033 if ( mDegree != o->mDegree || mControlPoints.size() != o->mControlPoints.size() || mWeights.size() != o->mWeights.size() || mKnots.size() != o->mKnots.size() )
1034 {
1035 return false;
1036 }
1037
1038 for ( int i = 0; i < mControlPoints.size(); ++i )
1039 {
1040 if ( mControlPoints[i].distance( o->mControlPoints[i] ) >= epsilon )
1041 return false;
1042 }
1043
1044 for ( int i = 0; i < mWeights.size(); ++i )
1045 {
1046 if ( std::fabs( mWeights[i] - o->mWeights[i] ) > epsilon )
1047 return false;
1048 }
1049
1050 for ( int i = 0; i < mKnots.size(); ++i )
1051 {
1052 if ( std::fabs( mKnots[i] - o->mKnots[i] ) > epsilon )
1053 return false;
1054 }
1055
1056 return true;
1057}
1058
1059bool QgsNurbsCurve::fuzzyDistanceEqual( const QgsAbstractGeometry &other, double epsilon ) const
1060{
1061 return fuzzyEqual( other, epsilon );
1062}
1063
1065{
1066 return u"NurbsCurve"_s;
1067}
1068
1070{
1071 return true;
1072}
1073
1075{
1076 return 1;
1077}
1078
1080{
1081 return new QgsNurbsCurve( *this );
1082}
1083
1085{
1086 if ( id.part != 0 || id.ring != 0 )
1087 {
1088 return QgsPoint();
1089 }
1090 const int idx = id.vertex;
1091 if ( idx < 0 || idx >= mControlPoints.size() )
1092 {
1093 return QgsPoint();
1094 }
1095 return mControlPoints[idx];
1096}
1097
1098int QgsNurbsCurve::vertexCount( int part, int ring ) const
1099{
1100 return ( part == 0 && ring == 0 ) ? mControlPoints.size() : 0;
1101}
1102
1104{
1105 if ( id.part == 0 && id.ring == 0 )
1106 {
1107 return id.vertex;
1108 }
1109 return -1;
1110}
1111
1112bool QgsNurbsCurve::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
1113{
1114 Q_UNUSED( flags );
1115
1116 // Use cached validity if available
1117 if ( mValidityComputed )
1118 {
1119 if ( !mIsValid )
1120 error = u"NURBS curve is invalid"_s;
1121 return mIsValid;
1122 }
1123
1124 mValidityComputed = true;
1125 mIsValid = false;
1126
1127 if ( mDegree < 1 )
1128 {
1129 error = u"Degree must be >= 1"_s;
1130 return false;
1131 }
1132
1133 const int n = mControlPoints.size();
1134 if ( n < mDegree + 1 )
1135 {
1136 error = u"Not enough control points for degree"_s;
1137 return false;
1138 }
1139
1140 if ( mKnots.size() != n + mDegree + 1 )
1141 {
1142 error = u"Knot vector size is incorrect"_s;
1143 return false;
1144 }
1145
1146 if ( mWeights.size() != n )
1147 {
1148 error = u"Weights vector size mismatch"_s;
1149 return false;
1150 }
1151
1152 // Check that knots are non-decreasing
1153 for ( int i = 1; i < mKnots.size(); ++i )
1154 {
1155 if ( mKnots[i] < mKnots[i - 1] )
1156 {
1157 error = u"Knot vector values must be non-decreasing"_s;
1158 return false;
1159 }
1160 }
1161
1162 mIsValid = true;
1163 return true;
1164}
1165
1166void QgsNurbsCurve::addToPainterPath( QPainterPath &path ) const
1167{
1168 std::unique_ptr<QgsLineString> line( curveToLine() );
1169 if ( line )
1170 line->addToPainterPath( path );
1171}
1172
1173QgsCurve *QgsNurbsCurve::curveSubstring( double startDistance, double endDistance ) const
1174{
1175 std::unique_ptr<QgsLineString> line( curveToLine() );
1176 if ( !line )
1177 return nullptr;
1178 return line->curveSubstring( startDistance, endDistance );
1179}
1180
1182{
1183 std::unique_ptr<QgsLineString> line( curveToLine() );
1184 return line ? line->length() : 0.0;
1185}
1186
1188{
1189 std::unique_ptr<QgsLineString> line( curveToLine() );
1190 if ( !line )
1191 return 0.0;
1192 return line->segmentLength( startVertex );
1193}
1194
1196{
1197 std::unique_ptr<QgsLineString> line( curveToLine() );
1198 if ( !line )
1199 return -1.0;
1200 return line->distanceBetweenVertices( fromVertex, toVertex );
1201}
1202
1203QgsAbstractGeometry *QgsNurbsCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
1204{
1205 auto result = new QgsNurbsCurve( *this );
1206 for ( QgsPoint &pt : result->mControlPoints )
1207 {
1208 if ( hSpacing > 0 )
1209 pt.setX( std::round( pt.x() / hSpacing ) * hSpacing );
1210 if ( vSpacing > 0 )
1211 pt.setY( std::round( pt.y() / vSpacing ) * vSpacing );
1212 if ( pt.is3D() && dSpacing > 0 )
1213 pt.setZ( std::round( pt.z() / dSpacing ) * dSpacing );
1214 if ( pt.isMeasure() && mSpacing > 0 )
1215 pt.setM( std::round( pt.m() / mSpacing ) * mSpacing );
1216 }
1217
1218 if ( removeRedundantPoints )
1219 result->removeDuplicateNodes();
1220
1221 return result;
1222}
1223
1225{
1226 std::unique_ptr<QgsLineString> line( curveToLine() );
1227 if ( !line )
1228 return new QgsNurbsCurve( *this );
1229 return line->simplifyByDistance( tolerance );
1230}
1231
1232bool QgsNurbsCurve::removeDuplicateNodes( double epsilon, bool useZValues )
1233{
1234 if ( mControlPoints.size() < 2 )
1235 return false;
1236
1237 QVector<QgsPoint> newPoints;
1238 QVector<double> newWeights;
1239
1240 newPoints.reserve( mControlPoints.size() );
1241 newWeights.reserve( mWeights.size() );
1242
1243 newPoints.append( mControlPoints.first() );
1244 if ( !mWeights.isEmpty() )
1245 newWeights.append( mWeights.first() );
1246
1247 for ( int i = 1; i < mControlPoints.size(); ++i )
1248 {
1249 const double dist = ( useZValues && mControlPoints[i].is3D() && mControlPoints[i - 1].is3D() )
1250 ? mControlPoints[i].distance3D( mControlPoints[i - 1] )
1251 : mControlPoints[i].distance( mControlPoints[i - 1] );
1252
1253 if ( dist >= epsilon )
1254 {
1255 newPoints.append( mControlPoints[i] );
1256 if ( i < mWeights.size() )
1257 newWeights.append( mWeights[i] );
1258 }
1259 }
1260
1261 const bool changed = ( newPoints.size() != mControlPoints.size() );
1262 if ( !changed )
1263 return false;
1264
1265 mControlPoints = newPoints;
1266 mWeights = newWeights;
1267
1268 // Regenerate uniform knot vector for the new number of control points
1270
1271 clearCache();
1272 return true;
1273}
1274
1276{
1277 std::unique_ptr<QgsLineString> line( curveToLine() );
1278 if ( !line )
1279 return 0.0;
1280 return line->vertexAngle( vertex );
1281}
1282
1284{
1285 for ( QgsPoint &pt : mControlPoints )
1286 {
1287 const double x = pt.x();
1288 pt.setX( pt.y() );
1289 pt.setY( x );
1290 }
1291 clearCache();
1292}
1293
1295{
1296 Q_UNUSED( feedback );
1297 if ( !transformer )
1298 return false;
1299
1300 for ( QgsPoint &pt : mControlPoints )
1301 {
1302 double x = pt.x(), y = pt.y(), z = pt.z(), m = pt.m();
1303 if ( !transformer->transformPoint( x, y, z, m ) )
1304 return false;
1305 pt.setX( x );
1306 pt.setY( y );
1307 pt.setZ( z );
1308 pt.setM( m );
1309 }
1310
1311 clearCache();
1312 return true;
1313}
1314
1319
1320double QgsNurbsCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1321{
1322 std::unique_ptr<QgsLineString> line( curveToLine() );
1323 if ( !line )
1324 {
1325 segmentPt = QgsPoint();
1326 vertexAfter = QgsVertexId();
1327 if ( leftOf )
1328 *leftOf = 0;
1329 return -1;
1330 }
1331 return line->closestSegment( pt, segmentPt, vertexAfter, leftOf, epsilon );
1332}
1333
1335{
1336 for ( QgsPoint &pt : mControlPoints )
1337 {
1338 double x = pt.x();
1339 double y = pt.y();
1340 double z = transformZ && pt.is3D() ? pt.z() : std::numeric_limits<double>::quiet_NaN();
1341 ct.transformInPlace( x, y, z, d );
1342 pt.setX( x );
1343 pt.setY( y );
1344 if ( transformZ && pt.is3D() )
1345 pt.setZ( z );
1346 }
1347 clearCache();
1348}
1349
1350void QgsNurbsCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1351{
1352 for ( QgsPoint &pt : mControlPoints )
1353 {
1354 const QPointF p = t.map( QPointF( pt.x(), pt.y() ) );
1355 pt.setX( p.x() );
1356 pt.setY( p.y() );
1357
1358 if ( pt.is3D() )
1359 pt.setZ( pt.z() * zScale + zTranslate );
1360 if ( pt.isMeasure() )
1361 pt.setM( pt.m() * mScale + mTranslate );
1362 }
1363 clearCache();
1364}
1365
1370
1372{
1373 if ( mBoundingBox.isNull() )
1374 {
1376 }
1377 return mBoundingBox;
1378}
1379
1381{
1382 if ( mControlPoints.isEmpty() )
1383 return QgsBox3D();
1384
1385 // The bounding box must include all control points, not just points on the curve.
1386 // This is important for Snapping to control points (they can lie outside the curve itself)
1387 QgsBox3D bbox;
1388 for ( const QgsPoint &pt : mControlPoints )
1389 {
1390 bbox.combineWith( pt.x(), pt.y(), pt.is3D() ? pt.z() : std::numeric_limits<double>::quiet_NaN() );
1391 }
1392
1393 // Also include points on the curve to ensure the bbox is complete
1394 std::unique_ptr<QgsLineString> line( curveToLine() );
1395 if ( line )
1396 {
1397 bbox.combineWith( line->boundingBox3D() );
1398 }
1399
1400 return bbox;
1401}
1402
1404{
1406 mValidityComputed = false;
1407 mIsValid = false;
1408}
1409
1410bool QgsNurbsCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
1411{
1412 if ( position.part != 0 || position.ring != 0 )
1413 return false;
1414
1415 const int idx = position.vertex;
1416 if ( idx < 0 || idx >= mControlPoints.size() )
1417 return false;
1418
1419 mControlPoints[idx] = newPos;
1420 clearCache();
1421 return true;
1422}
1423
1424bool QgsNurbsCurve::insertVertex( QgsVertexId position, const QgsPoint &vertex )
1425{
1426 if ( position.part != 0 || position.ring != 0 )
1427 return false;
1428
1429 const int idx = position.vertex;
1430 if ( idx < 0 || idx > mControlPoints.size() )
1431 return false;
1432
1433 mControlPoints.insert( idx, vertex );
1434 if ( idx <= mWeights.size() )
1435 mWeights.insert( idx, 1.0 );
1436
1438
1439 clearCache();
1440 return true;
1441}
1442
1444{
1445 Q_UNUSED( flags );
1446
1447 const bool is3D = QgsWkbTypes::hasZ( mWkbType );
1448 const bool isMeasure = QgsWkbTypes::hasM( mWkbType );
1449 const int coordinateDimension = 2 + ( is3D ? 1 : 0 ) + ( isMeasure ? 1 : 0 );
1450
1451 int size = 0;
1452
1453 // WKB header (endianness + type)
1454 size += 1 + 4;
1455
1456 // Degree (4 bytes)
1457 size += 4;
1458
1459 // Number of control points (4 bytes)
1460 size += 4;
1461
1462 // Control points data
1463 for ( int i = 0; i < mControlPoints.size(); ++i )
1464 {
1465 // Point byte order (1 byte)
1466 size += 1;
1467
1468 // Coordinates (8 bytes per coordinate)
1469 size += coordinateDimension * 8;
1470
1471 // Weight flag (1 byte)
1472 size += 1;
1473
1474 // Weight value if not default (8 bytes)
1475 if ( i < mWeights.size() && std::fabs( mWeights[i] - 1.0 ) > 1e-10 )
1476 size += 8;
1477 }
1478
1479 // Number of knots (4 bytes)
1480 size += 4;
1481
1482 // Knot values (8 bytes each)
1483 size += mKnots.size() * 8;
1484
1485 return size;
1486}
1487
1489{
1490 QByteArray wkbArray;
1491 wkbArray.resize( QgsNurbsCurve::wkbSize( flags ) );
1492 QgsWkbPtr wkbPtr( wkbArray );
1493
1494 // Write WKB header
1495 wkbPtr << static_cast<char>( QgsApplication::endian() );
1496 wkbPtr << static_cast<quint32>( mWkbType );
1497
1498 // Write degree (4 bytes uint32)
1499 wkbPtr << static_cast<quint32>( mDegree );
1500
1501 // Write number of control points (4 bytes uint32)
1502 wkbPtr << static_cast<quint32>( mControlPoints.size() );
1503
1504 const bool is3D = QgsWkbTypes::hasZ( mWkbType );
1505 const bool isMeasure = QgsWkbTypes::hasM( mWkbType );
1506
1507 // Write control points
1508 for ( int i = 0; i < mControlPoints.size(); ++i )
1509 {
1510 const QgsPoint &point = mControlPoints[i];
1511
1512 // Write byte order for this point (1 byte) - use same as global
1513 wkbPtr << static_cast<char>( QgsApplication::endian() );
1514
1515 // Write coordinates
1516 wkbPtr << point.x() << point.y();
1517
1518 if ( is3D )
1519 wkbPtr << point.z();
1520 if ( isMeasure )
1521 wkbPtr << point.m();
1522
1523 // Write weight flag and weight
1524 const double weight = ( i < mWeights.size() ) ? mWeights[i] : 1.0;
1525 const bool hasCustomWeight = std::fabs( weight - 1.0 ) > 1e-10;
1526
1527 wkbPtr << static_cast<char>( hasCustomWeight ? 1 : 0 );
1528
1529 if ( hasCustomWeight )
1530 {
1531 wkbPtr << weight;
1532 }
1533 }
1534
1535 // Write number of knots (4 bytes uint32)
1536 wkbPtr << static_cast<quint32>( mKnots.size() );
1537
1538 // Write knot values (8 bytes double each)
1539 for ( const double knot : mKnots )
1540 {
1541 wkbPtr << knot;
1542 }
1543
1544 return wkbArray;
1545}
1546
1547QString QgsNurbsCurve::asWkt( int precision ) const
1548{
1549 QString wkt = wktTypeStr();
1550
1551 if ( isEmpty() )
1552 {
1553 wkt += " EMPTY"_L1;
1554 }
1555 else
1556 {
1557 wkt += " ("_L1;
1558
1559 // Add degree first
1560 wkt += QString::number( mDegree );
1561
1562 // Add control points
1563 wkt += ", ("_L1;
1564 for ( int i = 0; i < mControlPoints.size(); ++i )
1565 {
1566 if ( i > 0 )
1567 wkt += ", "_L1;
1568
1569 const QgsPoint &pt = mControlPoints[i];
1570 wkt += qgsDoubleToString( pt.x(), precision ) + ' ' + qgsDoubleToString( pt.y(), precision );
1571
1572 if ( pt.is3D() )
1573 wkt += ' ' + qgsDoubleToString( pt.z(), precision );
1574
1575 if ( pt.isMeasure() )
1576 wkt += ' ' + qgsDoubleToString( pt.m(), precision );
1577 }
1578 wkt += ')';
1579
1580 // Always add weights if they exist to ensure round-trip consistency
1581 if ( !mWeights.isEmpty() )
1582 {
1583 wkt += ", ("_L1;
1584 for ( int i = 0; i < mWeights.size(); ++i )
1585 {
1586 if ( i > 0 )
1587 wkt += ", "_L1;
1588 wkt += qgsDoubleToString( mWeights[i], precision );
1589 }
1590 wkt += ')';
1591 }
1592
1593 // Always add knots if they exist to ensure round-trip consistency
1594 if ( !mKnots.isEmpty() )
1595 {
1596 wkt += ", ("_L1;
1597 for ( int i = 0; i < mKnots.size(); ++i )
1598 {
1599 if ( i > 0 )
1600 wkt += ", "_L1;
1601 wkt += qgsDoubleToString( mKnots[i], precision );
1602 }
1603 wkt += ')';
1604 }
1605
1606 wkt += ')';
1607 }
1608
1609 return wkt;
1610}
1611
1612QDomElement QgsNurbsCurve::asGml2( QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder ) const
1613{
1614 // GML2 does not support NURBS curves, convert to LineString
1615 // TODO: GML3 has BSpline support, but it's not clear how it's handled elsewhere in QGIS
1616 std::unique_ptr<QgsLineString> line( curveToLine() );
1617 if ( !line )
1618 return QDomElement();
1619 return line->asGml2( doc, precision, ns, axisOrder );
1620}
1621
1622QDomElement QgsNurbsCurve::asGml3( QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder ) const
1623{
1624 // TODO: GML3 has native BSpline support (gml:BSpline), but it's not clear how it's handled elsewhere in QGIS
1625 // For now, convert to LineString for compatibility
1626 std::unique_ptr<QgsLineString> line( curveToLine() );
1627 if ( !line )
1628 return QDomElement();
1629 return line->asGml3( doc, precision, ns, axisOrder );
1630}
1631
1632json QgsNurbsCurve::asJsonObject( int precision ) const
1633{
1634 std::unique_ptr<QgsLineString> line( curveToLine() );
1635 if ( !line )
1636 return json::object();
1637 return line->asJsonObject( precision );
1638}
1639
1640QString QgsNurbsCurve::asKml( int precision ) const
1641{
1642 // KML does not support NURBS curves, convert to LineString
1643 std::unique_ptr<QgsLineString> line( curveToLine() );
1644 if ( !line )
1645 return QString();
1646 return line->asKml( precision );
1647}
1648
1650{
1651 return 1;
1652}
1653
1655{
1656 return mControlPoints.isEmpty();
1657}
1658
1660{
1661 mControlPoints.clear();
1662 mKnots.clear();
1663 mWeights.clear();
1664 mDegree = 0;
1665 clearCache();
1666}
1667
1669{
1670 return boundingBox().intersects( rectangle );
1671}
1672
1674{
1675 return boundingBox3D().intersects( box3d );
1676}
1677
1679{
1680 std::unique_ptr<QgsLineString> line( curveToLine() );
1681 return line ? line->centroid() : QgsPoint();
1682}
1683
1685{
1686 const QgsNurbsCurve *otherCurve = qgsgeometry_cast<const QgsNurbsCurve *>( other );
1687 if ( !otherCurve )
1688 return -1;
1689
1690 if ( mDegree < otherCurve->mDegree )
1691 return -1;
1692 else if ( mDegree > otherCurve->mDegree )
1693 return 1;
1694
1695 const int nThis = mControlPoints.size();
1696 const int nOther = otherCurve->mControlPoints.size();
1697
1698 if ( nThis < nOther )
1699 return -1;
1700 else if ( nThis > nOther )
1701 return 1;
1702
1703 for ( int i = 0; i < nThis; ++i )
1704 {
1705 if ( mControlPoints[i].x() < otherCurve->mControlPoints[i].x() )
1706 return -1;
1707 if ( mControlPoints[i].x() > otherCurve->mControlPoints[i].x() )
1708 return 1;
1709 if ( mControlPoints[i].y() < otherCurve->mControlPoints[i].y() )
1710 return -1;
1711 if ( mControlPoints[i].y() > otherCurve->mControlPoints[i].y() )
1712 return 1;
1713 }
1714
1715 if ( mWeights.size() < otherCurve->mWeights.size() )
1716 return -1;
1717 else if ( mWeights.size() > otherCurve->mWeights.size() )
1718 return 1;
1719
1720 for ( int i = 0; i < mWeights.size(); ++i )
1721 {
1722 if ( mWeights[i] < otherCurve->mWeights[i] )
1723 return -1;
1724 else if ( mWeights[i] > otherCurve->mWeights[i] )
1725 return 1;
1726 }
1727
1728 if ( mKnots.size() < otherCurve->mKnots.size() )
1729 return -1;
1730 else if ( mKnots.size() > otherCurve->mKnots.size() )
1731 return 1;
1732
1733 for ( int i = 0; i < mKnots.size(); ++i )
1734 {
1735 if ( mKnots[i] < otherCurve->mKnots[i] )
1736 return -1;
1737 else if ( mKnots[i] > otherCurve->mKnots[i] )
1738 return 1;
1739 }
1740
1741 return 0;
1742}
1743
1744double QgsNurbsCurve::weight( int index ) const
1745{
1746 if ( index < 0 || index >= mWeights.size() )
1747 return 1.0;
1748 return mWeights[index];
1749}
1750
1751bool QgsNurbsCurve::setWeight( int index, double weight )
1752{
1753 if ( index < 0 || index >= mWeights.size() )
1754 return false;
1755 if ( weight <= 0.0 )
1756 return false;
1757 mWeights[index] = weight;
1758 clearCache();
1759 return true;
1760}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2133
VertexType
Types of vertex.
Definition qgis.h:3124
@ ControlPoint
A NURBS control point (does not lie on the curve).
Definition qgis.h:3127
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ NurbsCurveM
NurbsCurveM.
Definition qgis.h:330
@ NurbsCurve
NurbsCurve.
Definition qgis.h:297
@ NurbsCurveZ
NurbsCurveZ.
Definition qgis.h:314
@ PointM
PointM.
Definition qgis.h:315
@ NurbsCurveZM
NurbsCurveZM.
Definition qgis.h:346
@ PointZ
PointZ.
Definition qgis.h:299
@ PointZM
PointZM.
Definition qgis.h:331
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2729
An abstract base class for classes which transform geometries by transforming input points to output ...
virtual bool transformPoint(double &x, double &y, double &z, double &m)=0
Transforms the point defined by the coordinates (x, y, z) and the specified m value.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
bool isMeasure() const
Returns true if the geometry contains m values.
QFlags< WkbFlag > WkbFlags
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
virtual QString geometryType() const =0
Returns a unique string representing the geometry type.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
QgsAbstractGeometry()=default
QgsGeometryConstPartIterator parts() const
Returns Java-style iterator for traversal of parts of the geometry.
static endian_t endian()
Returns whether this machine uses big or little endian.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
bool intersects(const QgsBox3D &other) const
Returns true if box intersects with another box.
Definition qgsbox3d.cpp:150
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:381
void combineWith(const QgsBox3D &box)
Expands the bbox so that it covers both the original rectangle and the given rectangle.
Definition qgsbox3d.cpp:214
A const WKB pointer.
Definition qgswkbptr.h:139
int remaining() const
remaining
Definition qgswkbptr.h:196
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:60
Handles coordinate transforms between two coordinate systems.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition qgscurve.cpp:294
QgsBox3D mBoundingBox
Cached bounding box.
Definition qgscurve.h:399
QgsCurve()=default
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
static QStringList wktGetChildBlocks(const QString &wkt, const QString &defaultType=QString())
Parses a WKT string and returns of list of blocks contained in the WKT.
static QPair< Qgis::WkbType, QString > wktReadBlock(const QString &wkt)
Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents (...
Line string geometry type, with support for z-dimension and m-values.
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex(int index) const override
Splits the curve at the specified vertex index, returning two curves which represent the portion of t...
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
QgsPoint startPoint() const override
Returns the starting point of the curve.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
bool isRational() const
Returns true if this curve is rational (has non-uniform weights).
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
QgsNurbsCurve()
Constructor for an empty NURBS curve geometry.
QgsPoint endPoint() const override
Returns the end point of the curve.
int indexOf(const QgsPoint &point) const override
Returns the index of the first vertex matching the given point, or -1 if a matching vertex is not fou...
bool transform(QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback=nullptr) override
Transforms the vertices from the geometry in place, using the specified geometry transformer object.
QgsPoint centroid() const override
Returns the centroid of the geometry.
bool equals(const QgsCurve &other) const override
Checks whether this curve exactly equals another curve.
QgsCurve * curveSubstring(double startDistance, double endDistance) const override
Returns a new curve representing a substring of this curve.
int partCount() const override
Returns count of parts contained in the geometry.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
bool fuzzyDistanceEqual(const QgsAbstractGeometry &other, double epsilon=1e-8) const override
Performs fuzzy distance comparison between this geometry and other using an epsilon.
QgsAbstractGeometry * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
void sumUpArea(double &sum) const override
Sums up the area of the curve by iterating over the vertices (shoelace formula).
QString geometryType() const override
Returns a unique string representing the geometry type.
QString asKml(int precision=17) const override
Returns a KML representation of the geometry.
QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
bool dropMValue() override
Drops any measure values which exist in the geometry.
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
QVector< double > knots() const
Returns the knot vector of the NURBS curve.
void scroll(int firstVertexIndex) override
Scrolls the curve vertices so that they start with the vertex at the given index.
bool isEmpty() const override
Returns true if the geometry is empty.
bool isPolyBezier() const
Returns true if this curve represents a poly-Bézier curve.
void filterVertices(const std::function< bool(const QgsPoint &)> &filter) override
bool isBSpline() const
Returns true if this curve represents a B-spline (non-rational NURBS).
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
int numPoints() const override
Returns the number of points in the curve.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
QgsBox3D boundingBox3D() const override
Returns the 3D bounding box for the geometry.
bool isClosed() const override
Returns true if the curve is closed.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
QgsAbstractGeometry * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
int vertexNumberFromVertexId(QgsVertexId id) const override
Returns the vertex number corresponding to a vertex id.
void swapXy() override
Swaps the x and y coordinates from the geometry.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML2 representation of the geometry.
QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML3 representation of the geometry.
bool setWeight(int index, double weight)
Sets the weight at the specified control point index.
bool fuzzyEqual(const QgsAbstractGeometry &other, double epsilon=1e-8) const override
Performs fuzzy comparison between this geometry and other using an epsilon.
QgsCurve * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
double mAt(int index) const override
Returns the m-coordinate of the specified node in the line string.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
bool boundingBoxIntersects(const QgsRectangle &rectangle) const override
Returns true if the bounding box of this geometry intersects with a rectangle.
double weight(int index) const
Returns the weight at the specified control point index.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns a WKB representation of the geometry.
bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const override
Returns the point and vertex id of a point within the curve.
int degree() const
Returns the degree of the NURBS curve.
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const override
Searches for the closest segment of the geometry to a given point.
void sumUpArea3D(double &sum) const override
Sums up the 3d area of the curve by iterating over the vertices (shoelace formula).
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) override
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
static QVector< double > generateUniformKnots(int numControlPoints, int degree)
Generates a uniform clamped knot vector for a NURBS curve.
QVector< QgsPoint > controlPoints() const
Returns the control points of the NURBS curve.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
bool isBezier() const
Returns true if this curve represents a Bézier curve.
QgsRectangle boundingBox() const override
Returns the minimal bounding box for the geometry.
bool isClosed2D() const override
Returns true if the curve is closed.
QgsCurve * toCurveType() const override
Returns the geometry converted to the more generic curve type.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
int compareToSameClass(const QgsAbstractGeometry *other) const final
Compares to an other geometry of the same class, and returns a integer for sorting of the two geometr...
QgsPoint * interpolatePoint(double distance) const override
Returns an interpolated point on the curve at the specified distance.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
QgsPoint evaluate(double t) const
Evaluates the NURBS curve at parameter t ∈ [0,1].
int dimension() const override
Returns the inherent dimension of the geometry.
QVector< double > weights() const
Returns the weight vector of the NURBS curve.
QgsAbstractGeometry * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0, bool removeRedundantPoints=false) const override
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
QgsNurbsCurve * clone() const override
Clones the geometry by performing a deep copy.
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
double distanceBetweenVertices(QgsVertexId fromVertex, QgsVertexId toVertex) const override
Returns the distance along the curve between two vertices.
int vertexCount(int part=0, int ring=0) const override
Returns the number of vertices of which this geometry is built.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:341
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:330
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
void setM(double m)
Sets the point's m-value.
Definition qgspoint.h:369
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:742
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:354
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
A rectangle specified with double values.
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
WKB pointer handler.
Definition qgswkbptr.h:45
static Qgis::WkbType dropM(Qgis::WkbType type)
Drops the m dimension (if present) for a WKB type and returns the new type.
static Qgis::WkbType dropZ(Qgis::WkbType type)
Drops the z dimension (if present) for a WKB type and returns the new type.
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Q_INVOKABLE bool isNurbsType(Qgis::WkbType type)
Returns true if the WKB type is a NURBS curve type.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6817
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:34
int vertex
Vertex number.
Definition qgsvertexid.h:98
int part
Part number.
Definition qgsvertexid.h:92
int ring
Ring number.
Definition qgsvertexid.h:95