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