QGIS API Documentation 4.1.0-Master (31622b25bb0)
Loading...
Searching...
No Matches
qgslinestring.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslinestring.cpp
3 -------------------
4 begin : September 2014
5 copyright : (C) 2014 by Marco Hugentobler
6 email : marco at sourcepole dot ch
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 "qgslinestring.h"
19
20#include <cmath>
21#include <limits>
22#include <memory>
23#include <nlohmann/json.hpp>
24
25#include "qgsapplication.h"
26#include "qgsbox3d.h"
27#include "qgscompoundcurve.h"
29#include "qgsfeedback.h"
31#include "qgsgeometryutils.h"
33#include "qgslinesegment.h"
34#include "qgsvector3d.h"
35#include "qgswkbptr.h"
36
37#include <QDomDocument>
38#include <QJsonObject>
39#include <QPainter>
40#include <QString>
41
42using namespace Qt::StringLiterals;
43
44/***************************************************************************
45 * This class is considered CRITICAL and any change MUST be accompanied with
46 * full unit tests.
47 * See details in QEP #17
48 ****************************************************************************/
49
54
55QgsLineString::QgsLineString( const QVector<QgsPoint> &points )
56{
57 if ( points.isEmpty() )
58 {
60 return;
61 }
62 Qgis::WkbType ptType = points.at( 0 ).wkbType();
64 mX.resize( points.count() );
65 mY.resize( points.count() );
66 double *x = mX.data();
67 double *y = mY.data();
68 double *z = nullptr;
69 double *m = nullptr;
71 {
72 mZ.resize( points.count() );
73 z = mZ.data();
74 }
76 {
77 mM.resize( points.count() );
78 m = mM.data();
79 }
80
81 for ( const QgsPoint &pt : points )
82 {
83 *x++ = pt.x();
84 *y++ = pt.y();
85 if ( z )
86 *z++ = pt.z();
87 if ( m )
88 *m++ = pt.m();
89 }
90}
91
92QgsLineString::QgsLineString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m, bool is25DType )
93{
95 int pointCount = std::min( x.size(), y.size() );
96 if ( x.size() == pointCount )
97 {
98 mX = x;
99 }
100 else
101 {
102 mX = x.mid( 0, pointCount );
103 }
104 if ( y.size() == pointCount )
105 {
106 mY = y;
107 }
108 else
109 {
110 mY = y.mid( 0, pointCount );
111 }
112 if ( !z.isEmpty() && z.count() >= pointCount )
113 {
115 if ( z.size() == pointCount )
116 {
117 mZ = z;
118 }
119 else
120 {
121 mZ = z.mid( 0, pointCount );
122 }
123 }
124 if ( !m.isEmpty() && m.count() >= pointCount )
125 {
127 if ( m.size() == pointCount )
128 {
129 mM = m;
130 }
131 else
132 {
133 mM = m.mid( 0, pointCount );
134 }
135 }
136}
137
139{
141 mX.resize( 2 );
142 mX[0] = p1.x();
143 mX[1] = p2.x();
144 mY.resize( 2 );
145 mY[0] = p1.y();
146 mY[1] = p2.y();
147 if ( p1.is3D() )
148 {
150 mZ.resize( 2 );
151 mZ[0] = p1.z();
152 mZ[1] = p2.z();
153 }
154 if ( p1.isMeasure() )
155 {
157 mM.resize( 2 );
158 mM[0] = p1.m();
159 mM[1] = p2.m();
160 }
161}
162
163QgsLineString::QgsLineString( const QVector<QgsPointXY> &points )
164{
166 mX.reserve( points.size() );
167 mY.reserve( points.size() );
168 for ( const QgsPointXY &p : points )
169 {
170 mX << p.x();
171 mY << p.y();
172 }
173}
174
176{
178 mX.resize( 2 );
179 mY.resize( 2 );
180 mX[0] = segment.startX();
181 mX[1] = segment.endX();
182 mY[0] = segment.startY();
183 mY[1] = segment.endY();
184}
185
186std::unique_ptr< QgsLineString > QgsLineString::fromBezierCurve( const QgsPoint &start, const QgsPoint &controlPoint1, const QgsPoint &controlPoint2, const QgsPoint &end, int segments )
187{
188 if ( segments == 0 )
189 return std::make_unique< QgsLineString >();
190
191 QVector<double> x( segments + 1 );
192 QVector<double> y( segments + 1 );
193 QVector<double> z;
194 QVector<double> m;
195
196 const bool hasZ = start.is3D() && controlPoint1.is3D() && controlPoint2.is3D() && end.is3D();
197 if ( hasZ )
198 {
199 z.resize( segments + 1 );
200 }
201
202 const bool hasM = start.isMeasure() && controlPoint1.isMeasure() && controlPoint2.isMeasure() && end.isMeasure();
203 if ( hasM )
204 {
205 m.resize( segments + 1 );
206 }
207
208 double *xData = x.data();
209 double *yData = y.data();
210 double *zData = z.data(); // will be nullptr if !hasZ
211 double *mData = m.data(); // will be nullptr if !hasM
212
213 const double step = 1.0 / segments;
214
215 for ( int i = 0; i <= segments; ++i )
216 {
217 const double t = i * step;
218
219 double ix, iy; // interpolated x, y
220 double iz = std::numeric_limits<double>::quiet_NaN();
221 double im = std::numeric_limits<double>::quiet_NaN();
222
224 start.x(),
225 start.y(),
226 start.z(),
227 start.m(),
228 controlPoint1.x(),
229 controlPoint1.y(),
230 controlPoint1.z(),
231 controlPoint1.m(),
232 controlPoint2.x(),
233 controlPoint2.y(),
234 controlPoint2.z(),
235 controlPoint2.m(),
236 end.x(),
237 end.y(),
238 end.z(),
239 end.m(),
240 t,
241 hasZ,
242 hasM,
243 ix,
244 iy,
245 iz,
246 im
247 );
248
249 *xData++ = ix;
250 *yData++ = iy;
251 if ( hasZ )
252 *zData++ = iz;
253 if ( hasM )
254 *mData++ = im;
255 }
256
257 return std::make_unique< QgsLineString >( x, y, z, m );
258}
259
260std::unique_ptr< QgsLineString > QgsLineString::fromQPolygonF( const QPolygonF &polygon )
261{
262 QVector< double > x;
263 QVector< double > y;
264 x.resize( polygon.count() );
265 y.resize( polygon.count() );
266 double *xData = x.data();
267 double *yData = y.data();
268
269 const QPointF *src = polygon.data();
270 for ( int i = 0; i < polygon.size(); ++i )
271 {
272 *xData++ = src->x();
273 *yData++ = src->y();
274 src++;
275 }
276
277 return std::make_unique< QgsLineString >( x, y );
278}
279
281{
282 return new QgsLineString( *this );
283}
284
286{
287 mX.clear();
288 mY.clear();
289 mZ.clear();
290 mM.clear();
292 clearCache();
293}
294
296{
297 return mX.isEmpty();
298}
299
300int QgsLineString::indexOf( const QgsPoint &point ) const
301{
302 const int size = mX.size();
303 if ( size == 0 )
304 return -1;
305
306 const double *x = mX.constData();
307 const double *y = mY.constData();
308 const bool useZ = is3D();
309 const bool useM = isMeasure();
310 const double *z = useZ ? mZ.constData() : nullptr;
311 const double *m = useM ? mM.constData() : nullptr;
312
313 for ( int i = 0; i < size; ++i )
314 {
315 if ( qgsDoubleNear( *x, point.x() ) && qgsDoubleNear( *y, point.y() ) && ( !useZ || qgsDoubleNear( *z, point.z() ) ) && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
316 return i;
317
318 x++;
319 y++;
320 if ( useZ )
321 z++;
322 if ( useM )
323 m++;
324 }
325 return -1;
326}
327
328bool QgsLineString::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
329{
330 if ( !isEmpty() && ( numPoints() < 2 ) )
331 {
332 error = QObject::tr( "LineString has less than 2 points and is not empty." );
333 return false;
334 }
335 return QgsCurve::isValid( error, flags );
336}
337
338QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
339{
340 // prepare result
341 std::unique_ptr<QgsLineString> result { createEmptyWithSameType() };
342
343 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, result->mX, result->mY, result->mZ, result->mM, removeRedundantPoints );
344 if ( res )
345 return result.release();
346 else
347 return nullptr;
348}
349
350bool QgsLineString::removeDuplicateNodes( double epsilon, bool useZValues )
351{
352 if ( mX.count() <= 2 )
353 return false; // don't create degenerate lines
354 bool result = false;
355 double prevX = mX.at( 0 );
356 double prevY = mY.at( 0 );
357 bool hasZ = is3D();
358 bool useZ = hasZ && useZValues;
359 double prevZ = useZ ? mZ.at( 0 ) : 0;
360 int i = 1;
361 int remaining = mX.count();
362 while ( i < remaining )
363 {
364 double currentX = mX.at( i );
365 double currentY = mY.at( i );
366 double currentZ = useZ ? mZ.at( i ) : 0;
367 if ( qgsDoubleNear( currentX, prevX, epsilon ) && qgsDoubleNear( currentY, prevY, epsilon ) && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
368 {
369 result = true;
370 // remove point
371 mX.removeAt( i );
372 mY.removeAt( i );
373 if ( hasZ )
374 mZ.removeAt( i );
375 remaining--;
376 }
377 else
378 {
379 prevX = currentX;
380 prevY = currentY;
381 prevZ = currentZ;
382 i++;
383 }
384 }
385 return result;
386}
387
389{
390 if ( mX.empty() )
391 return false;
392
393 return qgsDoubleNear( mX.first(), mX.last() ) && qgsDoubleNear( mY.first(), mY.last() );
394}
395
397{
398 bool closed = isClosed2D();
399
400 if ( is3D() && closed )
401 closed &= qgsDoubleNear( mZ.first(), mZ.last() ) || ( std::isnan( mZ.first() ) && std::isnan( mZ.last() ) );
402 return closed;
403}
404
405// As `bool boundingBoxIntersects( const QgsBox3D &box3d )` and `bool boundingBoxIntersects( const QgsRectangle &rectangle )` are nearly
406// the same: if one of these functions is changed then remember to also update the other accordingly
408{
409 if ( mX.empty() )
410 return false;
411
412 if ( !mBoundingBox.isNull() )
413 {
414 return mBoundingBox.toRectangle().intersects( rectangle );
415 }
416 const int nb = mX.size();
417
418 // We are a little fancy here!
419 if ( nb > 40 )
420 {
421 // if a large number of vertices, take some sample vertices at 1/5th increments through the linestring
422 // and test whether any are inside the rectangle. Maybe we can shortcut a lot of iterations by doing this!
423 // (why 1/5th? it's picked so that it works nicely for polygon rings which are almost rectangles, so the vertex extremities
424 // will fall on approximately these vertex indices)
425 if ( rectangle.contains( mX.at( 0 ), mY.at( 0 ) )
426 || rectangle.contains( mX.at( static_cast< int >( nb * 0.2 ) ), mY.at( static_cast< int >( nb * 0.2 ) ) )
427 || rectangle.contains( mX.at( static_cast< int >( nb * 0.4 ) ), mY.at( static_cast< int >( nb * 0.4 ) ) )
428 || rectangle.contains( mX.at( static_cast< int >( nb * 0.6 ) ), mY.at( static_cast< int >( nb * 0.6 ) ) )
429 || rectangle.contains( mX.at( static_cast< int >( nb * 0.8 ) ), mY.at( static_cast< int >( nb * 0.8 ) ) )
430 || rectangle.contains( mX.at( nb - 1 ), mY.at( nb - 1 ) ) )
431 return true;
432 }
433
434 // Be even MORE fancy! Given that bounding box calculation is non-free, cached, and we don't
435 // already have it, we start performing the bounding box calculation while we are testing whether
436 // each point falls inside the rectangle. That way if we end up testing the majority of the points
437 // anyway, we can update the cached bounding box with the results we've calculated along the way
438 // and save future calls to calculate the bounding box!
439 double xmin = std::numeric_limits<double>::max();
440 double ymin = std::numeric_limits<double>::max();
441 double zmin = -std::numeric_limits<double>::max();
442 double xmax = -std::numeric_limits<double>::max();
443 double ymax = -std::numeric_limits<double>::max();
444 double zmax = -std::numeric_limits<double>::max();
445
446 const double *x = mX.constData();
447 const double *y = mY.constData();
448 const double *z = is3D() ? mZ.constData() : nullptr;
449 bool foundPointInRectangle = false;
450 for ( int i = 0; i < nb; ++i )
451 {
452 const double px = *x++;
453 xmin = std::min( xmin, px );
454 xmax = std::max( xmax, px );
455 const double py = *y++;
456 ymin = std::min( ymin, py );
457 ymax = std::max( ymax, py );
458 if ( z )
459 {
460 const double pz = *z++;
461 zmin = std::min( zmin, pz );
462 zmax = std::max( zmax, pz );
463 }
464
465 if ( !foundPointInRectangle && rectangle.contains( px, py ) )
466 {
467 foundPointInRectangle = true;
468
469 // now... we have a choice to make. If we've already looped through the majority of the points
470 // in this linestring then let's just continue to iterate through the remainder so that we can
471 // complete the overall bounding box calculation we've already mostly done. If however we're only
472 // just at the start of iterating the vertices, we shortcut out early and leave the bounding box
473 // uncalculated
474 if ( i < nb * 0.5 )
475 return true;
476 }
477 }
478
479 // at this stage we now know the overall bounding box of the linestring, so let's cache
480 // it so we don't ever have to calculate this again. We've done all the hard work anyway!
481 mBoundingBox = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax, false );
482
483 if ( foundPointInRectangle )
484 return true;
485
486 // NOTE: if none of the points in the line actually fell inside the rectangle, it doesn't
487 // exclude that the OVERALL bounding box of the linestring itself intersects the rectangle!!
488 // So we fall back to the parent class method which compares the overall bounding box against
489 // the rectangle... and this will be very cheap now that we've already calculated and cached
490 // the linestring's bounding box!
491 return QgsCurve::boundingBoxIntersects( rectangle );
492}
493
494// As `bool boundingBoxIntersects( const QgsBox3D &box3d )` and `bool boundingBoxIntersects( const QgsRectangle &rectangle )` are nearly
495// the same: if one of these functions is changed then remember to also update the other accordingly
497{
498 if ( mX.empty() )
499 return false;
500
501 if ( mZ.empty() )
502 return boundingBoxIntersects( box3d.toRectangle() );
503
504 if ( !mBoundingBox.isNull() )
505 {
506 return mBoundingBox.intersects( box3d );
507 }
508 const int nb = mX.size();
509
510 // We are a little fancy here!
511 if ( nb > 40 )
512 {
513 // if a large number of vertices, take some sample vertices at 1/5th increments through the linestring
514 // and test whether any are inside the rectangle. Maybe we can shortcut a lot of iterations by doing this!
515 // (why 1/5th? it's picked so that it works nicely for polygon rings which are almost rectangles, so the vertex extremities
516 // will fall on approximately these vertex indices)
517 if ( box3d.contains( mX.at( 0 ), mY.at( 0 ), mZ.at( 0 ) )
518 || box3d.contains( mX.at( static_cast< int >( nb * 0.2 ) ), mY.at( static_cast< int >( nb * 0.2 ) ), mZ.at( static_cast< int >( nb * 0.2 ) ) )
519 || box3d.contains( mX.at( static_cast< int >( nb * 0.4 ) ), mY.at( static_cast< int >( nb * 0.4 ) ), mZ.at( static_cast< int >( nb * 0.4 ) ) )
520 || box3d.contains( mX.at( static_cast< int >( nb * 0.6 ) ), mY.at( static_cast< int >( nb * 0.6 ) ), mZ.at( static_cast< int >( nb * 0.6 ) ) )
521 || box3d.contains( mX.at( static_cast< int >( nb * 0.8 ) ), mY.at( static_cast< int >( nb * 0.8 ) ), mZ.at( static_cast< int >( nb * 0.8 ) ) )
522 || box3d.contains( mX.at( nb - 1 ), mY.at( nb - 1 ), mZ.at( nb - 1 ) ) )
523 return true;
524 }
525
526 // Be even MORE fancy! Given that bounding box calculation is non-free, cached, and we don't
527 // already have it, we start performing the bounding box calculation while we are testing whether
528 // each point falls inside the rectangle. That way if we end up testing the majority of the points
529 // anyway, we can update the cached bounding box with the results we've calculated along the way
530 // and save future calls to calculate the bounding box!
531 double xmin = std::numeric_limits<double>::max();
532 double ymin = std::numeric_limits<double>::max();
533 double zmin = std::numeric_limits<double>::max();
534 double xmax = -std::numeric_limits<double>::max();
535 double ymax = -std::numeric_limits<double>::max();
536 double zmax = -std::numeric_limits<double>::max();
537
538 const double *x = mX.constData();
539 const double *y = mY.constData();
540 const double *z = mZ.constData();
541 bool foundPointInBox = false;
542 for ( int i = 0; i < nb; ++i )
543 {
544 const double px = *x++;
545 xmin = std::min( xmin, px );
546 xmax = std::max( xmax, px );
547 const double py = *y++;
548 ymin = std::min( ymin, py );
549 ymax = std::max( ymax, py );
550 const double pz = *z++;
551 zmin = std::min( zmin, pz );
552 zmax = std::max( zmax, pz );
553
554 if ( !foundPointInBox && box3d.contains( px, py, pz ) )
555 {
556 foundPointInBox = true;
557
558 // now... we have a choice to make. If we've already looped through the majority of the points
559 // in this linestring then let's just continue to iterate through the remainder so that we can
560 // complete the overall bounding box calculation we've already mostly done. If however we're only
561 // just at the start of iterating the vertices, we shortcut out early and leave the bounding box
562 // uncalculated
563 if ( i < nb * 0.5 )
564 return true;
565 }
566 }
567
568 // at this stage we now know the overall bounding box of the linestring, so let's cache
569 // it so we don't ever have to calculate this again. We've done all the hard work anyway!
570 mBoundingBox = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax, false );
571
572 if ( foundPointInBox )
573 return true;
574
575 // NOTE: if none of the points in the line actually fell inside the rectangle, it doesn't
576 // exclude that the OVERALL bounding box of the linestring itself intersects the rectangle!!
577 // So we fall back to the parent class method which compares the overall bounding box against
578 // the rectangle... and this will be very cheap now that we've already calculated and cached
579 // the linestring's bounding box!
580 return QgsCurve::boundingBoxIntersects( box3d );
581}
582
583QVector< QgsVertexId > QgsLineString::collectDuplicateNodes( double epsilon, bool useZValues ) const
584{
585 QVector< QgsVertexId > res;
586 if ( mX.count() <= 1 )
587 return res;
588
589 const double *x = mX.constData();
590 const double *y = mY.constData();
591 bool hasZ = is3D();
592 bool useZ = hasZ && useZValues;
593 const double *z = useZ ? mZ.constData() : nullptr;
594
595 double prevX = *x++;
596 double prevY = *y++;
597 double prevZ = z ? *z++ : 0;
598
599 QgsVertexId id;
600 for ( int i = 1; i < mX.count(); ++i )
601 {
602 double currentX = *x++;
603 double currentY = *y++;
604 double currentZ = useZ ? *z++ : 0;
605 if ( qgsDoubleNear( currentX, prevX, epsilon ) && qgsDoubleNear( currentY, prevY, epsilon ) && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
606 {
607 id.vertex = i;
608 res << id;
609 }
610 else
611 {
612 prevX = currentX;
613 prevY = currentY;
614 prevZ = currentZ;
615 }
616 }
617 return res;
618}
619
621{
622 const int nb = mX.size();
623 QPolygonF points( nb );
624
625 const double *x = mX.constData();
626 const double *y = mY.constData();
627 QPointF *dest = points.data();
628 for ( int i = 0; i < nb; ++i )
629 {
630 *dest++ = QPointF( *x++, *y++ );
631 }
632 return points;
633}
634
635
636void simplifySection( int i, int j, const double *x, const double *y, std::vector< bool > &usePoint, const double distanceToleranceSquared, const double epsilon )
637{
638 if ( i + 1 == j )
639 {
640 return;
641 }
642
643 double maxDistanceSquared = -1.0;
644
645 int maxIndex = i;
646 double mx, my;
647
648 for ( int k = i + 1; k < j; k++ )
649 {
650 const double distanceSquared = QgsGeometryUtilsBase::sqrDistToLine( x[k], y[k], x[i], y[i], x[j], y[j], mx, my, epsilon );
651
652 if ( distanceSquared > maxDistanceSquared )
653 {
654 maxDistanceSquared = distanceSquared;
655 maxIndex = k;
656 }
657 }
658 if ( maxDistanceSquared <= distanceToleranceSquared )
659 {
660 for ( int k = i + 1; k < j; k++ )
661 {
662 usePoint[k] = false;
663 }
664 }
665 else
666 {
667 simplifySection( i, maxIndex, x, y, usePoint, distanceToleranceSquared, epsilon );
668 simplifySection( maxIndex, j, x, y, usePoint, distanceToleranceSquared, epsilon );
669 }
670};
671
673{
674 if ( mX.empty() )
675 {
676 return new QgsLineString();
677 }
678
679 // ported from GEOS DouglasPeuckerLineSimplifier::simplify
680
681 const double distanceToleranceSquared = tolerance * tolerance;
682 const double *xData = mX.constData();
683 const double *yData = mY.constData();
684 const double *zData = mZ.constData();
685 const double *mData = mM.constData();
686
687 const int size = mX.size();
688
689 std::vector< bool > usePoint( size, true );
690
691 constexpr double epsilon = 4 * std::numeric_limits<double>::epsilon();
692 simplifySection( 0, size - 1, xData, yData, usePoint, distanceToleranceSquared, epsilon );
693
694 QVector< double > newX;
695 newX.reserve( size );
696 QVector< double > newY;
697 newY.reserve( size );
698
699 const bool hasZ = is3D();
700 const bool hasM = isMeasure();
701 QVector< double > newZ;
702 if ( hasZ )
703 newZ.reserve( size );
704 QVector< double > newM;
705 if ( hasM )
706 newM.reserve( size );
707
708 for ( int i = 0, n = size; i < n; ++i )
709 {
710 if ( usePoint[i] || i == n - 1 )
711 {
712 newX.append( xData[i] );
713 newY.append( yData[i] );
714 if ( hasZ )
715 newZ.append( zData[i] );
716 if ( hasM )
717 newM.append( mData[i] );
718 }
719 }
720
721 const bool simplifyRing = isRing();
722 const int newSize = newX.size();
723 if ( simplifyRing && newSize > 3 )
724 {
725 double mx, my;
726 const double distanceSquared = QgsGeometryUtilsBase::sqrDistToLine( newX[0], newY[0], newX[newSize - 2], newY[newSize - 2], newX[1], newY[1], mx, my, epsilon );
727
728 if ( distanceSquared <= distanceToleranceSquared )
729 {
730 newX.removeFirst();
731 newX.last() = newX.first();
732 newY.removeFirst();
733 newY.last() = newY.first();
734 if ( hasZ )
735 {
736 newZ.removeFirst();
737 newZ.last() = newZ.first();
738 }
739 if ( hasM )
740 {
741 newM.removeFirst();
742 newM.last() = newM.first();
743 }
744 }
745 }
746
747 return new QgsLineString( newX, newY, newZ, newM );
748}
749
751{
752 if ( !wkbPtr )
753 {
754 return false;
755 }
756
757 Qgis::WkbType type = wkbPtr.readHeader();
759 {
760 return false;
761 }
762 mWkbType = type;
763 importVerticesFromWkb( wkbPtr );
764 return true;
765}
766
768{
769 if ( mX.empty() )
770 {
771 return QgsBox3D();
772 }
773
774 auto result2D = std::minmax_element( mX.begin(), mX.end() );
775 const double xmin = *result2D.first;
776 const double xmax = *result2D.second;
777 result2D = std::minmax_element( mY.begin(), mY.end() );
778 const double ymin = *result2D.first;
779 const double ymax = *result2D.second;
780
781 double zmin = std::numeric_limits< double >::quiet_NaN();
782 double zmax = std::numeric_limits< double >::quiet_NaN();
783
784 if ( is3D() )
785 {
786 auto resultZ = std::minmax_element( mZ.begin(), mZ.end() );
787 zmin = *resultZ.first;
788 zmax = *resultZ.second;
789 }
790
791 return QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax );
792}
793
798
799void QgsLineString::scroll( int index )
800{
801 const int size = mX.size();
802 if ( index < 1 || index >= size - 1 )
803 return;
804
805 const bool useZ = is3D();
806 const bool useM = isMeasure();
807
808 QVector<double> newX( size );
809 QVector<double> newY( size );
810 QVector<double> newZ( useZ ? size : 0 );
811 QVector<double> newM( useM ? size : 0 );
812 auto it = std::copy( mX.constBegin() + index, mX.constEnd() - 1, newX.begin() );
813 it = std::copy( mX.constBegin(), mX.constBegin() + index, it );
814 *it = *newX.constBegin();
815 mX = std::move( newX );
816
817 it = std::copy( mY.constBegin() + index, mY.constEnd() - 1, newY.begin() );
818 it = std::copy( mY.constBegin(), mY.constBegin() + index, it );
819 *it = *newY.constBegin();
820 mY = std::move( newY );
821 if ( useZ )
822 {
823 it = std::copy( mZ.constBegin() + index, mZ.constEnd() - 1, newZ.begin() );
824 it = std::copy( mZ.constBegin(), mZ.constBegin() + index, it );
825 *it = *newZ.constBegin();
826 mZ = std::move( newZ );
827 }
828 if ( useM )
829 {
830 it = std::copy( mM.constBegin() + index, mM.constEnd() - 1, newM.begin() );
831 it = std::copy( mM.constBegin(), mM.constBegin() + index, it );
832 *it = *newM.constBegin();
833 mM = std::move( newM );
834 }
835}
836
837/***************************************************************************
838 * This class is considered CRITICAL and any change MUST be accompanied with
839 * full unit tests.
840 * See details in QEP #17
841 ****************************************************************************/
842bool QgsLineString::fromWkt( const QString &wkt )
843{
844 clear();
845
846 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
847
849 return false;
850 mWkbType = parts.first;
851
852 QString secondWithoutParentheses = parts.second;
853 secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
854 parts.second = parts.second.remove( '(' ).remove( ')' );
855 if ( ( parts.second.compare( "EMPTY"_L1, Qt::CaseInsensitive ) == 0 ) || secondWithoutParentheses.isEmpty() )
856 return true;
857
859 // There is a non number in the coordinates sequence
860 // LineString ( A b, 1 2)
861 if ( points.isEmpty() )
862 return false;
863
864 setPoints( points );
865 return true;
866}
867
869{
870 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
871 binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
872 return binarySize;
873}
874
875QByteArray QgsLineString::asWkb( WkbFlags flags ) const
876{
877 QByteArray wkbArray;
878 wkbArray.resize( QgsLineString::wkbSize( flags ) );
879 QgsWkbPtr wkb( wkbArray );
880 wkb << static_cast<char>( QgsApplication::endian() );
881 wkb << static_cast<quint32>( wkbType() );
883 points( pts );
884 QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure(), flags );
885 return wkbArray;
886}
887
888/***************************************************************************
889 * This class is considered CRITICAL and any change MUST be accompanied with
890 * full unit tests.
891 * See details in QEP #17
892 ****************************************************************************/
893
894QString QgsLineString::asWkt( int precision ) const
895{
896 QString wkt = wktTypeStr() + ' ';
897
898 if ( isEmpty() )
899 wkt += "EMPTY"_L1;
900 else
901 {
903 points( pts );
904 wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
905 }
906 return wkt;
907}
908
909QDomElement QgsLineString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
910{
912 points( pts );
913
914 QDomElement elemLineString = doc.createElementNS( ns, u"LineString"_s );
915
916 if ( isEmpty() )
917 return elemLineString;
918
919 elemLineString.appendChild( QgsGeometryUtils::pointsToGML2( pts, doc, precision, ns, axisOrder ) );
920
921 return elemLineString;
922}
923
924QDomElement QgsLineString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
925{
927 points( pts );
928
929 QDomElement elemLineString = doc.createElementNS( ns, u"LineString"_s );
930
931 if ( isEmpty() )
932 return elemLineString;
933
934 elemLineString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
935 return elemLineString;
936}
937
938json QgsLineString::asJsonObject( int precision ) const
939{
941 points( pts );
942 return { { "type", "LineString" }, { "coordinates", QgsGeometryUtils::pointsToJson( pts, precision ) } };
943}
944
945QString QgsLineString::asKml( int precision ) const
946{
947 QString kml;
948 if ( isRing() )
949 {
950 kml.append( "<LinearRing>"_L1 );
951 }
952 else
953 {
954 kml.append( "<LineString>"_L1 );
955 }
956 bool z = is3D();
957 kml.append( "<altitudeMode>"_L1 );
958 if ( z )
959 {
960 kml.append( "absolute"_L1 );
961 }
962 else
963 {
964 kml.append( "clampToGround"_L1 );
965 }
966 kml.append( "</altitudeMode>"_L1 );
967 kml.append( "<coordinates>"_L1 );
968
969 int nPoints = mX.size();
970 for ( int i = 0; i < nPoints; ++i )
971 {
972 if ( i > 0 )
973 {
974 kml.append( " "_L1 );
975 }
976 kml.append( qgsDoubleToString( mX[i], precision ) );
977 kml.append( ","_L1 );
978 kml.append( qgsDoubleToString( mY[i], precision ) );
979 if ( z )
980 {
981 kml.append( ","_L1 );
982 kml.append( qgsDoubleToString( mZ[i], precision ) );
983 }
984 else
985 {
986 kml.append( ",0"_L1 );
987 }
988 }
989 kml.append( "</coordinates>"_L1 );
990 if ( isRing() )
991 {
992 kml.append( "</LinearRing>"_L1 );
993 }
994 else
995 {
996 kml.append( "</LineString>"_L1 );
997 }
998 return kml;
999}
1000
1001/***************************************************************************
1002 * This class is considered CRITICAL and any change MUST be accompanied with
1003 * full unit tests.
1004 * See details in QEP #17
1005 ****************************************************************************/
1006
1008{
1009 double total = 0;
1010 const int size = mX.size();
1011 if ( size < 2 )
1012 return 0;
1013
1014 const double *x = mX.constData();
1015 const double *y = mY.constData();
1016 double dx, dy;
1017
1018 double prevX = *x++;
1019 double prevY = *y++;
1020
1021 for ( int i = 1; i < size; ++i )
1022 {
1023 dx = *x - prevX;
1024 dy = *y - prevY;
1025 total += std::sqrt( dx * dx + dy * dy );
1026
1027 prevX = *x++;
1028 prevY = *y++;
1029 }
1030 return total;
1031}
1032
1033std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsLineString::splitCurveAtVertex( int index ) const
1034{
1035 const bool useZ = is3D();
1036 const bool useM = isMeasure();
1037
1038 const int size = mX.size();
1039 if ( size == 0 )
1040 return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >() );
1041
1042 index = std::clamp( index, 0, size - 1 );
1043
1044 const int part1Size = index + 1;
1045 QVector< double > x1( part1Size );
1046 QVector< double > y1( part1Size );
1047 QVector< double > z1( useZ ? part1Size : 0 );
1048 QVector< double > m1( useM ? part1Size : 0 );
1049
1050 const double *sourceX = mX.constData();
1051 const double *sourceY = mY.constData();
1052 const double *sourceZ = useZ ? mZ.constData() : nullptr;
1053 const double *sourceM = useM ? mM.constData() : nullptr;
1054
1055 double *destX = x1.data();
1056 double *destY = y1.data();
1057 double *destZ = useZ ? z1.data() : nullptr;
1058 double *destM = useM ? m1.data() : nullptr;
1059
1060 std::copy( sourceX, sourceX + part1Size, destX );
1061 std::copy( sourceY, sourceY + part1Size, destY );
1062 if ( useZ )
1063 std::copy( sourceZ, sourceZ + part1Size, destZ );
1064 if ( useM )
1065 std::copy( sourceM, sourceM + part1Size, destM );
1066
1067 const int part2Size = size - index;
1068 if ( part2Size < 2 )
1069 return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >() );
1070
1071 QVector< double > x2( part2Size );
1072 QVector< double > y2( part2Size );
1073 QVector< double > z2( useZ ? part2Size : 0 );
1074 QVector< double > m2( useM ? part2Size : 0 );
1075 destX = x2.data();
1076 destY = y2.data();
1077 destZ = useZ ? z2.data() : nullptr;
1078 destM = useM ? m2.data() : nullptr;
1079 std::copy( sourceX + index, sourceX + size, destX );
1080 std::copy( sourceY + index, sourceY + size, destY );
1081 if ( useZ )
1082 std::copy( sourceZ + index, sourceZ + size, destZ );
1083 if ( useM )
1084 std::copy( sourceM + index, sourceM + size, destM );
1085
1086 if ( part1Size < 2 )
1087 return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
1088 else
1089 return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
1090}
1091
1092QVector<QgsLineString *> QgsLineString::splitToDisjointXYParts() const
1093{
1094 const double *allPointsX = xData();
1095 const double *allPointsY = yData();
1096 size_t allPointsCount = numPoints();
1097 QVector<double> partX;
1098 QVector<double> partY;
1099 QSet<QgsPointXY> partPointSet;
1100
1101 QVector<QgsLineString *> disjointParts;
1102 for ( size_t i = 0; i < allPointsCount; i++ )
1103 {
1104 const QgsPointXY point( *allPointsX++, *allPointsY++ );
1105 if ( partPointSet.contains( point ) )
1106 {
1107 // This point is used multiple times, cut the curve and add the
1108 // current part
1109 disjointParts.push_back( new QgsLineString( partX, partY ) );
1110 // Now start a new part containing the last line
1111 partX = { partX.last() };
1112 partY = { partY.last() };
1113 partPointSet = { QgsPointXY( partX[0], partY[0] ) };
1114 }
1115 partX.push_back( point.x() );
1116 partY.push_back( point.y() );
1117 partPointSet.insert( point );
1118 }
1119 // Add the last part (if we didn't stop by closing the loop)
1120 if ( partX.size() > 1 || disjointParts.size() == 0 )
1121 disjointParts.push_back( new QgsLineString( partX, partY ) );
1122
1123 return disjointParts;
1124}
1125
1127{
1128 if ( is3D() )
1129 {
1130 double total = 0;
1131 const int size = mX.size();
1132 if ( size < 2 )
1133 return 0;
1134
1135 const double *x = mX.constData();
1136 const double *y = mY.constData();
1137 const double *z = mZ.constData();
1138 double dx, dy, dz;
1139
1140 double prevX = *x++;
1141 double prevY = *y++;
1142 double prevZ = *z++;
1143
1144 for ( int i = 1; i < size; ++i )
1145 {
1146 dx = *x - prevX;
1147 dy = *y - prevY;
1148 dz = *z - prevZ;
1149 total += std::sqrt( dx * dx + dy * dy + dz * dz );
1150
1151 prevX = *x++;
1152 prevY = *y++;
1153 prevZ = *z++;
1154 }
1155 return total;
1156 }
1157 else
1158 {
1159 return length();
1160 }
1161}
1162
1164{
1165 if ( numPoints() < 1 )
1166 {
1167 return QgsPoint();
1168 }
1169 return pointN( 0 );
1170}
1171
1173{
1174 if ( numPoints() < 1 )
1175 {
1176 return QgsPoint();
1177 }
1178 return pointN( numPoints() - 1 );
1179}
1180
1181/***************************************************************************
1182 * This class is considered CRITICAL and any change MUST be accompanied with
1183 * full unit tests.
1184 * See details in QEP #17
1185 ****************************************************************************/
1186
1188{
1189 Q_UNUSED( tolerance )
1190 Q_UNUSED( toleranceType )
1191 return clone();
1192}
1193
1195{
1196 return mX.size();
1197}
1198
1200{
1201 return mX.size();
1202}
1203
1205{
1206 if ( i < 0 || i >= mX.size() )
1207 {
1208 return QgsPoint();
1209 }
1210
1211 double x = mX.at( i );
1212 double y = mY.at( i );
1213 double z = std::numeric_limits<double>::quiet_NaN();
1214 double m = std::numeric_limits<double>::quiet_NaN();
1215
1216 bool hasZ = is3D();
1217 if ( hasZ )
1218 {
1219 z = mZ.at( i );
1220 }
1221 bool hasM = isMeasure();
1222 if ( hasM )
1223 {
1224 m = mM.at( i );
1225 }
1226
1229 {
1231 }
1232 else if ( hasZ && hasM )
1233 {
1235 }
1236 else if ( hasZ )
1237 {
1239 }
1240 else if ( hasM )
1241 {
1243 }
1244 return QgsPoint( t, x, y, z, m );
1245}
1246
1247/***************************************************************************
1248 * This class is considered CRITICAL and any change MUST be accompanied with
1249 * full unit tests.
1250 * See details in QEP #17
1251 ****************************************************************************/
1252
1253double QgsLineString::xAt( int index ) const
1254{
1255 if ( index >= 0 && index < mX.size() )
1256 return mX.at( index );
1257 else
1258 return 0.0;
1259}
1260
1261double QgsLineString::yAt( int index ) const
1262{
1263 if ( index >= 0 && index < mY.size() )
1264 return mY.at( index );
1265 else
1266 return 0.0;
1267}
1268
1269void QgsLineString::setXAt( int index, double x )
1270{
1271 if ( index >= 0 && index < mX.size() )
1272 mX[index] = x;
1273 clearCache();
1274}
1275
1276void QgsLineString::setYAt( int index, double y )
1277{
1278 if ( index >= 0 && index < mY.size() )
1279 mY[index] = y;
1280 clearCache();
1281}
1282
1283/***************************************************************************
1284 * This class is considered CRITICAL and any change MUST be accompanied with
1285 * full unit tests.
1286 * See details in QEP #17
1287 ****************************************************************************/
1288
1290{
1291 pts.clear();
1292 int nPoints = numPoints();
1293 pts.reserve( nPoints );
1294 for ( int i = 0; i < nPoints; ++i )
1295 {
1296 pts.push_back( pointN( i ) );
1297 }
1298}
1299
1300void QgsLineString::setPoints( size_t size, const double *x, const double *y, const double *z, const double *m )
1301{
1302 clearCache(); //set bounding box invalid
1303
1304 if ( size == 0 )
1305 {
1306 clear();
1307 return;
1308 }
1309
1310 const bool hasZ = static_cast< bool >( z );
1311 const bool hasM = static_cast< bool >( m );
1312
1313 if ( hasZ && hasM )
1314 {
1316 }
1317 else if ( hasZ )
1318 {
1320 }
1321 else if ( hasM )
1322 {
1324 }
1325 else
1326 {
1328 }
1329
1330 mX.resize( size );
1331 mY.resize( size );
1332 double *destX = mX.data();
1333 double *destY = mY.data();
1334 double *destZ = nullptr;
1335 if ( hasZ )
1336 {
1337 mZ.resize( size );
1338 destZ = mZ.data();
1339 }
1340 else
1341 {
1342 mZ.clear();
1343 }
1344 double *destM = nullptr;
1345 if ( hasM )
1346 {
1347 mM.resize( size );
1348 destM = mM.data();
1349 }
1350 else
1351 {
1352 mM.clear();
1353 }
1354
1355 for ( size_t i = 0; i < size; ++i )
1356 {
1357 *destX++ = *x++;
1358 *destY++ = *y++;
1359 if ( hasZ )
1360 {
1361 *destZ++ = *z++;
1362 }
1363 if ( hasM )
1364 {
1365 *destM++ = *m++;
1366 }
1367 }
1368}
1369
1371{
1372 clearCache(); //set bounding box invalid
1373
1374 if ( points.isEmpty() )
1375 {
1376 clear();
1377 return;
1378 }
1379
1380 //get wkb type from first point
1381 const QgsPoint &firstPt = points.at( 0 );
1382 bool hasZ = firstPt.is3D();
1383 bool hasM = firstPt.isMeasure();
1384
1386
1387 mX.resize( points.size() );
1388 mY.resize( points.size() );
1389 if ( hasZ )
1390 {
1391 mZ.resize( points.size() );
1392 }
1393 else
1394 {
1395 mZ.clear();
1396 }
1397 if ( hasM )
1398 {
1399 mM.resize( points.size() );
1400 }
1401 else
1402 {
1403 mM.clear();
1404 }
1405
1406 for ( int i = 0; i < points.size(); ++i )
1407 {
1408 mX[i] = points.at( i ).x();
1409 mY[i] = points.at( i ).y();
1410 if ( hasZ )
1411 {
1412 double z = points.at( i ).z();
1413 mZ[i] = std::isnan( z ) ? 0 : z;
1414 }
1415 if ( hasM )
1416 {
1417 double m = points.at( i ).m();
1418 mM[i] = std::isnan( m ) ? 0 : m;
1419 }
1420 }
1421}
1422
1423/***************************************************************************
1424 * This class is considered CRITICAL and any change MUST be accompanied with
1425 * full unit tests.
1426 * See details in QEP #17
1427 ****************************************************************************/
1428
1430{
1431 if ( !line )
1432 {
1433 return;
1434 }
1435
1436 if ( numPoints() < 1 )
1437 {
1439 }
1440
1441 // do not store duplicate points
1442 if ( numPoints() > 0 && line->numPoints() > 0 && endPoint() == line->startPoint() )
1443 {
1444 mX.pop_back();
1445 mY.pop_back();
1446
1447 if ( is3D() )
1448 {
1449 mZ.pop_back();
1450 }
1451 if ( isMeasure() )
1452 {
1453 mM.pop_back();
1454 }
1455 }
1456
1457 mX += line->mX;
1458 mY += line->mY;
1459
1460 if ( is3D() )
1461 {
1462 if ( line->is3D() )
1463 {
1464 mZ += line->mZ;
1465 }
1466 else
1467 {
1468 // if append line does not have z coordinates, fill with NaN to match number of points in final line
1469 mZ.insert( mZ.count(), mX.size() - mZ.size(), std::numeric_limits<double>::quiet_NaN() );
1470 }
1471 }
1472
1473 if ( isMeasure() )
1474 {
1475 if ( line->isMeasure() )
1476 {
1477 mM += line->mM;
1478 }
1479 else
1480 {
1481 // if append line does not have m values, fill with NaN to match number of points in final line
1482 mM.insert( mM.count(), mX.size() - mM.size(), std::numeric_limits<double>::quiet_NaN() );
1483 }
1484 }
1485
1486 clearCache(); //set bounding box invalid
1487}
1488
1490{
1491 QgsLineString *copy = clone();
1492 std::reverse( copy->mX.begin(), copy->mX.end() );
1493 std::reverse( copy->mY.begin(), copy->mY.end() );
1494 if ( copy->is3D() )
1495 {
1496 std::reverse( copy->mZ.begin(), copy->mZ.end() );
1497 }
1498 if ( copy->isMeasure() )
1499 {
1500 std::reverse( copy->mM.begin(), copy->mM.end() );
1501 }
1502
1504 return copy;
1505}
1506
1508 const double distance, const std::function<bool( double, double, double, double, double, double, double, double, double, double, double, double )> &visitPoint
1509) const
1510{
1511 if ( distance < 0 )
1512 return;
1513
1514 double distanceTraversed = 0;
1515 const int totalPoints = numPoints();
1516 if ( totalPoints == 0 )
1517 return;
1518
1519 const double *x = mX.constData();
1520 const double *y = mY.constData();
1521 const double *z = is3D() ? mZ.constData() : nullptr;
1522 const double *m = isMeasure() ? mM.constData() : nullptr;
1523
1524 double prevX = *x++;
1525 double prevY = *y++;
1526 double prevZ = z ? *z++ : 0.0;
1527 double prevM = m ? *m++ : 0.0;
1528
1529 if ( qgsDoubleNear( distance, 0.0 ) )
1530 {
1531 visitPoint( prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM );
1532 return;
1533 }
1534
1535 double pZ = std::numeric_limits<double>::quiet_NaN();
1536 double pM = std::numeric_limits<double>::quiet_NaN();
1537 double nextPointDistance = distance;
1538 const double eps = 4 * nextPointDistance * std::numeric_limits<double>::epsilon();
1539 for ( int i = 1; i < totalPoints; ++i )
1540 {
1541 double thisX = *x++;
1542 double thisY = *y++;
1543 double thisZ = z ? *z++ : 0.0;
1544 double thisM = m ? *m++ : 0.0;
1545
1546 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1547 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength, eps ) )
1548 {
1549 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1550 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
1551 double pX, pY;
1553 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
1554
1555 if ( !visitPoint( pX, pY, pZ, pM, prevX, prevY, prevZ, prevM, thisX, thisY, thisZ, thisM ) )
1556 return;
1557
1558 nextPointDistance += distance;
1559 }
1560
1561 distanceTraversed += segmentLength;
1562 prevX = thisX;
1563 prevY = thisY;
1564 prevZ = thisZ;
1565 prevM = thisM;
1566 }
1567}
1568
1569QgsPoint *QgsLineString::interpolatePoint( const double distance ) const
1570{
1571 if ( distance < 0 )
1572 return nullptr;
1573
1575 if ( is3D() )
1576 pointType = Qgis::WkbType::PointZ;
1577 if ( isMeasure() )
1578 pointType = QgsWkbTypes::addM( pointType );
1579
1580 std::unique_ptr< QgsPoint > res;
1581 visitPointsByRegularDistance( distance, [&]( double x, double y, double z, double m, double, double, double, double, double, double, double, double ) -> bool {
1582 res = std::make_unique< QgsPoint >( pointType, x, y, z, m );
1583 return false;
1584 } );
1585 return res.release();
1586}
1587
1588bool QgsLineString::lineLocatePointByM( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance ) const
1589{
1590 return lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, false );
1591}
1592
1593bool QgsLineString::lineLocatePointByMPrivate( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance, bool haveInterpolatedM ) const
1594{
1595 if ( !isMeasure() )
1596 return false;
1597
1598 distanceFromStart = 0;
1599 const int totalPoints = numPoints();
1600 if ( totalPoints == 0 )
1601 return false;
1602
1603 const double *xData = mX.constData();
1604 const double *yData = mY.constData();
1605 const double *mData = mM.constData();
1606
1607 const double *zData = is3D() ? mZ.constData() : nullptr;
1608 use3DDistance &= static_cast< bool >( zData );
1609
1610 double prevX = *xData++;
1611 double prevY = *yData++;
1612 double prevZ = zData ? *zData++ : 0;
1613 double prevM = *mData++;
1614
1615 int i = 1;
1616 while ( i < totalPoints )
1617 {
1618 double thisX = *xData++;
1619 double thisY = *yData++;
1620 double thisZ = zData ? *zData++ : 0;
1621 double thisM = *mData++;
1622 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( thisX, thisY, thisZ, prevX, prevY, prevZ ) : QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1623
1624 if ( std::isnan( thisM ) )
1625 {
1626 if ( haveInterpolatedM )
1627 return false;
1628
1629 // if we hit a NaN m value, interpolate m to fill the blanks and then re-try
1630 std::unique_ptr< QgsLineString > interpolatedM( interpolateM( use3DDistance ) );
1631 return interpolatedM->lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, true );
1632 }
1633 else
1634 {
1635 // check if target m value falls within this segment's range
1636 if ( ( prevM < m && thisM > m ) || ( prevM > m && thisM < m ) || qgsDoubleNear( prevM, m ) || qgsDoubleNear( thisM, m ) )
1637 {
1638 // use centroid for constant value m segments
1639 if ( qgsDoubleNear( thisM, m ) && ( i < totalPoints - 1 ) && qgsDoubleNear( *mData, m ) )
1640 {
1641 distanceFromStart += segmentLength;
1642 // scan ahead till we find a vertex with a different m
1643 double totalLengthOfSegmentsWithConstantM = 0;
1644 for ( int j = 0; j < ( totalPoints - i ); ++j )
1645 {
1646 if ( !qgsDoubleNear( *( mData + j ), m ) )
1647 break;
1648
1649 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( *( xData + j - 1 ), *( yData + j - 1 ), *( zData + j - 1 ), *( xData + j ), *( yData + j ), *( zData + j ) )
1650 : QgsGeometryUtilsBase::distance2D( *( xData + j - 1 ), *( yData + j - 1 ), *( xData + j ), *( yData + j ) );
1651 totalLengthOfSegmentsWithConstantM += segmentLength;
1652 }
1653
1654 distanceFromStart += totalLengthOfSegmentsWithConstantM / 2;
1655 std::unique_ptr< QgsPoint> point( interpolatePoint( distanceFromStart ) );
1656 if ( !point )
1657 return false;
1658 x = point->x();
1659 y = point->y();
1660 z = point->z();
1661 return true;
1662 }
1663
1664 const double delta = ( m - prevM ) / ( thisM - prevM );
1665
1666 const double distanceToPoint = delta * segmentLength;
1667
1668 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, x, y );
1669 z = prevZ + ( thisZ - prevZ ) * delta;
1670 distanceFromStart += distanceToPoint;
1671 return true;
1672 }
1673 }
1674
1675 distanceFromStart += segmentLength;
1676 prevX = thisX;
1677 prevY = thisY;
1678 prevZ = thisZ;
1679 prevM = thisM;
1680 ++i;
1681 }
1682 return false;
1683}
1684
1685QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
1686{
1687 if ( startDistance < 0 && endDistance < 0 )
1688 return createEmptyWithSameType();
1689
1690 endDistance = std::max( startDistance, endDistance );
1691
1692 const int totalPoints = numPoints();
1693 if ( totalPoints == 0 )
1694 return clone();
1695
1696 QVector< QgsPoint > substringPoints;
1697 substringPoints.reserve( totalPoints );
1698
1700 if ( is3D() )
1701 pointType = Qgis::WkbType::PointZ;
1702 if ( isMeasure() )
1703 pointType = QgsWkbTypes::addM( pointType );
1704
1705 const double *x = mX.constData();
1706 const double *y = mY.constData();
1707 const double *z = is3D() ? mZ.constData() : nullptr;
1708 const double *m = isMeasure() ? mM.constData() : nullptr;
1709
1710 double distanceTraversed = 0;
1711 double prevX = *x++;
1712 double prevY = *y++;
1713 double prevZ = z ? *z++ : 0.0;
1714 double prevM = m ? *m++ : 0.0;
1715 bool foundStart = false;
1716
1717 if ( startDistance < 0 )
1718 startDistance = 0;
1719
1720 for ( int i = 1; i < totalPoints; ++i )
1721 {
1722 double thisX = *x++;
1723 double thisY = *y++;
1724 double thisZ = z ? *z++ : 0.0;
1725 double thisM = m ? *m++ : 0.0;
1726
1727 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1728
1729 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1730 {
1731 // start point falls on this segment
1732 const double distanceToStart = startDistance - distanceTraversed;
1733 double startX, startY;
1734 double startZ = 0;
1735 double startM = 0;
1737 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToStart, startX, startY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &startZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &startM : nullptr );
1738 substringPoints << QgsPoint( pointType, startX, startY, startZ, startM );
1739 foundStart = true;
1740 }
1741 if ( foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1742 {
1743 // end point falls on this segment
1744 const double distanceToEnd = endDistance - distanceTraversed;
1745 double endX, endY;
1746 double endZ = 0;
1747 double endM = 0;
1749 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToEnd, endX, endY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &endZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &endM : nullptr );
1750 substringPoints << QgsPoint( pointType, endX, endY, endZ, endM );
1751 }
1752 else if ( foundStart )
1753 {
1754 substringPoints << QgsPoint( pointType, thisX, thisY, thisZ, thisM );
1755 }
1756
1757 prevX = thisX;
1758 prevY = thisY;
1759 prevZ = thisZ;
1760 prevM = thisM;
1761 distanceTraversed += segmentLength;
1762 if ( distanceTraversed >= endDistance )
1763 break;
1764 }
1765
1766 // start point is the last node
1767 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1768 {
1769 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1770 }
1771
1772 return new QgsLineString( substringPoints );
1773}
1774
1775/***************************************************************************
1776 * This class is considered CRITICAL and any change MUST be accompanied with
1777 * full unit tests.
1778 * See details in QEP #17
1779 ****************************************************************************/
1780
1781void QgsLineString::draw( QPainter &p ) const
1782{
1783 p.drawPolyline( asQPolygonF() );
1784}
1785
1786void QgsLineString::addToPainterPath( QPainterPath &path ) const
1787{
1788 int nPoints = numPoints();
1789 if ( nPoints < 1 )
1790 {
1791 return;
1792 }
1793
1794 if ( path.isEmpty() || path.currentPosition() != QPointF( mX.at( 0 ), mY.at( 0 ) ) )
1795 {
1796 path.moveTo( mX.at( 0 ), mY.at( 0 ) );
1797 }
1798
1799 for ( int i = 1; i < nPoints; ++i )
1800 {
1801 path.lineTo( mX.at( i ), mY.at( i ) );
1802 }
1803}
1804
1805void QgsLineString::drawAsPolygon( QPainter &p ) const
1806{
1807 p.drawPolygon( asQPolygonF() );
1808}
1809
1811{
1812 QgsCompoundCurve *compoundCurve = new QgsCompoundCurve();
1813 compoundCurve->addCurve( clone() );
1814 return compoundCurve;
1815}
1816
1817void QgsLineString::extend( double startDistance, double endDistance )
1818{
1819 if ( mX.size() < 2 || mY.size() < 2 )
1820 return;
1821
1822 const bool extendStart = startDistance > 0;
1823 const bool extendEnd = endDistance > 0;
1824
1825 // start of line
1826 if ( extendStart )
1827 {
1828 const double currentLen = std::sqrt( std::pow( mX.at( 0 ) - mX.at( 1 ), 2 ) + std::pow( mY.at( 0 ) - mY.at( 1 ), 2 ) );
1829 const double newLen = currentLen + startDistance;
1830 mX[0] = mX.at( 1 ) + ( mX.at( 0 ) - mX.at( 1 ) ) / currentLen * newLen;
1831 mY[0] = mY.at( 1 ) + ( mY.at( 0 ) - mY.at( 1 ) ) / currentLen * newLen;
1832 }
1833 // end of line
1834 if ( extendEnd )
1835 {
1836 const int last = mX.size() - 1;
1837 const double currentLen = std::sqrt( std::pow( mX.at( last ) - mX.at( last - 1 ), 2 ) + std::pow( mY.at( last ) - mY.at( last - 1 ), 2 ) );
1838 const double newLen = currentLen + endDistance;
1839 mX[last] = mX.at( last - 1 ) + ( mX.at( last ) - mX.at( last - 1 ) ) / currentLen * newLen;
1840 mY[last] = mY.at( last - 1 ) + ( mY.at( last ) - mY.at( last - 1 ) ) / currentLen * newLen;
1841 }
1842
1843 if ( extendStart || extendEnd )
1844 clearCache(); //set bounding box invalid
1845}
1846
1848{
1849 auto result = std::make_unique< QgsLineString >();
1850 result->mWkbType = mWkbType;
1851 return result.release();
1852}
1853
1855{
1856 const QgsLineString *otherLine = qgsgeometry_cast<const QgsLineString *>( other );
1857 if ( !otherLine )
1858 return -1;
1859
1860 const int size = mX.size();
1861 const int otherSize = otherLine->mX.size();
1862 if ( size > otherSize )
1863 {
1864 return 1;
1865 }
1866 else if ( size < otherSize )
1867 {
1868 return -1;
1869 }
1870
1871 if ( is3D() && !otherLine->is3D() )
1872 return 1;
1873 else if ( !is3D() && otherLine->is3D() )
1874 return -1;
1875 const bool considerZ = is3D();
1876
1877 if ( isMeasure() && !otherLine->isMeasure() )
1878 return 1;
1879 else if ( !isMeasure() && otherLine->isMeasure() )
1880 return -1;
1881 const bool considerM = isMeasure();
1882
1883 for ( int i = 0; i < size; i++ )
1884 {
1885 const double x = mX[i];
1886 const double otherX = otherLine->mX[i];
1887 if ( x < otherX )
1888 {
1889 return -1;
1890 }
1891 else if ( x > otherX )
1892 {
1893 return 1;
1894 }
1895
1896 const double y = mY[i];
1897 const double otherY = otherLine->mY[i];
1898 if ( y < otherY )
1899 {
1900 return -1;
1901 }
1902 else if ( y > otherY )
1903 {
1904 return 1;
1905 }
1906
1907 if ( considerZ )
1908 {
1909 const double z = mZ[i];
1910 const double otherZ = otherLine->mZ[i];
1911
1912 if ( z < otherZ )
1913 {
1914 return -1;
1915 }
1916 else if ( z > otherZ )
1917 {
1918 return 1;
1919 }
1920 }
1921
1922 if ( considerM )
1923 {
1924 const double m = mM[i];
1925 const double otherM = otherLine->mM[i];
1926
1927 if ( m < otherM )
1928 {
1929 return -1;
1930 }
1931 else if ( m > otherM )
1932 {
1933 return 1;
1934 }
1935 }
1936 }
1937 return 0;
1938}
1939
1941{
1942 return u"LineString"_s;
1943}
1944
1946{
1947 return 1;
1948}
1949
1950/***************************************************************************
1951 * This class is considered CRITICAL and any change MUST be accompanied with
1952 * full unit tests.
1953 * See details in QEP #17
1954 ****************************************************************************/
1955
1957{
1958 double *zArray = nullptr;
1959 bool hasZ = is3D();
1960 int nPoints = numPoints();
1961
1962 // it's possible that transformCoords will throw an exception - so we need to use
1963 // a smart pointer for the dummy z values in order to ensure that they always get cleaned up
1964 std::unique_ptr< double[] > dummyZ;
1965 if ( !hasZ || !transformZ )
1966 {
1967 dummyZ = std::make_unique<double[]>( nPoints );
1968 zArray = dummyZ.get();
1969 }
1970 else
1971 {
1972 zArray = mZ.data();
1973 }
1974 ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
1975 clearCache();
1976}
1977
1978void QgsLineString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1979{
1980 int nPoints = numPoints();
1981 bool hasZ = is3D();
1982 bool hasM = isMeasure();
1983 double *x = mX.data();
1984 double *y = mY.data();
1985 double *z = hasZ ? mZ.data() : nullptr;
1986 double *m = hasM ? mM.data() : nullptr;
1987 for ( int i = 0; i < nPoints; ++i )
1988 {
1989 double xOut, yOut;
1990 t.map( *x, *y, &xOut, &yOut );
1991 *x++ = xOut;
1992 *y++ = yOut;
1993 if ( hasZ )
1994 {
1995 *z = *z * zScale + zTranslate;
1996 z++;
1997 }
1998 if ( hasM )
1999 {
2000 *m = *m * mScale + mTranslate;
2001 m++;
2002 }
2003 }
2004 clearCache();
2005}
2006
2007/***************************************************************************
2008 * This class is considered CRITICAL and any change MUST be accompanied with
2009 * full unit tests.
2010 * See details in QEP #17
2011 ****************************************************************************/
2012
2013bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
2014{
2015 if ( position.vertex < 0 || position.vertex > mX.size() )
2016 {
2017 return false;
2018 }
2019
2020 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
2021 {
2023 }
2024
2025 mX.insert( position.vertex, vertex.x() );
2026 mY.insert( position.vertex, vertex.y() );
2027 if ( is3D() )
2028 {
2029 mZ.insert( position.vertex, vertex.z() );
2030 }
2031 if ( isMeasure() )
2032 {
2033 mM.insert( position.vertex, vertex.m() );
2034 }
2035 clearCache(); //set bounding box invalid
2036 return true;
2037}
2038
2039bool QgsLineString::moveVertex( QgsVertexId position, const QgsPoint &newPos )
2040{
2041 if ( position.vertex < 0 || position.vertex >= mX.size() )
2042 {
2043 return false;
2044 }
2045 mX[position.vertex] = newPos.x();
2046 mY[position.vertex] = newPos.y();
2047 if ( is3D() && newPos.is3D() )
2048 {
2049 mZ[position.vertex] = newPos.z();
2050 }
2051 if ( isMeasure() && newPos.isMeasure() )
2052 {
2053 mM[position.vertex] = newPos.m();
2054 }
2055 clearCache(); //set bounding box invalid
2056 return true;
2057}
2058
2060{
2061 if ( position.vertex >= mX.size() || position.vertex < 0 )
2062 {
2063 return false;
2064 }
2065
2066 mX.remove( position.vertex );
2067 mY.remove( position.vertex );
2068 if ( is3D() )
2069 {
2070 mZ.remove( position.vertex );
2071 }
2072 if ( isMeasure() )
2073 {
2074 mM.remove( position.vertex );
2075 }
2076
2077 if ( numPoints() == 1 )
2078 {
2079 clear();
2080 }
2081
2082 clearCache(); //set bounding box invalid
2083 return true;
2084}
2085
2086bool QgsLineString::deleteVertices( const QSet<QgsVertexId> &positions )
2087{
2088 if ( positions.isEmpty() )
2089 {
2090 return false;
2091 }
2092
2093 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
2094
2095 for ( QgsVertexId pos : positions )
2096 {
2097 if ( !hasVertex( pos ) )
2098 {
2099 return false;
2100 }
2101 }
2102
2103 if ( numPoints() <= vertices.size() + 1 )
2104 {
2105 clear();
2106 return true;
2107 }
2108
2109 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex > b.vertex; } );
2110
2111 for ( QgsVertexId position : vertices )
2112 {
2113 mX.remove( position.vertex );
2114 mY.remove( position.vertex );
2115 if ( is3D() )
2116 {
2117 mZ.remove( position.vertex );
2118 }
2119 if ( isMeasure() )
2120 {
2121 mM.remove( position.vertex );
2122 }
2123 }
2124
2125 clearCache(); //set bounding box invalid
2126 return true;
2127}
2128
2129/***************************************************************************
2130 * This class is considered CRITICAL and any change MUST be accompanied with
2131 * full unit tests.
2132 * See details in QEP #17
2133 ****************************************************************************/
2134
2136{
2137 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
2138 {
2140 }
2141
2142 mX.append( pt.x() );
2143 mY.append( pt.y() );
2144 if ( is3D() )
2145 {
2146 mZ.append( pt.z() );
2147 }
2148 if ( isMeasure() )
2149 {
2150 mM.append( pt.m() );
2151 }
2152 clearCache(); //set bounding box invalid
2153}
2154
2155double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
2156{
2157 double sqrDist = std::numeric_limits<double>::max();
2158 double leftOfDist = std::numeric_limits<double>::max();
2159 int prevLeftOf = 0;
2160 double prevLeftOfX = 0.0;
2161 double prevLeftOfY = 0.0;
2162 double testDist = 0;
2163 double segmentPtX, segmentPtY;
2164
2165 if ( leftOf )
2166 *leftOf = 0;
2167
2168 const int size = mX.size();
2169 if ( size == 0 || size == 1 )
2170 {
2171 vertexAfter = QgsVertexId( 0, 0, 0 );
2172 return -1;
2173 }
2174
2175 const double *xData = mX.constData();
2176 const double *yData = mY.constData();
2177 for ( int i = 1; i < size; ++i )
2178 {
2179 double prevX = xData[i - 1];
2180 double prevY = yData[i - 1];
2181 double currentX = xData[i];
2182 double currentY = yData[i];
2183 testDist = QgsGeometryUtilsBase::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon );
2184 if ( testDist < sqrDist )
2185 {
2186 sqrDist = testDist;
2187 segmentPt.setX( segmentPtX );
2188 segmentPt.setY( segmentPtY );
2189 vertexAfter.part = 0;
2190 vertexAfter.ring = 0;
2191 vertexAfter.vertex = i;
2192 }
2193 if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
2194 {
2195 int left = QgsGeometryUtilsBase::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
2196 // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
2197 // so don't set leftOf in this case, and hope that there's another segment that's the same distance
2198 // where we can perform the check
2199 if ( left != 0 )
2200 {
2201 if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 )
2202 {
2203 // we have two possible segments each with equal distance to point, but they disagree
2204 // on whether or not the point is to the left of them.
2205 // so we test the segments themselves and flip the result.
2206 // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
2207 *leftOf = -QgsGeometryUtilsBase::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY );
2208 }
2209 else
2210 {
2211 *leftOf = left;
2212 }
2213 prevLeftOf = *leftOf;
2214 leftOfDist = testDist;
2215 prevLeftOfX = prevX;
2216 prevLeftOfY = prevY;
2217 }
2218 else if ( testDist < leftOfDist )
2219 {
2220 *leftOf = left;
2221 leftOfDist = testDist;
2222 prevLeftOf = 0;
2223 }
2224 }
2225 }
2226 return sqrDist;
2227}
2228
2229/***************************************************************************
2230 * This class is considered CRITICAL and any change MUST be accompanied with
2231 * full unit tests.
2232 * See details in QEP #17
2233 ****************************************************************************/
2234
2235bool QgsLineString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
2236{
2237 if ( node < 0 || node >= numPoints() )
2238 {
2239 return false;
2240 }
2241 point = pointN( node );
2243 return true;
2244}
2245
2247{
2248 if ( mX.isEmpty() )
2249 return QgsPoint();
2250
2251 int numPoints = mX.count();
2252 if ( numPoints == 1 )
2253 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
2254
2255 double totalLineLength = 0.0;
2256 double prevX = mX.at( 0 );
2257 double prevY = mY.at( 0 );
2258 double sumX = 0.0;
2259 double sumY = 0.0;
2260
2261 for ( int i = 1; i < numPoints; ++i )
2262 {
2263 double currentX = mX.at( i );
2264 double currentY = mY.at( i );
2265 double segmentLength = std::sqrt( std::pow( currentX - prevX, 2.0 ) + std::pow( currentY - prevY, 2.0 ) );
2266 if ( qgsDoubleNear( segmentLength, 0.0 ) )
2267 continue;
2268
2269 totalLineLength += segmentLength;
2270 sumX += segmentLength * ( currentX + prevX );
2271 sumY += segmentLength * ( currentY + prevY );
2272 prevX = currentX;
2273 prevY = currentY;
2274 }
2275 sumX *= 0.5;
2276 sumY *= 0.5;
2277
2278 if ( qgsDoubleNear( totalLineLength, 0.0 ) )
2279 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
2280 else
2281 return QgsPoint( sumX / totalLineLength, sumY / totalLineLength );
2282}
2283
2284/***************************************************************************
2285 * This class is considered CRITICAL and any change MUST be accompanied with
2286 * full unit tests.
2287 * See details in QEP #17
2288 ****************************************************************************/
2289
2290void QgsLineString::sumUpArea( double &sum ) const
2291{
2293 {
2294 sum += mSummedUpArea;
2295 return;
2296 }
2297
2298 mSummedUpArea = 0;
2299 const int maxIndex = mX.size();
2300 if ( maxIndex < 2 )
2301 {
2303 return;
2304 }
2305
2306 const double *x = mX.constData();
2307 const double *y = mY.constData();
2308 double prevX = *x++;
2309 double prevY = *y++;
2310 for ( int i = 1; i < maxIndex; ++i )
2311 {
2312 mSummedUpArea += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
2313 prevX = *x++;
2314 prevY = *y++;
2315 }
2316 mSummedUpArea *= 0.5;
2317
2319 sum += mSummedUpArea;
2320}
2321
2322void QgsLineString::sumUpArea3D( double &sum ) const
2323{
2325 {
2326 sum += mSummedUpArea3D;
2327 return;
2328 }
2329
2330 // No Z component. Fallback to the 2D version
2331 if ( mZ.isEmpty() )
2332 {
2333 double area2D = 0;
2334 sumUpArea( area2D );
2335 mSummedUpArea3D = area2D;
2337 sum += mSummedUpArea3D;
2338 return;
2339 }
2340
2341 mSummedUpArea3D = 0;
2342
2343 // Look for a reference unit normal
2344 QgsPoint ptA;
2345 QgsPoint ptB;
2346 QgsPoint ptC;
2347 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
2348 {
2350 return;
2351 }
2352
2353 QgsVector3D vAB = QgsVector3D( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
2354 QgsVector3D vAC = QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() );
2355 QgsVector3D planeNormal = QgsVector3D::crossProduct( vAB, vAC );
2356
2357 // Ensure a Consistent orientation: prioritize Z+, then Y+, then X+
2358 if ( !qgsDoubleNear( planeNormal.z(), 0.0 ) )
2359 {
2360 if ( planeNormal.z() < 0 )
2361 {
2362 planeNormal = -planeNormal;
2363 }
2364 }
2365 else if ( !qgsDoubleNear( planeNormal.y(), 0.0 ) )
2366 {
2367 if ( planeNormal.y() < 0 )
2368 planeNormal = -planeNormal;
2369 }
2370 else
2371 {
2372 if ( planeNormal.x() < 0 )
2373 planeNormal = -planeNormal;
2374 }
2375 planeNormal.normalize();
2376
2377 const double *x = mX.constData();
2378 const double *y = mY.constData();
2379 const double *z = mZ.constData();
2380
2381 double prevX = *x++;
2382 double prevY = *y++;
2383 double prevZ = *z++;
2384
2385 double normalX = 0.0;
2386 double normalY = 0.0; // #spellok - Y component of normal vector
2387 double normalZ = 0.0;
2388
2389 for ( unsigned int i = 1; i < mX.size(); ++i )
2390 {
2391 normalX += prevY * ( *z - prevZ ) - prevZ * ( *y - prevY );
2392 normalY += prevZ * ( *x - prevX ) - prevX * ( *z - prevZ ); // #spellok
2393 normalZ += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
2394
2395 prevX = *x++;
2396 prevY = *y++;
2397 prevZ = *z++;
2398 }
2399
2400 mSummedUpArea3D = 0.5 * ( normalX * planeNormal.x() + normalY * planeNormal.y() + normalZ * planeNormal.z() ); // #spellok
2401
2403 sum += mSummedUpArea3D;
2404}
2405
2406void QgsLineString::importVerticesFromWkb( const QgsConstWkbPtr &wkb )
2407{
2408 bool hasZ = is3D();
2409 bool hasM = isMeasure();
2410 int nVertices = 0;
2411 wkb >> nVertices;
2412 mX.resize( nVertices );
2413 mY.resize( nVertices );
2414 hasZ ? mZ.resize( nVertices ) : mZ.clear();
2415 hasM ? mM.resize( nVertices ) : mM.clear();
2416 double *x = mX.data();
2417 double *y = mY.data();
2418 double *m = hasM ? mM.data() : nullptr;
2419 double *z = hasZ ? mZ.data() : nullptr;
2420 for ( int i = 0; i < nVertices; ++i )
2421 {
2422 wkb >> *x++;
2423 wkb >> *y++;
2424 if ( hasZ )
2425 {
2426 wkb >> *z++;
2427 }
2428 if ( hasM )
2429 {
2430 wkb >> *m++;
2431 }
2432 }
2433 clearCache(); //set bounding box invalid
2434}
2435
2436/***************************************************************************
2437 * This class is considered CRITICAL and any change MUST be accompanied with
2438 * full unit tests.
2439 * See details in QEP #17
2440 ****************************************************************************/
2441
2443{
2444 if ( numPoints() < 1 || isClosed() )
2445 {
2446 return;
2447 }
2448 addVertex( startPoint() );
2449}
2450
2452{
2453 if ( mX.count() < 2 )
2454 {
2455 //undefined
2456 return 0.0;
2457 }
2458
2459 if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
2460 {
2461 if ( isClosed() )
2462 {
2463 double previousX = mX.at( numPoints() - 2 );
2464 double previousY = mY.at( numPoints() - 2 );
2465 double currentX = mX.at( 0 );
2466 double currentY = mY.at( 0 );
2467 double afterX = mX.at( 1 );
2468 double afterY = mY.at( 1 );
2469 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
2470 }
2471 else if ( vertex.vertex == 0 )
2472 {
2473 return QgsGeometryUtilsBase::lineAngle( mX.at( 0 ), mY.at( 0 ), mX.at( 1 ), mY.at( 1 ) );
2474 }
2475 else
2476 {
2477 int a = numPoints() - 2;
2478 int b = numPoints() - 1;
2479 return QgsGeometryUtilsBase::lineAngle( mX.at( a ), mY.at( a ), mX.at( b ), mY.at( b ) );
2480 }
2481 }
2482 else
2483 {
2484 double previousX = mX.at( vertex.vertex - 1 );
2485 double previousY = mY.at( vertex.vertex - 1 );
2486 double currentX = mX.at( vertex.vertex );
2487 double currentY = mY.at( vertex.vertex );
2488 double afterX = mX.at( vertex.vertex + 1 );
2489 double afterY = mY.at( vertex.vertex + 1 );
2490 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
2491 }
2492}
2493
2495{
2496 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 1 )
2497 return 0.0;
2498
2499 double dx = mX.at( startVertex.vertex + 1 ) - mX.at( startVertex.vertex );
2500 double dy = mY.at( startVertex.vertex + 1 ) - mY.at( startVertex.vertex );
2501 return std::sqrt( dx * dx + dy * dy );
2502}
2503
2504/***************************************************************************
2505 * This class is considered CRITICAL and any change MUST be accompanied with
2506 * full unit tests.
2507 * See details in QEP #17
2508 ****************************************************************************/
2509
2510bool QgsLineString::addZValue( double zValue )
2511{
2512 if ( QgsWkbTypes::hasZ( mWkbType ) )
2513 return false;
2514
2515 clearCache();
2517 {
2519 return true;
2520 }
2521
2523
2524 mZ.clear();
2525 int nPoints = numPoints();
2526 mZ.reserve( nPoints );
2527 for ( int i = 0; i < nPoints; ++i )
2528 {
2529 mZ << zValue;
2530 }
2531 return true;
2532}
2533
2534bool QgsLineString::addMValue( double mValue )
2535{
2536 if ( QgsWkbTypes::hasM( mWkbType ) )
2537 return false;
2538
2539 clearCache();
2541 {
2543 return true;
2544 }
2545
2547 {
2549 }
2550 else
2551 {
2553 }
2554
2555 mM.clear();
2556 int nPoints = numPoints();
2557 mM.reserve( nPoints );
2558 for ( int i = 0; i < nPoints; ++i )
2559 {
2560 mM << mValue;
2561 }
2562 return true;
2563}
2564
2566{
2567 if ( !is3D() )
2568 return false;
2569
2570 clearCache();
2572 mZ.clear();
2573 return true;
2574}
2575
2577{
2578 if ( !isMeasure() )
2579 return false;
2580
2581 clearCache();
2583 mM.clear();
2584 return true;
2585}
2586
2588{
2589 std::swap( mX, mY );
2590 clearCache();
2591}
2592
2594{
2595 if ( type == mWkbType )
2596 return true;
2597
2598 clearCache();
2599 if ( type == Qgis::WkbType::LineString25D )
2600 {
2601 //special handling required for conversion to LineString25D
2602 dropMValue();
2603 addZValue( std::numeric_limits<double>::quiet_NaN() );
2605 return true;
2606 }
2607 else
2608 {
2609 return QgsCurve::convertTo( type );
2610 }
2611}
2612
2614{
2615 if ( !transformer )
2616 return false;
2617
2618 bool hasZ = is3D();
2619 bool hasM = isMeasure();
2620 int size = mX.size();
2621
2622 double *srcX = mX.data();
2623 double *srcY = mY.data();
2624 double *srcM = hasM ? mM.data() : nullptr;
2625 double *srcZ = hasZ ? mZ.data() : nullptr;
2626
2627 bool res = true;
2628 for ( int i = 0; i < size; ++i )
2629 {
2630 double x = *srcX;
2631 double y = *srcY;
2632 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
2633 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
2634 if ( !transformer->transformPoint( x, y, z, m ) )
2635 {
2636 res = false;
2637 break;
2638 }
2639
2640 *srcX++ = x;
2641 *srcY++ = y;
2642 if ( hasM )
2643 *srcM++ = m;
2644 if ( hasZ )
2645 *srcZ++ = z;
2646
2647 if ( feedback && feedback->isCanceled() )
2648 {
2649 res = false;
2650 break;
2651 }
2652 }
2653 clearCache();
2654 return res;
2655}
2656
2657void QgsLineString::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
2658{
2659 bool hasZ = is3D();
2660 bool hasM = isMeasure();
2661 int size = mX.size();
2662
2663 double *srcX = mX.data();
2664 double *srcY = mY.data();
2665 double *srcM = hasM ? mM.data() : nullptr;
2666 double *srcZ = hasZ ? mZ.data() : nullptr;
2667
2668 double *destX = srcX;
2669 double *destY = srcY;
2670 double *destM = srcM;
2671 double *destZ = srcZ;
2672
2673 int filteredPoints = 0;
2674 for ( int i = 0; i < size; ++i )
2675 {
2676 double x = *srcX++;
2677 double y = *srcY++;
2678 double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
2679 double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
2680
2681 if ( filter( QgsPoint( x, y, z, m ) ) )
2682 {
2683 filteredPoints++;
2684 *destX++ = x;
2685 *destY++ = y;
2686 if ( hasM )
2687 *destM++ = m;
2688 if ( hasZ )
2689 *destZ++ = z;
2690 }
2691 }
2692
2693 mX.resize( filteredPoints );
2694 mY.resize( filteredPoints );
2695 if ( hasZ )
2696 mZ.resize( filteredPoints );
2697 if ( hasM )
2698 mM.resize( filteredPoints );
2699
2700 clearCache();
2701}
2702
2703void QgsLineString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
2704{
2705 bool hasZ = is3D();
2706 bool hasM = isMeasure();
2707 int size = mX.size();
2708
2709 double *srcX = mX.data();
2710 double *srcY = mY.data();
2711 double *srcM = hasM ? mM.data() : nullptr;
2712 double *srcZ = hasZ ? mZ.data() : nullptr;
2713
2714 for ( int i = 0; i < size; ++i )
2715 {
2716 double x = *srcX;
2717 double y = *srcY;
2718 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
2719 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
2720 QgsPoint res = transform( QgsPoint( x, y, z, m ) );
2721 *srcX++ = res.x();
2722 *srcY++ = res.y();
2723 if ( hasM )
2724 *srcM++ = res.m();
2725 if ( hasZ )
2726 *srcZ++ = res.z();
2727 }
2728 clearCache();
2729}
2730
2731
2732std::unique_ptr< QgsLineString > QgsLineString::measuredLine( double start, double end ) const
2733{
2734 const int nbpoints = numPoints();
2735 std::unique_ptr< QgsLineString > cloned( clone() );
2736
2737 if ( !cloned->convertTo( QgsWkbTypes::addM( mWkbType ) ) )
2738 {
2739 return cloned;
2740 }
2741
2742 if ( isEmpty() || ( nbpoints < 2 ) )
2743 {
2744 return cloned;
2745 }
2746
2747 const double range = end - start;
2748 double lineLength = length();
2749 double lengthSoFar = 0.0;
2750
2751
2752 double *mOut = cloned->mM.data();
2753 *mOut++ = start;
2754 for ( int i = 1; i < nbpoints; ++i )
2755 {
2756 lengthSoFar += QgsGeometryUtilsBase::distance2D( mX[i - 1], mY[i - 1], mX[i], mY[i] );
2757 if ( lineLength > 0.0 )
2758 *mOut++ = start + range * lengthSoFar / lineLength;
2759 else if ( lineLength == 0.0 && nbpoints > 1 )
2760 *mOut++ = start + range * i / ( nbpoints - 1 );
2761 else
2762 *mOut++ = 0.0;
2763 }
2764
2765 return cloned;
2766}
2767
2768std::unique_ptr< QgsLineString > QgsLineString::interpolateM( bool use3DDistance ) const
2769{
2770 if ( !isMeasure() )
2771 return nullptr;
2772
2773 const int totalPoints = numPoints();
2774 if ( totalPoints < 2 )
2775 return std::unique_ptr< QgsLineString >( clone() );
2776
2777 const double *xData = mX.constData();
2778 const double *yData = mY.constData();
2779 const double *mData = mM.constData();
2780 const double *zData = is3D() ? mZ.constData() : nullptr;
2781 use3DDistance &= static_cast< bool >( zData );
2782
2783 QVector< double > xOut( totalPoints );
2784 QVector< double > yOut( totalPoints );
2785 QVector< double > mOut( totalPoints );
2786 QVector< double > zOut( static_cast< bool >( zData ) ? totalPoints : 0 );
2787
2788 double *xOutData = xOut.data();
2789 double *yOutData = yOut.data();
2790 double *mOutData = mOut.data();
2791 double *zOutData = static_cast< bool >( zData ) ? zOut.data() : nullptr;
2792
2793 int i = 0;
2794 double currentSegmentLength = 0;
2795 double lastValidM = std::numeric_limits< double >::quiet_NaN();
2796 double prevX = *xData;
2797 double prevY = *yData;
2798 double prevZ = zData ? *zData : 0;
2799 while ( i < totalPoints )
2800 {
2801 double thisX = *xData++;
2802 double thisY = *yData++;
2803 double thisZ = zData ? *zData++ : 0;
2804 double thisM = *mData++;
2805
2806 currentSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( prevX, prevY, prevZ, thisX, thisY, thisZ ) : QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
2807
2808 if ( !std::isnan( thisM ) )
2809 {
2810 *xOutData++ = thisX;
2811 *yOutData++ = thisY;
2812 *mOutData++ = thisM;
2813 if ( zOutData )
2814 *zOutData++ = thisZ;
2815 lastValidM = thisM;
2816 }
2817 else if ( i == 0 )
2818 {
2819 // nan m value at start of line, read ahead to find first non-nan value and backfill
2820 int j = 0;
2821 double scanAheadM = thisM;
2822 while ( i + j + 1 < totalPoints && std::isnan( scanAheadM ) )
2823 {
2824 scanAheadM = mData[j];
2825 ++j;
2826 }
2827 if ( std::isnan( scanAheadM ) )
2828 {
2829 // no valid m values in line
2830 return nullptr;
2831 }
2832 *xOutData++ = thisX;
2833 *yOutData++ = thisY;
2834 *mOutData++ = scanAheadM;
2835 if ( zOutData )
2836 *zOutData++ = thisZ;
2837 for ( ; i < j; ++i )
2838 {
2839 thisX = *xData++;
2840 thisY = *yData++;
2841 *xOutData++ = thisX;
2842 *yOutData++ = thisY;
2843 *mOutData++ = scanAheadM;
2844 mData++;
2845 if ( zOutData )
2846 *zOutData++ = *zData++;
2847 }
2848 lastValidM = scanAheadM;
2849 }
2850 else
2851 {
2852 // nan m value in middle of line, read ahead till next non-nan value and interpolate
2853 int j = 0;
2854 double scanAheadX = thisX;
2855 double scanAheadY = thisY;
2856 double scanAheadZ = thisZ;
2857 double distanceToNextValidM = currentSegmentLength;
2858 std::vector< double > scanAheadSegmentLengths;
2859 scanAheadSegmentLengths.emplace_back( currentSegmentLength );
2860
2861 double nextValidM = std::numeric_limits< double >::quiet_NaN();
2862 while ( i + j < totalPoints - 1 )
2863 {
2864 double nextScanAheadX = xData[j];
2865 double nextScanAheadY = yData[j];
2866 double nextScanAheadZ = zData ? zData[j] : 0;
2867 double nextScanAheadM = mData[j];
2868 const double scanAheadSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( scanAheadX, scanAheadY, scanAheadZ, nextScanAheadX, nextScanAheadY, nextScanAheadZ )
2869 : QgsGeometryUtilsBase::distance2D( scanAheadX, scanAheadY, nextScanAheadX, nextScanAheadY );
2870 scanAheadSegmentLengths.emplace_back( scanAheadSegmentLength );
2871 distanceToNextValidM += scanAheadSegmentLength;
2872
2873 if ( !std::isnan( nextScanAheadM ) )
2874 {
2875 nextValidM = nextScanAheadM;
2876 break;
2877 }
2878
2879 scanAheadX = nextScanAheadX;
2880 scanAheadY = nextScanAheadY;
2881 scanAheadZ = nextScanAheadZ;
2882 ++j;
2883 }
2884
2885 if ( std::isnan( nextValidM ) )
2886 {
2887 // no more valid m values, so just fill remainder of vertices with previous valid m value
2888 *xOutData++ = thisX;
2889 *yOutData++ = thisY;
2890 *mOutData++ = lastValidM;
2891 if ( zOutData )
2892 *zOutData++ = thisZ;
2893 ++i;
2894 for ( ; i < totalPoints; ++i )
2895 {
2896 *xOutData++ = *xData++;
2897 *yOutData++ = *yData++;
2898 *mOutData++ = lastValidM;
2899 if ( zOutData )
2900 *zOutData++ = *zData++;
2901 }
2902 break;
2903 }
2904 else
2905 {
2906 // interpolate along segments
2907 const double delta = ( nextValidM - lastValidM ) / distanceToNextValidM;
2908 *xOutData++ = thisX;
2909 *yOutData++ = thisY;
2910 *mOutData++ = lastValidM + delta * scanAheadSegmentLengths[0];
2911 double totalScanAheadLength = scanAheadSegmentLengths[0];
2912 if ( zOutData )
2913 *zOutData++ = thisZ;
2914 for ( int k = 1; k <= j; ++i, ++k )
2915 {
2916 thisX = *xData++;
2917 thisY = *yData++;
2918 *xOutData++ = thisX;
2919 *yOutData++ = thisY;
2920 totalScanAheadLength += scanAheadSegmentLengths[k];
2921 *mOutData++ = lastValidM + delta * totalScanAheadLength;
2922 mData++;
2923 if ( zOutData )
2924 *zOutData++ = *zData++;
2925 }
2926 lastValidM = nextValidM;
2927 }
2928 }
2929
2930 prevX = thisX;
2931 prevY = thisY;
2932 prevZ = thisZ;
2933 ++i;
2934 }
2935 return std::make_unique< QgsLineString >( xOut, yOut, zOut, mOut );
2936}
2937
2939{
2940 // Convert QgsVertexId to simple vertex numbers for linestrings (single ring, single part)
2941 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
2942 return -1.0;
2943
2944 const int fromVertexNumber = fromVertex.vertex;
2945 const int toVertexNumber = toVertex.vertex;
2946
2947 // Ensure fromVertex < toVertex for simplicity
2948 if ( fromVertexNumber > toVertexNumber )
2949 {
2950 return distanceBetweenVertices( QgsVertexId( 0, 0, toVertexNumber ), QgsVertexId( 0, 0, fromVertexNumber ) );
2951 }
2952
2953 const int nPoints = numPoints();
2954 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
2955 return -1.0;
2956
2957 if ( fromVertexNumber == toVertexNumber )
2958 return 0.0;
2959
2960 const bool is3DGeometry = is3D();
2961 const double *xData = mX.constData();
2962 const double *yData = mY.constData();
2963 const double *zData = is3DGeometry ? mZ.constData() : nullptr;
2964 double totalDistance = 0.0;
2965
2966 // For linestring, just accumulate Euclidean distances between consecutive points
2967 for ( int i = fromVertexNumber; i < toVertexNumber; ++i )
2968 {
2969 double dx = xData[i + 1] - xData[i];
2970 double dy = yData[i + 1] - yData[i];
2971 double dz = 0.0;
2972
2973 if ( is3DGeometry )
2974 {
2975 dz = zData[i + 1] - zData[i];
2976 }
2977
2978 totalDistance += std::sqrt( dx * dx + dy * dy + dz * dz );
2979 }
2980
2981 return totalDistance;
2982}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2196
VertexType
Types of vertex.
Definition qgis.h:3246
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3247
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ LineString25D
LineString25D.
Definition qgis.h:362
@ LineStringM
LineStringM.
Definition qgis.h:330
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ LineStringZM
LineStringZM.
Definition qgis.h:346
@ Unknown
Unknown.
Definition qgis.h:295
@ PointM
PointM.
Definition qgis.h:329
@ PointZ
PointZ.
Definition qgis.h:313
@ Point25D
Point25D.
Definition qgis.h:361
@ PointZM
PointZM.
Definition qgis.h:345
@ LineStringZ
LineStringZ.
Definition qgis.h:314
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2831
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.
virtual bool convertTo(Qgis::WkbType type)
Converts the geometry to a specified type.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
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.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, Qgis::WkbType baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
virtual bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
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 contains(const QgsBox3D &other) const
Returns true when box contains other box.
Definition qgsbox3d.cpp:164
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:388
Compound curve geometry type.
void addCurve(QgsCurve *c, bool extendPrevious=false)
Adds a curve to the geometry (takes ownership).
A const WKB pointer.
Definition qgswkbptr.h:211
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:60
Handles coordinate transforms between two coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition qgscurve.cpp:298
double mSummedUpArea3D
Definition qgscurve.h:408
bool mHasCachedSummedUpArea
Definition qgscurve.h:405
virtual bool isRing() const
Returns true if the curve is a ring.
Definition qgscurve.cpp:65
bool mHasCachedSummedUpArea3D
Definition qgscurve.h:407
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
Definition qgscurve.cpp:247
bool snapToGridPrivate(double hSpacing, double vSpacing, double dSpacing, double mSpacing, const QVector< double > &srcX, const QVector< double > &srcY, const QVector< double > &srcZ, const QVector< double > &srcM, QVector< double > &outX, QVector< double > &outY, QVector< double > &outZ, QVector< double > &outM, bool removeRedundantPoints) const
Helper function for QgsCurve subclasses to snap to grids.
Definition qgscurve.cpp:323
QgsBox3D mBoundingBox
Cached bounding box.
Definition qgscurve.h:403
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
Definition qgscurve.cpp:266
double mSummedUpArea
Definition qgscurve.h:406
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
Convenience functions for geometry utils.
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
static double averageAngle(double x1, double y1, double x2, double y2, double x3, double y3)
Calculates the average angle (in radians) between the two linear segments from (x1,...
static double distance3D(double x1, double y1, double z1, double x2, double y2, double z2)
Returns the 3D distance between (x1, y1, z1) and (x2, y2, z2).
static void interpolatePointOnCubicBezier(double p0x, double p0y, double p0z, double p0m, double p1x, double p1y, double p1z, double p1m, double p2x, double p2y, double p2z, double p2m, double p3x, double p3y, double p3z, double p3m, double t, bool hasZ, bool hasM, double &outX, double &outY, double &outZ, double &outM)
Evaluates a point on a cubic Bézier curve defined by four control points.
static double sqrDistToLine(double ptX, double ptY, double x1, double y1, double x2, double y2, double &minDistX, double &minDistY, double epsilon)
Returns the squared distance between a point and a line.
static int leftOfLine(const double x, const double y, const double x1, const double y1, const double x2, const double y2)
Returns a value < 0 if the point (x, y) is left of the line from (x1, y1) -> (x2, y2).
static json pointsToJson(const QgsPointSequence &points, int precision)
Returns coordinates as json object.
static void pointsToWKB(QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure, QgsAbstractGeometry::WkbFlags flags)
Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }.
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 (...
static QgsPointSequence pointsFromWKT(const QString &wktCoordinateList, bool is3D, bool isMeasure)
Returns a list of points contained in a WKT string.
static QDomElement pointsToGML2(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY)
Returns a gml::coordinates DOM element.
static bool checkWeaklyFor3DPlane(const QgsAbstractGeometry *geom, QgsPoint &pt1, QgsPoint &pt2, QgsPoint &pt3, double epsilon=std::numeric_limits< double >::epsilon())
Checks if a 3D geometry has a plane defined by at least 3 non-collinear points.
static QDomElement pointsToGML3(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY)
Returns a gml::posList DOM element.
static QString pointsToWKT(const QgsPointSequence &points, int precision, bool is3D, bool isMeasure)
Returns a WKT coordinate list.
Represents a single 2D line segment, consisting of a 2D start and end vertex only.
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
static std::unique_ptr< QgsLineString > fromBezierCurve(const QgsPoint &start, const QgsPoint &controlPoint1, const QgsPoint &controlPoint2, const QgsPoint &end, int segments=30)
Returns a new linestring created by segmentizing the bezier curve between start and end,...
bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const override
Returns the point and vertex id of a point within the curve.
bool isClosed() const override
Returns true if the curve is closed.
void swapXy() override
Swaps the x and y coordinates from the geometry.
const double * yData() const
Returns a const pointer to the y vertex data.
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
QVector< QgsLineString * > splitToDisjointXYParts() const
Divides the linestring into parts that don't share any points or lines.
const double * xData() const
Returns a const pointer to the x vertex data.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
double length3D() const
Returns the length in 3D world of the line string.
void points(QgsPointSequence &pt) const override
Returns a list of points within the curve.
static std::unique_ptr< QgsLineString > fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
QgsLineString * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
int dimension() const override
Returns the inherent dimension of the geometry.
void sumUpArea(double &sum) const override
Calculates the shoelace/triangle formula sum for the points in the linestring.
void clear() override
Clears the geometry, ie reset it to a null geometry.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
QgsPoint startPoint() const override
Returns the starting point of the curve.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
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 isEmpty() const override
Returns true if the geometry is empty.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
int numPoints() const override
Returns the number of points in the curve.
void sumUpArea3D(double &sum) const override
Calculates the shoelace/triangle formula sum for the points in the linestring.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
int nCoordinates() const override
Returns the number of nodes contained in the geometry.
QgsLineString()
Constructor for an empty linestring geometry.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
QString asKml(int precision=17) const override
Returns a KML representation of the geometry.
std::unique_ptr< QgsLineString > interpolateM(bool use3DDistance=true) const
Returns a copy of this line with all missing (NaN) m values interpolated from m values of surrounding...
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
void setPoints(size_t size, const double *x, const double *y, const double *z=nullptr, const double *m=nullptr)
Resets the line string to match the specified point data.
QgsPoint centroid() const override
Returns the centroid 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.
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
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...
void scroll(int firstVertexIndex) final
Scrolls the curve vertices so that they start with the vertex at the given index.
bool boundingBoxIntersects(const QgsRectangle &rectangle) const override
Returns true if the bounding box of this geometry intersects with a rectangle.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
QString geometryType() const override
Returns a unique string representing the geometry type.
std::unique_ptr< QgsLineString > measuredLine(double start, double end) const
Re-write the measure ordinate (or add one, if it isn't already there) interpolating the measure betwe...
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
QgsPoint endPoint() const override
Returns the end point of the curve.
void setYAt(int index, double y)
Sets the y-coordinate of the specified node in the line string.
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.
QgsLineString * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
void close()
Closes the line string by appending the first point to the end of the line, if it is not already clos...
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
int indexOf(const QgsPoint &point) const final
Returns the index of the first vertex matching the given point, or -1 if a matching vertex is not fou...
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
void extend(double startDistance, double endDistance)
Extends the line geometry by extrapolating out the start or end of the line by a specified distance.
QgsCompoundCurve * toCurveType() const override
Returns the geometry converted to the more generic curve type QgsCompoundCurve.
double distanceBetweenVertices(QgsVertexId fromVertex, QgsVertexId toVertex) const override
Returns the distance along the curve between two vertices.
void append(const QgsLineString *line)
Appends the contents of another line string to the end of this line string.
QgsLineString * curveSubstring(double startDistance, double endDistance) const override
Returns a new curve representing a substring of this curve.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex(int index) const final
Splits the curve at the specified vertex index, returning two curves which represent the portion of t...
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
bool lineLocatePointByM(double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance=true) const
Attempts to locate a point on the linestring by m value.
void visitPointsByRegularDistance(double distance, const std::function< bool(double x, double y, double z, double m, double startSegmentX, double startSegmentY, double startSegmentZ, double startSegmentM, double endSegmentX, double endSegmentY, double endSegmentZ, double endSegmentM) > &visitPoint) const
Visits regular points along the linestring, spaced by distance.
void setXAt(int index, double x)
Sets the x-coordinate of the specified node in the line string.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
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.
QVector< QgsVertexId > collectDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) const
Returns a list of any duplicate nodes contained in the geometry, within the specified tolerance.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
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...
bool dropMValue() override
Drops any measure values which exist in the geometry.
Q_DECL_DEPRECATED QgsBox3D calculateBoundingBox3d() const
Calculates the minimal 3D bounding box for the geometry.
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter) override
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns a WKB representation of the geometry.
void transformVertices(const std::function< QgsPoint(const QgsPoint &) > &transform) override
Transforms the vertices from the geometry in place, applying the transform function to every vertex.
QgsLineString * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
bool convertTo(Qgis::WkbType type) override
Converts the geometry to a specified type.
bool isClosed2D() const override
Returns true if the curve is closed.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
QgsPoint * interpolatePoint(double distance) const override
Returns an interpolated point on the curve at the specified distance.
QgsLineString * 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.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
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:387
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:376
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
void normalize()
Normalizes the current vector in place.
static QgsVector3D crossProduct(const QgsVector3D &v1, const QgsVector3D &v2)
Returns the cross product of two vectors.
WKB pointer handler.
Definition qgswkbptr.h:47
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 zmType(Qgis::WkbType type, bool hasZ, bool hasM)
Returns the modified input geometry type according to hasZ / hasM.
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:7140
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7222
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
void simplifySection(int i, int j, const double *x, const double *y, std::vector< bool > &usePoint, const double distanceToleranceSquared, const double epsilon)
QLineF segment(int index, QRectF rect, double radius)
double distance2D(const QgsPolylineXY &coords)
Definition qgstracer.cpp:50
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:94
int ring
Ring number.
Definition qgsvertexid.h:97