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