QGIS API Documentation 4.3.0-Master (59e977eef4b)
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 "qgsbox3d.h"
26#include "qgscompoundcurve.h"
28#include "qgsgeometryutils.h"
30#include "qgslinesegment.h"
31#include "qgsvector3d.h"
32
33#include <QDomDocument>
34#include <QJsonObject>
35#include <QPainter>
36#include <QString>
37
38using namespace Qt::StringLiterals;
39
40/***************************************************************************
41 * This class is considered CRITICAL and any change MUST be accompanied with
42 * full unit tests.
43 * See details in QEP #17
44 ****************************************************************************/
45
50
51QgsLineString::QgsLineString( const QVector<QgsPoint> &points )
52{
53 if ( points.isEmpty() )
54 {
56 return;
57 }
58 Qgis::WkbType ptType = points.at( 0 ).wkbType();
60 mX.resize( points.count() );
61 mY.resize( points.count() );
62 double *x = mX.data();
63 double *y = mY.data();
64 double *z = nullptr;
65 double *m = nullptr;
67 {
68 mZ.resize( points.count() );
69 z = mZ.data();
70 }
72 {
73 mM.resize( points.count() );
74 m = mM.data();
75 }
76
77 for ( const QgsPoint &pt : points )
78 {
79 *x++ = pt.x();
80 *y++ = pt.y();
81 if ( z )
82 *z++ = pt.z();
83 if ( m )
84 *m++ = pt.m();
85 }
86}
87
88QgsLineString::QgsLineString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m, bool is25DType )
89{
91 int pointCount = std::min( x.size(), y.size() );
92 if ( x.size() == pointCount )
93 {
94 mX = x;
95 }
96 else
97 {
98 mX = x.mid( 0, pointCount );
99 }
100 if ( y.size() == pointCount )
101 {
102 mY = y;
103 }
104 else
105 {
106 mY = y.mid( 0, pointCount );
107 }
108 if ( !z.isEmpty() && z.count() >= pointCount )
109 {
111 if ( z.size() == pointCount )
112 {
113 mZ = z;
114 }
115 else
116 {
117 mZ = z.mid( 0, pointCount );
118 }
119 }
120 if ( !m.isEmpty() && m.count() >= pointCount )
121 {
123 if ( m.size() == pointCount )
124 {
125 mM = m;
126 }
127 else
128 {
129 mM = m.mid( 0, pointCount );
130 }
131 }
132}
133
135{
137 mX.resize( 2 );
138 mX[0] = p1.x();
139 mX[1] = p2.x();
140 mY.resize( 2 );
141 mY[0] = p1.y();
142 mY[1] = p2.y();
143 if ( p1.is3D() )
144 {
146 mZ.resize( 2 );
147 mZ[0] = p1.z();
148 mZ[1] = p2.z();
149 }
150 if ( p1.isMeasure() )
151 {
153 mM.resize( 2 );
154 mM[0] = p1.m();
155 mM[1] = p2.m();
156 }
157}
158
159QgsLineString::QgsLineString( const QVector<QgsPointXY> &points )
160{
162 mX.reserve( points.size() );
163 mY.reserve( points.size() );
164 for ( const QgsPointXY &p : points )
165 {
166 mX << p.x();
167 mY << p.y();
168 }
169}
170
172{
174 mX.resize( 2 );
175 mY.resize( 2 );
176 mX[0] = segment.startX();
177 mX[1] = segment.endX();
178 mY[0] = segment.startY();
179 mY[1] = segment.endY();
180}
181
182std::unique_ptr< QgsLineString > QgsLineString::fromBezierCurve( const QgsPoint &start, const QgsPoint &controlPoint1, const QgsPoint &controlPoint2, const QgsPoint &end, int segments )
183{
184 if ( segments == 0 )
185 return std::make_unique< QgsLineString >();
186
187 QVector<double> x( segments + 1 );
188 QVector<double> y( segments + 1 );
189 QVector<double> z;
190 QVector<double> m;
191
192 const bool hasZ = start.is3D() && controlPoint1.is3D() && controlPoint2.is3D() && end.is3D();
193 if ( hasZ )
194 {
195 z.resize( segments + 1 );
196 }
197
198 const bool hasM = start.isMeasure() && controlPoint1.isMeasure() && controlPoint2.isMeasure() && end.isMeasure();
199 if ( hasM )
200 {
201 m.resize( segments + 1 );
202 }
203
204 double *xData = x.data();
205 double *yData = y.data();
206 double *zData = z.data(); // will be nullptr if !hasZ
207 double *mData = m.data(); // will be nullptr if !hasM
208
209 const double step = 1.0 / segments;
210
211 for ( int i = 0; i <= segments; ++i )
212 {
213 const double t = i * step;
214
215 double ix, iy; // interpolated x, y
216 double iz = std::numeric_limits<double>::quiet_NaN();
217 double im = std::numeric_limits<double>::quiet_NaN();
218
220 start.x(),
221 start.y(),
222 start.z(),
223 start.m(),
224 controlPoint1.x(),
225 controlPoint1.y(),
226 controlPoint1.z(),
227 controlPoint1.m(),
228 controlPoint2.x(),
229 controlPoint2.y(),
230 controlPoint2.z(),
231 controlPoint2.m(),
232 end.x(),
233 end.y(),
234 end.z(),
235 end.m(),
236 t,
237 hasZ,
238 hasM,
239 ix,
240 iy,
241 iz,
242 im
243 );
244
245 *xData++ = ix;
246 *yData++ = iy;
247 if ( hasZ )
248 *zData++ = iz;
249 if ( hasM )
250 *mData++ = im;
251 }
252
253 return std::make_unique< QgsLineString >( x, y, z, m );
254}
255
256std::unique_ptr< QgsLineString > QgsLineString::fromQPolygonF( const QPolygonF &polygon )
257{
258 QVector< double > x;
259 QVector< double > y;
260 x.resize( polygon.count() );
261 y.resize( polygon.count() );
262 double *xData = x.data();
263 double *yData = y.data();
264
265 const QPointF *src = polygon.data();
266 for ( int i = 0; i < polygon.size(); ++i )
267 {
268 *xData++ = src->x();
269 *yData++ = src->y();
270 src++;
271 }
272
273 return std::make_unique< QgsLineString >( x, y );
274}
275
277{
278 return new QgsLineString( *this );
279}
280
286
287int QgsLineString::indexOf( const QgsPoint &point ) const
288{
289 const int size = mX.size();
290 if ( size == 0 )
291 return -1;
292
293 const double *x = mX.constData();
294 const double *y = mY.constData();
295 const bool useZ = is3D();
296 const bool useM = isMeasure();
297 const double *z = useZ ? mZ.constData() : nullptr;
298 const double *m = useM ? mM.constData() : nullptr;
299
300 for ( int i = 0; i < size; ++i )
301 {
302 if ( qgsDoubleNear( *x, point.x() ) && qgsDoubleNear( *y, point.y() ) && ( !useZ || qgsDoubleNear( *z, point.z() ) ) && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
303 return i;
304
305 x++;
306 y++;
307 if ( useZ )
308 z++;
309 if ( useM )
310 m++;
311 }
312 return -1;
313}
314
315bool QgsLineString::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
316{
317 if ( !isEmpty() && ( numPoints() < 2 ) )
318 {
319 error = QObject::tr( "LineString has less than 2 points and is not empty." );
320 return false;
321 }
322 return QgsCurve::isValid( error, flags );
323}
324
325QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
326{
327 // prepare result
328 std::unique_ptr<QgsLineString> result { createEmptyWithSameType() };
329
330 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM, 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 ) && qgsDoubleNear( currentY, prevY, epsilon ) && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
355 {
356 result = true;
357 // remove point
358 mX.removeAt( i );
359 mY.removeAt( i );
360 if ( hasZ )
361 mZ.removeAt( i );
362 remaining--;
363 }
364 else
365 {
366 prevX = currentX;
367 prevY = currentY;
368 prevZ = currentZ;
369 i++;
370 }
371 }
372 return result;
373}
374
376{
377 if ( mX.empty() )
378 return false;
379
380 return qgsDoubleNear( mX.first(), mX.last() ) && qgsDoubleNear( mY.first(), mY.last() );
381}
382
384{
385 bool closed = isClosed2D();
386
387 if ( is3D() && closed )
388 closed &= qgsDoubleNear( mZ.first(), mZ.last() ) || ( std::isnan( mZ.first() ) && std::isnan( mZ.last() ) );
389 return closed;
390}
391
392// As `bool boundingBoxIntersects( const QgsBox3D &box3d )` and `bool boundingBoxIntersects( const QgsRectangle &rectangle )` are nearly
393// the same: if one of these functions is changed then remember to also update the other accordingly
395{
396 if ( mX.empty() )
397 return false;
398
399 if ( !mBoundingBox.isNull() )
400 {
401 return mBoundingBox.toRectangle().intersects( rectangle );
402 }
403 const int nb = mX.size();
404
405 // We are a little fancy here!
406 if ( nb > 40 )
407 {
408 // if a large number of vertices, take some sample vertices at 1/5th increments through the linestring
409 // and test whether any are inside the rectangle. Maybe we can shortcut a lot of iterations by doing this!
410 // (why 1/5th? it's picked so that it works nicely for polygon rings which are almost rectangles, so the vertex extremities
411 // will fall on approximately these vertex indices)
412 if ( rectangle.contains( mX.at( 0 ), mY.at( 0 ) )
413 || rectangle.contains( mX.at( static_cast< int >( nb * 0.2 ) ), mY.at( static_cast< int >( nb * 0.2 ) ) )
414 || rectangle.contains( mX.at( static_cast< int >( nb * 0.4 ) ), mY.at( static_cast< int >( nb * 0.4 ) ) )
415 || rectangle.contains( mX.at( static_cast< int >( nb * 0.6 ) ), mY.at( static_cast< int >( nb * 0.6 ) ) )
416 || rectangle.contains( mX.at( static_cast< int >( nb * 0.8 ) ), mY.at( static_cast< int >( nb * 0.8 ) ) )
417 || rectangle.contains( mX.at( nb - 1 ), mY.at( nb - 1 ) ) )
418 return true;
419 }
420
421 // Be even MORE fancy! Given that bounding box calculation is non-free, cached, and we don't
422 // already have it, we start performing the bounding box calculation while we are testing whether
423 // each point falls inside the rectangle. That way if we end up testing the majority of the points
424 // anyway, we can update the cached bounding box with the results we've calculated along the way
425 // and save future calls to calculate the bounding box!
426 double xmin = std::numeric_limits<double>::max();
427 double ymin = std::numeric_limits<double>::max();
428 double zmin = -std::numeric_limits<double>::max();
429 double xmax = -std::numeric_limits<double>::max();
430 double ymax = -std::numeric_limits<double>::max();
431 double zmax = -std::numeric_limits<double>::max();
432
433 const double *x = mX.constData();
434 const double *y = mY.constData();
435 const double *z = is3D() ? mZ.constData() : nullptr;
436 bool foundPointInRectangle = false;
437 for ( int i = 0; i < nb; ++i )
438 {
439 const double px = *x++;
440 xmin = std::min( xmin, px );
441 xmax = std::max( xmax, px );
442 const double py = *y++;
443 ymin = std::min( ymin, py );
444 ymax = std::max( ymax, py );
445 if ( z )
446 {
447 const double pz = *z++;
448 zmin = std::min( zmin, pz );
449 zmax = std::max( zmax, pz );
450 }
451
452 if ( !foundPointInRectangle && rectangle.contains( px, py ) )
453 {
454 foundPointInRectangle = true;
455
456 // now... we have a choice to make. If we've already looped through the majority of the points
457 // in this linestring then let's just continue to iterate through the remainder so that we can
458 // complete the overall bounding box calculation we've already mostly done. If however we're only
459 // just at the start of iterating the vertices, we shortcut out early and leave the bounding box
460 // uncalculated
461 if ( i < nb * 0.5 )
462 return true;
463 }
464 }
465
466 // at this stage we now know the overall bounding box of the linestring, so let's cache
467 // it so we don't ever have to calculate this again. We've done all the hard work anyway!
468 mBoundingBox = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax, false );
469
470 if ( foundPointInRectangle )
471 return true;
472
473 // NOTE: if none of the points in the line actually fell inside the rectangle, it doesn't
474 // exclude that the OVERALL bounding box of the linestring itself intersects the rectangle!!
475 // So we fall back to the parent class method which compares the overall bounding box against
476 // the rectangle... and this will be very cheap now that we've already calculated and cached
477 // the linestring's bounding box!
478 return QgsCurve::boundingBoxIntersects( rectangle );
479}
480
481// As `bool boundingBoxIntersects( const QgsBox3D &box3d )` and `bool boundingBoxIntersects( const QgsRectangle &rectangle )` are nearly
482// the same: if one of these functions is changed then remember to also update the other accordingly
484{
485 if ( mX.empty() )
486 return false;
487
488 if ( mZ.empty() )
489 return boundingBoxIntersects( box3d.toRectangle() );
490
491 if ( !mBoundingBox.isNull() )
492 {
493 return mBoundingBox.intersects( box3d );
494 }
495 const int nb = mX.size();
496
497 // We are a little fancy here!
498 if ( nb > 40 )
499 {
500 // if a large number of vertices, take some sample vertices at 1/5th increments through the linestring
501 // and test whether any are inside the rectangle. Maybe we can shortcut a lot of iterations by doing this!
502 // (why 1/5th? it's picked so that it works nicely for polygon rings which are almost rectangles, so the vertex extremities
503 // will fall on approximately these vertex indices)
504 if ( box3d.contains( mX.at( 0 ), mY.at( 0 ), mZ.at( 0 ) )
505 || 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 ) ) )
506 || 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 ) ) )
507 || 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 ) ) )
508 || 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 ) ) )
509 || box3d.contains( mX.at( nb - 1 ), mY.at( nb - 1 ), mZ.at( nb - 1 ) ) )
510 return true;
511 }
512
513 // Be even MORE fancy! Given that bounding box calculation is non-free, cached, and we don't
514 // already have it, we start performing the bounding box calculation while we are testing whether
515 // each point falls inside the rectangle. That way if we end up testing the majority of the points
516 // anyway, we can update the cached bounding box with the results we've calculated along the way
517 // and save future calls to calculate the bounding box!
518 double xmin = std::numeric_limits<double>::max();
519 double ymin = std::numeric_limits<double>::max();
520 double zmin = std::numeric_limits<double>::max();
521 double xmax = -std::numeric_limits<double>::max();
522 double ymax = -std::numeric_limits<double>::max();
523 double zmax = -std::numeric_limits<double>::max();
524
525 const double *x = mX.constData();
526 const double *y = mY.constData();
527 const double *z = mZ.constData();
528 bool foundPointInBox = false;
529 for ( int i = 0; i < nb; ++i )
530 {
531 const double px = *x++;
532 xmin = std::min( xmin, px );
533 xmax = std::max( xmax, px );
534 const double py = *y++;
535 ymin = std::min( ymin, py );
536 ymax = std::max( ymax, py );
537 const double pz = *z++;
538 zmin = std::min( zmin, pz );
539 zmax = std::max( zmax, pz );
540
541 if ( !foundPointInBox && box3d.contains( px, py, pz ) )
542 {
543 foundPointInBox = true;
544
545 // now... we have a choice to make. If we've already looped through the majority of the points
546 // in this linestring then let's just continue to iterate through the remainder so that we can
547 // complete the overall bounding box calculation we've already mostly done. If however we're only
548 // just at the start of iterating the vertices, we shortcut out early and leave the bounding box
549 // uncalculated
550 if ( i < nb * 0.5 )
551 return true;
552 }
553 }
554
555 // at this stage we now know the overall bounding box of the linestring, so let's cache
556 // it so we don't ever have to calculate this again. We've done all the hard work anyway!
557 mBoundingBox = QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax, false );
558
559 if ( foundPointInBox )
560 return true;
561
562 // NOTE: if none of the points in the line actually fell inside the rectangle, it doesn't
563 // exclude that the OVERALL bounding box of the linestring itself intersects the rectangle!!
564 // So we fall back to the parent class method which compares the overall bounding box against
565 // the rectangle... and this will be very cheap now that we've already calculated and cached
566 // the linestring's bounding box!
567 return QgsCurve::boundingBoxIntersects( box3d );
568}
569
570QVector< QgsVertexId > QgsLineString::collectDuplicateNodes( double epsilon, bool useZValues ) const
571{
572 QVector< QgsVertexId > res;
573 if ( mX.count() <= 1 )
574 return res;
575
576 const double *x = mX.constData();
577 const double *y = mY.constData();
578 bool hasZ = is3D();
579 bool useZ = hasZ && useZValues;
580 const double *z = useZ ? mZ.constData() : nullptr;
581
582 double prevX = *x++;
583 double prevY = *y++;
584 double prevZ = z ? *z++ : 0;
585
586 QgsVertexId id;
587 for ( int i = 1; i < mX.count(); ++i )
588 {
589 double currentX = *x++;
590 double currentY = *y++;
591 double currentZ = useZ ? *z++ : 0;
592 if ( qgsDoubleNear( currentX, prevX, epsilon ) && qgsDoubleNear( currentY, prevY, epsilon ) && ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
593 {
594 id.vertex = i;
595 res << id;
596 }
597 else
598 {
599 prevX = currentX;
600 prevY = currentY;
601 prevZ = currentZ;
602 }
603 }
604 return res;
605}
606
608{
609 const int nb = mX.size();
610 QPolygonF points( nb );
611
612 const double *x = mX.constData();
613 const double *y = mY.constData();
614 QPointF *dest = points.data();
615 for ( int i = 0; i < nb; ++i )
616 {
617 *dest++ = QPointF( *x++, *y++ );
618 }
619 return points;
620}
621
622
623void simplifySection( int i, int j, const double *x, const double *y, std::vector< bool > &usePoint, const double distanceToleranceSquared, const double epsilon )
624{
625 if ( i + 1 == j )
626 {
627 return;
628 }
629
630 double maxDistanceSquared = -1.0;
631
632 int maxIndex = i;
633 double mx, my;
634
635 for ( int k = i + 1; k < j; k++ )
636 {
637 const double distanceSquared = QgsGeometryUtilsBase::sqrDistToLine( x[k], y[k], x[i], y[i], x[j], y[j], mx, my, epsilon );
638
639 if ( distanceSquared > maxDistanceSquared )
640 {
641 maxDistanceSquared = distanceSquared;
642 maxIndex = k;
643 }
644 }
645 if ( maxDistanceSquared <= distanceToleranceSquared )
646 {
647 for ( int k = i + 1; k < j; k++ )
648 {
649 usePoint[k] = false;
650 }
651 }
652 else
653 {
654 simplifySection( i, maxIndex, x, y, usePoint, distanceToleranceSquared, epsilon );
655 simplifySection( maxIndex, j, x, y, usePoint, distanceToleranceSquared, epsilon );
656 }
657};
658
660{
661 if ( mX.empty() )
662 {
663 return new QgsLineString();
664 }
665
666 // ported from GEOS DouglasPeuckerLineSimplifier::simplify
667
668 const double distanceToleranceSquared = tolerance * tolerance;
669 const double *xData = mX.constData();
670 const double *yData = mY.constData();
671 const double *zData = mZ.constData();
672 const double *mData = mM.constData();
673
674 const int size = mX.size();
675
676 std::vector< bool > usePoint( size, true );
677
678 constexpr double epsilon = 4 * std::numeric_limits<double>::epsilon();
679 simplifySection( 0, size - 1, xData, yData, usePoint, distanceToleranceSquared, epsilon );
680
681 QVector< double > newX;
682 newX.reserve( size );
683 QVector< double > newY;
684 newY.reserve( size );
685
686 const bool hasZ = is3D();
687 const bool hasM = isMeasure();
688 QVector< double > newZ;
689 if ( hasZ )
690 newZ.reserve( size );
691 QVector< double > newM;
692 if ( hasM )
693 newM.reserve( size );
694
695 for ( int i = 0, n = size; i < n; ++i )
696 {
697 if ( usePoint[i] || i == n - 1 )
698 {
699 newX.append( xData[i] );
700 newY.append( yData[i] );
701 if ( hasZ )
702 newZ.append( zData[i] );
703 if ( hasM )
704 newM.append( mData[i] );
705 }
706 }
707
708 const bool simplifyRing = isRing();
709 const int newSize = newX.size();
710 if ( simplifyRing && newSize > 3 )
711 {
712 double mx, my;
713 const double distanceSquared = QgsGeometryUtilsBase::sqrDistToLine( newX[0], newY[0], newX[newSize - 2], newY[newSize - 2], newX[1], newY[1], mx, my, epsilon );
714
715 if ( distanceSquared <= distanceToleranceSquared )
716 {
717 newX.removeFirst();
718 newX.last() = newX.first();
719 newY.removeFirst();
720 newY.last() = newY.first();
721 if ( hasZ )
722 {
723 newZ.removeFirst();
724 newZ.last() = newZ.first();
725 }
726 if ( hasM )
727 {
728 newM.removeFirst();
729 newM.last() = newM.first();
730 }
731 }
732 }
733
734 return new QgsLineString( newX, newY, newZ, newM );
735}
736
738{
739 if ( mX.empty() )
740 {
741 return QgsBox3D();
742 }
743
744 auto result2D = std::minmax_element( mX.begin(), mX.end() );
745 const double xmin = *result2D.first;
746 const double xmax = *result2D.second;
747 result2D = std::minmax_element( mY.begin(), mY.end() );
748 const double ymin = *result2D.first;
749 const double ymax = *result2D.second;
750
751 double zmin = std::numeric_limits< double >::quiet_NaN();
752 double zmax = std::numeric_limits< double >::quiet_NaN();
753
754 if ( is3D() )
755 {
756 auto resultZ = std::minmax_element( mZ.begin(), mZ.end() );
757 zmin = *resultZ.first;
758 zmax = *resultZ.second;
759 }
760
761 return QgsBox3D( xmin, ymin, zmin, xmax, ymax, zmax );
762}
763
768
769/***************************************************************************
770 * This class is considered CRITICAL and any change MUST be accompanied with
771 * full unit tests.
772 * See details in QEP #17
773 ****************************************************************************/
774QDomElement QgsLineString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
775{
777 points( pts );
778
779 QDomElement elemLineString = doc.createElementNS( ns, u"LineString"_s );
780
781 if ( isEmpty() )
782 return elemLineString;
783
784 elemLineString.appendChild( QgsGeometryUtils::pointsToGML2( pts, doc, precision, ns, axisOrder ) );
785
786 return elemLineString;
787}
788
789QDomElement QgsLineString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
790{
792 points( pts );
793
794 QDomElement elemLineString = doc.createElementNS( ns, u"LineString"_s );
795
796 if ( isEmpty() )
797 return elemLineString;
798
799 elemLineString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
800 return elemLineString;
801}
802
803json QgsLineString::asJsonObject( int precision, Qgis::GeoJsonProfile profile ) const
804{
805 // The current profiles do not make any difference for LineString,
806 // but we keep the parameter in case we need to add specific handling
807 // for future profiles
808 Q_UNUSED( profile )
810 points( pts );
811 return { { "type", "LineString" }, { "coordinates", QgsGeometryUtils::pointsToJson( pts, precision, profile ) } };
812}
813
814QString QgsLineString::asKml( int precision ) const
815{
816 QString kml;
817 if ( isRing() )
818 {
819 kml.append( "<LinearRing>"_L1 );
820 }
821 else
822 {
823 kml.append( "<LineString>"_L1 );
824 }
825 bool z = is3D();
826 kml.append( "<altitudeMode>"_L1 );
827 if ( z )
828 {
829 kml.append( "absolute"_L1 );
830 }
831 else
832 {
833 kml.append( "clampToGround"_L1 );
834 }
835 kml.append( "</altitudeMode>"_L1 );
836 kml.append( "<coordinates>"_L1 );
837
838 int nPoints = mX.size();
839 for ( int i = 0; i < nPoints; ++i )
840 {
841 if ( i > 0 )
842 {
843 kml.append( " "_L1 );
844 }
845 kml.append( qgsDoubleToString( mX[i], precision ) );
846 kml.append( ","_L1 );
847 kml.append( qgsDoubleToString( mY[i], precision ) );
848 if ( z )
849 {
850 kml.append( ","_L1 );
851 kml.append( qgsDoubleToString( mZ[i], precision ) );
852 }
853 else
854 {
855 kml.append( ",0"_L1 );
856 }
857 }
858 kml.append( "</coordinates>"_L1 );
859 if ( isRing() )
860 {
861 kml.append( "</LinearRing>"_L1 );
862 }
863 else
864 {
865 kml.append( "</LineString>"_L1 );
866 }
867 return kml;
868}
869
870/***************************************************************************
871 * This class is considered CRITICAL and any change MUST be accompanied with
872 * full unit tests.
873 * See details in QEP #17
874 ****************************************************************************/
875
877{
878 double total = 0;
879 const int size = mX.size();
880 if ( size < 2 )
881 return 0;
882
883 const double *x = mX.constData();
884 const double *y = mY.constData();
885 double dx, dy;
886
887 double prevX = *x++;
888 double prevY = *y++;
889
890 for ( int i = 1; i < size; ++i )
891 {
892 dx = *x - prevX;
893 dy = *y - prevY;
894 total += std::sqrt( dx * dx + dy * dy );
895
896 prevX = *x++;
897 prevY = *y++;
898 }
899 return total;
900}
901
902std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsLineString::splitCurveAtVertex( int index ) const
903{
904 QVector< double > x1, y1, z1, m1;
905 QVector< double > x2, y2, z2, m2;
906 QgsSimpleCurve::splitCurveAtVertexProtected( index, x1, y1, z1, m1, x2, y2, z2, m2 );
907
908 std::unique_ptr< QgsLineString > first;
909 if ( x1.isEmpty() || ( x1.size() < 2 && x2.size() >= 2 ) )
910 first = std::make_unique< QgsLineString >();
911 else
912 first = std::make_unique< QgsLineString >( x1, y1, z1, m1 );
913
914 std::unique_ptr< QgsLineString > second;
915 if ( x2.isEmpty() || x2.size() < 2 )
916 second = std::make_unique< QgsLineString >();
917 else
918 second = std::make_unique< QgsLineString >( x2, y2, z2, m2 );
919
920 return std::make_tuple( std::move( first ), std::move( second ) );
921}
922
923QVector<QgsLineString *> QgsLineString::splitToDisjointXYParts() const
924{
925 const double *allPointsX = xData();
926 const double *allPointsY = yData();
927 size_t allPointsCount = numPoints();
928 QVector<double> partX;
929 QVector<double> partY;
930 QSet<QgsPointXY> partPointSet;
931
932 QVector<QgsLineString *> disjointParts;
933 for ( size_t i = 0; i < allPointsCount; i++ )
934 {
935 const QgsPointXY point( *allPointsX++, *allPointsY++ );
936 if ( partPointSet.contains( point ) )
937 {
938 // This point is used multiple times, cut the curve and add the
939 // current part
940 disjointParts.push_back( new QgsLineString( partX, partY ) );
941 // Now start a new part containing the last line
942 partX = { partX.last() };
943 partY = { partY.last() };
944 partPointSet = { QgsPointXY( partX[0], partY[0] ) };
945 }
946 partX.push_back( point.x() );
947 partY.push_back( point.y() );
948 partPointSet.insert( point );
949 }
950 // Add the last part (if we didn't stop by closing the loop)
951 if ( partX.size() > 1 || disjointParts.size() == 0 )
952 disjointParts.push_back( new QgsLineString( partX, partY ) );
953
954 return disjointParts;
955}
956
958{
959 if ( is3D() )
960 {
961 double total = 0;
962 const int size = mX.size();
963 if ( size < 2 )
964 return 0;
965
966 const double *x = mX.constData();
967 const double *y = mY.constData();
968 const double *z = mZ.constData();
969 double dx, dy, dz;
970
971 double prevX = *x++;
972 double prevY = *y++;
973 double prevZ = *z++;
974
975 for ( int i = 1; i < size; ++i )
976 {
977 dx = *x - prevX;
978 dy = *y - prevY;
979 dz = *z - prevZ;
980 total += std::sqrt( dx * dx + dy * dy + dz * dz );
981
982 prevX = *x++;
983 prevY = *y++;
984 prevZ = *z++;
985 }
986 return total;
987 }
988 else
989 {
990 return length();
991 }
992}
993
994/***************************************************************************
995 * This class is considered CRITICAL and any change MUST be accompanied with
996 * full unit tests.
997 * See details in QEP #17
998 ****************************************************************************/
999
1001{
1002 Q_UNUSED( tolerance )
1003 Q_UNUSED( toleranceType )
1004 return clone();
1005}
1006
1007/***************************************************************************
1008 * This class is considered CRITICAL and any change MUST be accompanied with
1009 * full unit tests.
1010 * See details in QEP #17
1011 ****************************************************************************/
1012
1013void QgsLineString::setPoints( size_t size, const double *x, const double *y, const double *z, const double *m )
1014{
1015 clearCache(); //set bounding box invalid
1016
1017 if ( size == 0 )
1018 {
1019 clear();
1020 return;
1021 }
1022
1023 const bool hasZ = static_cast< bool >( z );
1024 const bool hasM = static_cast< bool >( m );
1025
1026 if ( hasZ && hasM )
1027 {
1029 }
1030 else if ( hasZ )
1031 {
1033 }
1034 else if ( hasM )
1035 {
1037 }
1038 else
1039 {
1041 }
1042
1043 mX.resize( size );
1044 mY.resize( size );
1045 double *destX = mX.data();
1046 double *destY = mY.data();
1047 double *destZ = nullptr;
1048 if ( hasZ )
1049 {
1050 mZ.resize( size );
1051 destZ = mZ.data();
1052 }
1053 else
1054 {
1055 mZ.clear();
1056 }
1057 double *destM = nullptr;
1058 if ( hasM )
1059 {
1060 mM.resize( size );
1061 destM = mM.data();
1062 }
1063 else
1064 {
1065 mM.clear();
1066 }
1067
1068 for ( size_t i = 0; i < size; ++i )
1069 {
1070 *destX++ = *x++;
1071 *destY++ = *y++;
1072 if ( hasZ )
1073 {
1074 *destZ++ = *z++;
1075 }
1076 if ( hasM )
1077 {
1078 *destM++ = *m++;
1079 }
1080 }
1081}
1082
1083/***************************************************************************
1084 * This class is considered CRITICAL and any change MUST be accompanied with
1085 * full unit tests.
1086 * See details in QEP #17
1087 ****************************************************************************/
1088
1090{
1091 return qgis::down_cast< QgsLineString *>( QgsSimpleCurve::reversed() );
1092}
1093
1095 const double distance, const std::function<bool( double, double, double, double, double, double, double, double, double, double, double, double )> &visitPoint
1096) const
1097{
1098 if ( distance < 0 )
1099 return;
1100
1101 double distanceTraversed = 0;
1102 const int totalPoints = numPoints();
1103 if ( totalPoints == 0 )
1104 return;
1105
1106 const double *x = mX.constData();
1107 const double *y = mY.constData();
1108 const double *z = is3D() ? mZ.constData() : nullptr;
1109 const double *m = isMeasure() ? mM.constData() : nullptr;
1110
1111 double prevX = *x++;
1112 double prevY = *y++;
1113 double prevZ = z ? *z++ : 0.0;
1114 double prevM = m ? *m++ : 0.0;
1115
1116 if ( qgsDoubleNear( distance, 0.0 ) )
1117 {
1118 visitPoint( prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM );
1119 return;
1120 }
1121
1122 double pZ = std::numeric_limits<double>::quiet_NaN();
1123 double pM = std::numeric_limits<double>::quiet_NaN();
1124 double nextPointDistance = distance;
1125 const double eps = 4 * nextPointDistance * std::numeric_limits<double>::epsilon();
1126 for ( int i = 1; i < totalPoints; ++i )
1127 {
1128 double thisX = *x++;
1129 double thisY = *y++;
1130 double thisZ = z ? *z++ : 0.0;
1131 double thisM = m ? *m++ : 0.0;
1132
1133 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1134 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength, eps ) )
1135 {
1136 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1137 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
1138 double pX, pY;
1140 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
1141
1142 if ( !visitPoint( pX, pY, pZ, pM, prevX, prevY, prevZ, prevM, thisX, thisY, thisZ, thisM ) )
1143 return;
1144
1145 nextPointDistance += distance;
1146 }
1147
1148 distanceTraversed += segmentLength;
1149 prevX = thisX;
1150 prevY = thisY;
1151 prevZ = thisZ;
1152 prevM = thisM;
1153 }
1154}
1155
1156QgsPoint *QgsLineString::interpolatePoint( const double distance ) const
1157{
1158 if ( distance < 0 )
1159 return nullptr;
1160
1162 if ( is3D() )
1163 pointType = Qgis::WkbType::PointZ;
1164 if ( isMeasure() )
1165 pointType = QgsWkbTypes::addM( pointType );
1166
1167 std::unique_ptr< QgsPoint > res;
1168 visitPointsByRegularDistance( distance, [&]( double x, double y, double z, double m, double, double, double, double, double, double, double, double ) -> bool {
1169 res = std::make_unique< QgsPoint >( pointType, x, y, z, m );
1170 return false;
1171 } );
1172 return res.release();
1173}
1174
1175bool QgsLineString::lineLocatePointByM( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance ) const
1176{
1177 return lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, false );
1178}
1179
1180bool QgsLineString::lineLocatePointByMPrivate( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance, bool haveInterpolatedM ) const
1181{
1182 if ( !isMeasure() )
1183 return false;
1184
1185 distanceFromStart = 0;
1186 const int totalPoints = numPoints();
1187 if ( totalPoints == 0 )
1188 return false;
1189
1190 const double *xData = mX.constData();
1191 const double *yData = mY.constData();
1192 const double *mData = mM.constData();
1193
1194 const double *zData = is3D() ? mZ.constData() : nullptr;
1195 use3DDistance &= static_cast< bool >( zData );
1196
1197 double prevX = *xData++;
1198 double prevY = *yData++;
1199 double prevZ = zData ? *zData++ : 0;
1200 double prevM = *mData++;
1201
1202 int i = 1;
1203 while ( i < totalPoints )
1204 {
1205 double thisX = *xData++;
1206 double thisY = *yData++;
1207 double thisZ = zData ? *zData++ : 0;
1208 double thisM = *mData++;
1209 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( thisX, thisY, thisZ, prevX, prevY, prevZ ) : QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1210
1211 if ( std::isnan( thisM ) )
1212 {
1213 if ( haveInterpolatedM )
1214 return false;
1215
1216 // if we hit a NaN m value, interpolate m to fill the blanks and then re-try
1217 std::unique_ptr< QgsLineString > interpolatedM( interpolateM( use3DDistance ) );
1218 return interpolatedM->lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, true );
1219 }
1220 else
1221 {
1222 // check if target m value falls within this segment's range
1223 if ( ( prevM < m && thisM > m ) || ( prevM > m && thisM < m ) || qgsDoubleNear( prevM, m ) || qgsDoubleNear( thisM, m ) )
1224 {
1225 // use centroid for constant value m segments
1226 if ( qgsDoubleNear( thisM, m ) && ( i < totalPoints - 1 ) && qgsDoubleNear( *mData, m ) )
1227 {
1228 distanceFromStart += segmentLength;
1229 // scan ahead till we find a vertex with a different m
1230 double totalLengthOfSegmentsWithConstantM = 0;
1231 for ( int j = 0; j < ( totalPoints - i ); ++j )
1232 {
1233 if ( !qgsDoubleNear( *( mData + j ), m ) )
1234 break;
1235
1236 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( *( xData + j - 1 ), *( yData + j - 1 ), *( zData + j - 1 ), *( xData + j ), *( yData + j ), *( zData + j ) )
1237 : QgsGeometryUtilsBase::distance2D( *( xData + j - 1 ), *( yData + j - 1 ), *( xData + j ), *( yData + j ) );
1238 totalLengthOfSegmentsWithConstantM += segmentLength;
1239 }
1240
1241 distanceFromStart += totalLengthOfSegmentsWithConstantM / 2;
1242 std::unique_ptr< QgsPoint> point( interpolatePoint( distanceFromStart ) );
1243 if ( !point )
1244 return false;
1245 x = point->x();
1246 y = point->y();
1247 z = point->z();
1248 return true;
1249 }
1250
1251 const double delta = ( m - prevM ) / ( thisM - prevM );
1252
1253 const double distanceToPoint = delta * segmentLength;
1254
1255 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, x, y );
1256 z = prevZ + ( thisZ - prevZ ) * delta;
1257 distanceFromStart += distanceToPoint;
1258 return true;
1259 }
1260 }
1261
1262 distanceFromStart += segmentLength;
1263 prevX = thisX;
1264 prevY = thisY;
1265 prevZ = thisZ;
1266 prevM = thisM;
1267 ++i;
1268 }
1269 return false;
1270}
1271
1272QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
1273{
1274 if ( startDistance < 0 && endDistance < 0 )
1275 return createEmptyWithSameType();
1276
1277 endDistance = std::max( startDistance, endDistance );
1278
1279 const int totalPoints = numPoints();
1280 if ( totalPoints == 0 )
1281 return clone();
1282
1283 QVector< QgsPoint > substringPoints;
1284 substringPoints.reserve( totalPoints );
1285
1287 if ( is3D() )
1288 pointType = Qgis::WkbType::PointZ;
1289 if ( isMeasure() )
1290 pointType = QgsWkbTypes::addM( pointType );
1291
1292 const double *x = mX.constData();
1293 const double *y = mY.constData();
1294 const double *z = is3D() ? mZ.constData() : nullptr;
1295 const double *m = isMeasure() ? mM.constData() : nullptr;
1296
1297 double distanceTraversed = 0;
1298 double prevX = *x++;
1299 double prevY = *y++;
1300 double prevZ = z ? *z++ : 0.0;
1301 double prevM = m ? *m++ : 0.0;
1302 bool foundStart = false;
1303
1304 if ( startDistance < 0 )
1305 startDistance = 0;
1306
1307 for ( int i = 1; i < totalPoints; ++i )
1308 {
1309 double thisX = *x++;
1310 double thisY = *y++;
1311 double thisZ = z ? *z++ : 0.0;
1312 double thisM = m ? *m++ : 0.0;
1313
1314 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1315
1316 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1317 {
1318 // start point falls on this segment
1319 const double distanceToStart = startDistance - distanceTraversed;
1320 double startX, startY;
1321 double startZ = 0;
1322 double startM = 0;
1324 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToStart, startX, startY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &startZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &startM : nullptr );
1325 substringPoints << QgsPoint( pointType, startX, startY, startZ, startM );
1326 foundStart = true;
1327 }
1328 if ( foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1329 {
1330 // end point falls on this segment
1331 const double distanceToEnd = endDistance - distanceTraversed;
1332 double endX, endY;
1333 double endZ = 0;
1334 double endM = 0;
1336 pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToEnd, endX, endY, z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &endZ : nullptr, m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &endM : nullptr );
1337 substringPoints << QgsPoint( pointType, endX, endY, endZ, endM );
1338 }
1339 else if ( foundStart )
1340 {
1341 substringPoints << QgsPoint( pointType, thisX, thisY, thisZ, thisM );
1342 }
1343
1344 prevX = thisX;
1345 prevY = thisY;
1346 prevZ = thisZ;
1347 prevM = thisM;
1348 distanceTraversed += segmentLength;
1349 if ( distanceTraversed >= endDistance )
1350 break;
1351 }
1352
1353 // start point is the last node
1354 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1355 {
1356 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1357 }
1358
1359 return new QgsLineString( substringPoints );
1360}
1361
1362/***************************************************************************
1363 * This class is considered CRITICAL and any change MUST be accompanied with
1364 * full unit tests.
1365 * See details in QEP #17
1366 ****************************************************************************/
1367
1368void QgsLineString::draw( QPainter &p ) const
1369{
1370 p.drawPolyline( asQPolygonF() );
1371}
1372
1373void QgsLineString::addToPainterPath( QPainterPath &path ) const
1374{
1375 int nPoints = numPoints();
1376 if ( nPoints < 1 )
1377 {
1378 return;
1379 }
1380
1381 if ( path.isEmpty() || path.currentPosition() != QPointF( mX.at( 0 ), mY.at( 0 ) ) )
1382 {
1383 path.moveTo( mX.at( 0 ), mY.at( 0 ) );
1384 }
1385
1386 for ( int i = 1; i < nPoints; ++i )
1387 {
1388 path.lineTo( mX.at( i ), mY.at( i ) );
1389 }
1390}
1391
1392void QgsLineString::drawAsPolygon( QPainter &p ) const
1393{
1394 p.drawPolygon( asQPolygonF() );
1395}
1396
1398{
1399 QgsCompoundCurve *compoundCurve = new QgsCompoundCurve();
1400 compoundCurve->addCurve( clone() );
1401 return compoundCurve;
1402}
1403
1404void QgsLineString::extend( double startDistance, double endDistance, double startDeflection, double endDeflection )
1405{
1406 if ( mX.size() < 2 || mY.size() < 2 )
1407 return;
1408
1409 const bool extendStart = startDistance > 0;
1410 const bool extendEnd = endDistance > 0;
1411
1412 constexpr double DEG_TO_RAD = M_PI / 180.0;
1413
1414 // start of line
1415 if ( extendStart )
1416 {
1417 double dx = mX.at( 0 ) - mX.at( 1 );
1418 double dy = mY.at( 0 ) - mY.at( 1 );
1419 const double currentLen = std::sqrt( std::pow( dx, 2 ) + std::pow( dy, 2 ) );
1420 if ( currentLen > 0 )
1421 {
1422 if ( !qgsDoubleNear( startDeflection, 0 ) )
1423 {
1424 // if deflecting, rotate start point by degrees clockwise
1425 const double startRad = startDeflection * DEG_TO_RAD;
1426 const double cosStart = std::cos( startRad );
1427 const double sinStart = std::sin( startRad );
1428 const double rotatedDx = dx * cosStart + dy * sinStart;
1429 const double rotatedDy = -dx * sinStart + dy * cosStart;
1430
1431 const double newX = mX.at( 0 ) + ( rotatedDx / currentLen ) * startDistance;
1432 const double newY = mY.at( 0 ) + ( rotatedDy / currentLen ) * startDistance;
1433
1434 mX.insert( mX.begin(), newX );
1435 mY.insert( mY.begin(), newY );
1436 // copy z/m from first vertex if required
1437 if ( !mZ.empty() )
1438 {
1439 mZ.insert( mZ.begin(), mZ.at( 0 ) );
1440 }
1441 if ( !mM.empty() )
1442 {
1443 mM.insert( mM.begin(), mM.at( 0 ) );
1444 }
1445 }
1446 else
1447 {
1448 const double newLen = currentLen + startDistance;
1449 mX[0] = mX.at( 1 ) + dx / currentLen * newLen;
1450 mY[0] = mY.at( 1 ) + dy / currentLen * newLen;
1451 }
1452 }
1453 }
1454 // end of line
1455 if ( extendEnd )
1456 {
1457 const qsizetype last = mX.size() - 1;
1458 double dx = mX.at( last ) - mX.at( last - 1 );
1459 double dy = mY.at( last ) - mY.at( last - 1 );
1460 const double currentLen = std::sqrt( std::pow( dx, 2 ) + std::pow( dy, 2 ) );
1461
1462 if ( currentLen > 0 )
1463 {
1464 if ( !qgsDoubleNear( endDeflection, 0 ) )
1465 {
1466 // if deflecting, rotate end point by degrees clockwise
1467 const double endRad = endDeflection * DEG_TO_RAD;
1468 const double cosEnd = std::cos( endRad );
1469 const double sinEnd = std::sin( endRad );
1470 const double rotatedDx = dx * cosEnd + dy * sinEnd;
1471 const double rotatedDy = -dx * sinEnd + dy * cosEnd;
1472
1473 const double newX = mX.at( last ) + ( rotatedDx / currentLen ) * endDistance;
1474 const double newY = mY.at( last ) + ( rotatedDy / currentLen ) * endDistance;
1475
1476 mX.push_back( newX );
1477 mY.push_back( newY );
1478
1479 // copy z/m from last vertex if required
1480 if ( !mZ.empty() )
1481 {
1482 mZ.push_back( mZ.at( last ) );
1483 }
1484 if ( !mM.empty() )
1485 {
1486 mM.push_back( mM.at( last ) );
1487 }
1488 }
1489 else
1490 {
1491 const double newLen = currentLen + endDistance;
1492 mX[last] = mX.at( last - 1 ) + dx / currentLen * newLen;
1493 mY[last] = mY.at( last - 1 ) + dy / currentLen * newLen;
1494 }
1495 }
1496 }
1497
1498 if ( extendStart || extendEnd )
1499 clearCache(); //set bounding box invalid
1500}
1501
1503{
1504 auto result = std::make_unique< QgsLineString >();
1505 result->mWkbType = mWkbType;
1506 return result.release();
1507}
1508
1510{
1511 return u"LineString"_s;
1512}
1513
1514/***************************************************************************
1515 * This class is considered CRITICAL and any change MUST be accompanied with
1516 * full unit tests.
1517 * See details in QEP #17
1518 ****************************************************************************/
1519
1520bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
1521{
1522 if ( position.vertex < 0 || position.vertex > mX.size() )
1523 {
1524 return false;
1525 }
1526
1527 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1528 {
1530 }
1531
1532 mX.insert( position.vertex, vertex.x() );
1533 mY.insert( position.vertex, vertex.y() );
1534 if ( is3D() )
1535 {
1536 mZ.insert( position.vertex, vertex.z() );
1537 }
1538 if ( isMeasure() )
1539 {
1540 mM.insert( position.vertex, vertex.m() );
1541 }
1542 clearCache(); //set bounding box invalid
1543 return true;
1544}
1545
1547{
1548 if ( position.vertex >= mX.size() || position.vertex < 0 )
1549 {
1550 return false;
1551 }
1552
1553 mX.remove( position.vertex );
1554 mY.remove( position.vertex );
1555 if ( is3D() )
1556 {
1557 mZ.remove( position.vertex );
1558 }
1559 if ( isMeasure() )
1560 {
1561 mM.remove( position.vertex );
1562 }
1563
1564 if ( numPoints() == 1 )
1565 {
1566 clear();
1567 }
1568
1569 clearCache(); //set bounding box invalid
1570 return true;
1571}
1572
1573bool QgsLineString::deleteVertices( const QSet<QgsVertexId> &positions )
1574{
1575 if ( positions.isEmpty() )
1576 {
1577 return false;
1578 }
1579
1580 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
1581
1582 for ( QgsVertexId pos : positions )
1583 {
1584 if ( !hasVertex( pos ) )
1585 {
1586 return false;
1587 }
1588 }
1589
1590 if ( numPoints() <= vertices.size() + 1 )
1591 {
1592 clear();
1593 return true;
1594 }
1595
1596 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex > b.vertex; } );
1597
1598 for ( QgsVertexId position : vertices )
1599 {
1600 mX.remove( position.vertex );
1601 mY.remove( position.vertex );
1602 if ( is3D() )
1603 {
1604 mZ.remove( position.vertex );
1605 }
1606 if ( isMeasure() )
1607 {
1608 mM.remove( position.vertex );
1609 }
1610 }
1611
1612 clearCache(); //set bounding box invalid
1613 return true;
1614}
1615
1616/***************************************************************************
1617 * This class is considered CRITICAL and any change MUST be accompanied with
1618 * full unit tests.
1619 * See details in QEP #17
1620 ****************************************************************************/
1621
1623{
1624 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1625 {
1627 }
1628
1629 mX.append( pt.x() );
1630 mY.append( pt.y() );
1631 if ( is3D() )
1632 {
1633 mZ.append( pt.z() );
1634 }
1635 if ( isMeasure() )
1636 {
1637 mM.append( pt.m() );
1638 }
1639 clearCache(); //set bounding box invalid
1640}
1641
1642double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1643{
1644 double sqrDist = std::numeric_limits<double>::max();
1645 double leftOfDist = std::numeric_limits<double>::max();
1646 int prevLeftOf = 0;
1647 double prevLeftOfX = 0.0;
1648 double prevLeftOfY = 0.0;
1649 double testDist = 0;
1650 double segmentPtX, segmentPtY;
1651
1652 if ( leftOf )
1653 *leftOf = 0;
1654
1655 const int size = mX.size();
1656 if ( size == 0 || size == 1 )
1657 {
1658 vertexAfter = QgsVertexId( 0, 0, 0 );
1659 return -1;
1660 }
1661
1662 const double *xData = mX.constData();
1663 const double *yData = mY.constData();
1664 for ( int i = 1; i < size; ++i )
1665 {
1666 double prevX = xData[i - 1];
1667 double prevY = yData[i - 1];
1668 double currentX = xData[i];
1669 double currentY = yData[i];
1670 testDist = QgsGeometryUtilsBase::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon );
1671 if ( testDist < sqrDist )
1672 {
1673 sqrDist = testDist;
1674 segmentPt.setX( segmentPtX );
1675 segmentPt.setY( segmentPtY );
1676 vertexAfter.part = 0;
1677 vertexAfter.ring = 0;
1678 vertexAfter.vertex = i;
1679 }
1680 if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
1681 {
1682 int left = QgsGeometryUtilsBase::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
1683 // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
1684 // so don't set leftOf in this case, and hope that there's another segment that's the same distance
1685 // where we can perform the check
1686 if ( left != 0 )
1687 {
1688 if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 )
1689 {
1690 // we have two possible segments each with equal distance to point, but they disagree
1691 // on whether or not the point is to the left of them.
1692 // so we test the segments themselves and flip the result.
1693 // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
1694 *leftOf = -QgsGeometryUtilsBase::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY );
1695 }
1696 else
1697 {
1698 *leftOf = left;
1699 }
1700 prevLeftOf = *leftOf;
1701 leftOfDist = testDist;
1702 prevLeftOfX = prevX;
1703 prevLeftOfY = prevY;
1704 }
1705 else if ( testDist < leftOfDist )
1706 {
1707 *leftOf = left;
1708 leftOfDist = testDist;
1709 prevLeftOf = 0;
1710 }
1711 }
1712 }
1713 return sqrDist;
1714}
1715
1716/***************************************************************************
1717 * This class is considered CRITICAL and any change MUST be accompanied with
1718 * full unit tests.
1719 * See details in QEP #17
1720 ****************************************************************************/
1721
1722bool QgsLineString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1723{
1724 if ( node < 0 || node >= numPoints() )
1725 {
1726 return false;
1727 }
1728 point = pointN( node );
1730 return true;
1731}
1732
1734{
1735 if ( mX.isEmpty() )
1736 return QgsPoint();
1737
1738 int numPoints = mX.count();
1739 if ( numPoints == 1 )
1740 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1741
1742 double totalLineLength = 0.0;
1743 double prevX = mX.at( 0 );
1744 double prevY = mY.at( 0 );
1745 double sumX = 0.0;
1746 double sumY = 0.0;
1747
1748 for ( int i = 1; i < numPoints; ++i )
1749 {
1750 double currentX = mX.at( i );
1751 double currentY = mY.at( i );
1752 double segmentLength = std::sqrt( std::pow( currentX - prevX, 2.0 ) + std::pow( currentY - prevY, 2.0 ) );
1753 if ( qgsDoubleNear( segmentLength, 0.0 ) )
1754 continue;
1755
1756 totalLineLength += segmentLength;
1757 sumX += segmentLength * ( currentX + prevX );
1758 sumY += segmentLength * ( currentY + prevY );
1759 prevX = currentX;
1760 prevY = currentY;
1761 }
1762 sumX *= 0.5;
1763 sumY *= 0.5;
1764
1765 if ( qgsDoubleNear( totalLineLength, 0.0 ) )
1766 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1767 else
1768 return QgsPoint( sumX / totalLineLength, sumY / totalLineLength );
1769}
1770
1771/***************************************************************************
1772 * This class is considered CRITICAL and any change MUST be accompanied with
1773 * full unit tests.
1774 * See details in QEP #17
1775 ****************************************************************************/
1776
1777void QgsLineString::sumUpArea( double &sum ) const
1778{
1780 {
1781 sum += mSummedUpArea;
1782 return;
1783 }
1784
1785 mSummedUpArea = 0;
1786 const int maxIndex = mX.size();
1787 if ( maxIndex < 2 )
1788 {
1790 return;
1791 }
1792
1793 const double *x = mX.constData();
1794 const double *y = mY.constData();
1795 double prevX = *x++;
1796 double prevY = *y++;
1797 for ( int i = 1; i < maxIndex; ++i )
1798 {
1799 mSummedUpArea += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1800 prevX = *x++;
1801 prevY = *y++;
1802 }
1803 mSummedUpArea *= 0.5;
1804
1806 sum += mSummedUpArea;
1807}
1808
1809void QgsLineString::sumUpArea3D( double &sum ) const
1810{
1812 {
1813 sum += mSummedUpArea3D;
1814 return;
1815 }
1816
1817 // No Z component. Fallback to the 2D version
1818 if ( mZ.isEmpty() )
1819 {
1820 double area2D = 0;
1821 sumUpArea( area2D );
1822 mSummedUpArea3D = area2D;
1824 sum += mSummedUpArea3D;
1825 return;
1826 }
1827
1828 mSummedUpArea3D = 0;
1829
1830 // Look for a reference unit normal
1831 QgsPoint ptA;
1832 QgsPoint ptB;
1833 QgsPoint ptC;
1834 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
1835 {
1837 return;
1838 }
1839
1840 QgsVector3D vAB = QgsVector3D( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
1841 QgsVector3D vAC = QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() );
1842 QgsVector3D planeNormal = QgsVector3D::crossProduct( vAB, vAC );
1843
1844 // Ensure a Consistent orientation: prioritize Z+, then Y+, then X+
1845 if ( !qgsDoubleNear( planeNormal.z(), 0.0 ) )
1846 {
1847 if ( planeNormal.z() < 0 )
1848 {
1849 planeNormal = -planeNormal;
1850 }
1851 }
1852 else if ( !qgsDoubleNear( planeNormal.y(), 0.0 ) )
1853 {
1854 if ( planeNormal.y() < 0 )
1855 planeNormal = -planeNormal;
1856 }
1857 else
1858 {
1859 if ( planeNormal.x() < 0 )
1860 planeNormal = -planeNormal;
1861 }
1862 planeNormal.normalize();
1863
1864 const double *x = mX.constData();
1865 const double *y = mY.constData();
1866 const double *z = mZ.constData();
1867
1868 double prevX = *x++;
1869 double prevY = *y++;
1870 double prevZ = *z++;
1871
1872 double normalX = 0.0;
1873 double normalY = 0.0; // #spellok - Y component of normal vector
1874 double normalZ = 0.0;
1875
1876 for ( unsigned int i = 1; i < mX.size(); ++i )
1877 {
1878 normalX += prevY * ( *z - prevZ ) - prevZ * ( *y - prevY );
1879 normalY += prevZ * ( *x - prevX ) - prevX * ( *z - prevZ ); // #spellok
1880 normalZ += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1881
1882 prevX = *x++;
1883 prevY = *y++;
1884 prevZ = *z++;
1885 }
1886
1887 mSummedUpArea3D = 0.5 * ( normalX * planeNormal.x() + normalY * planeNormal.y() + normalZ * planeNormal.z() ); // #spellok
1888
1890 sum += mSummedUpArea3D;
1891}
1892
1893/***************************************************************************
1894 * This class is considered CRITICAL and any change MUST be accompanied with
1895 * full unit tests.
1896 * See details in QEP #17
1897 ****************************************************************************/
1898
1900{
1901 if ( numPoints() < 1 || isClosed() )
1902 {
1903 return;
1904 }
1905 addVertex( startPoint() );
1906}
1907
1909{
1910 if ( mX.count() < 2 )
1911 {
1912 //undefined
1913 return 0.0;
1914 }
1915
1916 if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
1917 {
1918 if ( isClosed() )
1919 {
1920 double previousX = mX.at( numPoints() - 2 );
1921 double previousY = mY.at( numPoints() - 2 );
1922 double currentX = mX.at( 0 );
1923 double currentY = mY.at( 0 );
1924 double afterX = mX.at( 1 );
1925 double afterY = mY.at( 1 );
1926 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1927 }
1928 else if ( vertex.vertex == 0 )
1929 {
1930 return QgsGeometryUtilsBase::lineAngle( mX.at( 0 ), mY.at( 0 ), mX.at( 1 ), mY.at( 1 ) );
1931 }
1932 else
1933 {
1934 int a = numPoints() - 2;
1935 int b = numPoints() - 1;
1936 return QgsGeometryUtilsBase::lineAngle( mX.at( a ), mY.at( a ), mX.at( b ), mY.at( b ) );
1937 }
1938 }
1939 else
1940 {
1941 double previousX = mX.at( vertex.vertex - 1 );
1942 double previousY = mY.at( vertex.vertex - 1 );
1943 double currentX = mX.at( vertex.vertex );
1944 double currentY = mY.at( vertex.vertex );
1945 double afterX = mX.at( vertex.vertex + 1 );
1946 double afterY = mY.at( vertex.vertex + 1 );
1947 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1948 }
1949}
1950
1952{
1953 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 1 )
1954 return 0.0;
1955
1956 double dx = mX.at( startVertex.vertex + 1 ) - mX.at( startVertex.vertex );
1957 double dy = mY.at( startVertex.vertex + 1 ) - mY.at( startVertex.vertex );
1958 return std::sqrt( dx * dx + dy * dy );
1959}
1960
1961/***************************************************************************
1962 * This class is considered CRITICAL and any change MUST be accompanied with
1963 * full unit tests.
1964 * See details in QEP #17
1965 ****************************************************************************/
1966
1968{
1969 if ( type == mWkbType )
1970 return true;
1971
1972 clearCache();
1973 if ( type == Qgis::WkbType::LineString25D )
1974 {
1975 //special handling required for conversion to LineString25D
1976 dropMValue();
1977 addZValue( std::numeric_limits<double>::quiet_NaN() );
1979 return true;
1980 }
1981 else
1982 {
1983 return QgsCurve::convertTo( type );
1984 }
1985}
1986
1987std::unique_ptr< QgsLineString > QgsLineString::measuredLine( double start, double end ) const
1988{
1989 const int nbpoints = numPoints();
1990 std::unique_ptr< QgsLineString > cloned( clone() );
1991
1992 if ( !cloned->convertTo( QgsWkbTypes::addM( mWkbType ) ) )
1993 {
1994 return cloned;
1995 }
1996
1997 if ( isEmpty() || ( nbpoints < 2 ) )
1998 {
1999 return cloned;
2000 }
2001
2002 const double range = end - start;
2003 double lineLength = length();
2004 double lengthSoFar = 0.0;
2005
2006
2007 double *mOut = cloned->mM.data();
2008 *mOut++ = start;
2009 for ( int i = 1; i < nbpoints; ++i )
2010 {
2011 lengthSoFar += QgsGeometryUtilsBase::distance2D( mX[i - 1], mY[i - 1], mX[i], mY[i] );
2012 if ( lineLength > 0.0 )
2013 *mOut++ = start + range * lengthSoFar / lineLength;
2014 else if ( lineLength == 0.0 && nbpoints > 1 )
2015 *mOut++ = start + range * i / ( nbpoints - 1 );
2016 else
2017 *mOut++ = 0.0;
2018 }
2019
2020 return cloned;
2021}
2022
2023std::unique_ptr< QgsLineString > QgsLineString::interpolateM( bool use3DDistance ) const
2024{
2025 if ( !isMeasure() )
2026 return nullptr;
2027
2028 const int totalPoints = numPoints();
2029 if ( totalPoints < 2 )
2030 return std::unique_ptr< QgsLineString >( clone() );
2031
2032 const double *xData = mX.constData();
2033 const double *yData = mY.constData();
2034 const double *mData = mM.constData();
2035 const double *zData = is3D() ? mZ.constData() : nullptr;
2036 use3DDistance &= static_cast< bool >( zData );
2037
2038 QVector< double > xOut( totalPoints );
2039 QVector< double > yOut( totalPoints );
2040 QVector< double > mOut( totalPoints );
2041 QVector< double > zOut( static_cast< bool >( zData ) ? totalPoints : 0 );
2042
2043 double *xOutData = xOut.data();
2044 double *yOutData = yOut.data();
2045 double *mOutData = mOut.data();
2046 double *zOutData = static_cast< bool >( zData ) ? zOut.data() : nullptr;
2047
2048 int i = 0;
2049 double currentSegmentLength = 0;
2050 double lastValidM = std::numeric_limits< double >::quiet_NaN();
2051 double prevX = *xData;
2052 double prevY = *yData;
2053 double prevZ = zData ? *zData : 0;
2054 while ( i < totalPoints )
2055 {
2056 double thisX = *xData++;
2057 double thisY = *yData++;
2058 double thisZ = zData ? *zData++ : 0;
2059 double thisM = *mData++;
2060
2061 currentSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( prevX, prevY, prevZ, thisX, thisY, thisZ ) : QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
2062
2063 if ( !std::isnan( thisM ) )
2064 {
2065 *xOutData++ = thisX;
2066 *yOutData++ = thisY;
2067 *mOutData++ = thisM;
2068 if ( zOutData )
2069 *zOutData++ = thisZ;
2070 lastValidM = thisM;
2071 }
2072 else if ( i == 0 )
2073 {
2074 // nan m value at start of line, read ahead to find first non-nan value and backfill
2075 int j = 0;
2076 double scanAheadM = thisM;
2077 while ( i + j + 1 < totalPoints && std::isnan( scanAheadM ) )
2078 {
2079 scanAheadM = mData[j];
2080 ++j;
2081 }
2082 if ( std::isnan( scanAheadM ) )
2083 {
2084 // no valid m values in line
2085 return nullptr;
2086 }
2087 *xOutData++ = thisX;
2088 *yOutData++ = thisY;
2089 *mOutData++ = scanAheadM;
2090 if ( zOutData )
2091 *zOutData++ = thisZ;
2092 for ( ; i < j; ++i )
2093 {
2094 thisX = *xData++;
2095 thisY = *yData++;
2096 *xOutData++ = thisX;
2097 *yOutData++ = thisY;
2098 *mOutData++ = scanAheadM;
2099 mData++;
2100 if ( zOutData )
2101 *zOutData++ = *zData++;
2102 }
2103 lastValidM = scanAheadM;
2104 }
2105 else
2106 {
2107 // nan m value in middle of line, read ahead till next non-nan value and interpolate
2108 int j = 0;
2109 double scanAheadX = thisX;
2110 double scanAheadY = thisY;
2111 double scanAheadZ = thisZ;
2112 double distanceToNextValidM = currentSegmentLength;
2113 std::vector< double > scanAheadSegmentLengths;
2114 scanAheadSegmentLengths.emplace_back( currentSegmentLength );
2115
2116 double nextValidM = std::numeric_limits< double >::quiet_NaN();
2117 while ( i + j < totalPoints - 1 )
2118 {
2119 double nextScanAheadX = xData[j];
2120 double nextScanAheadY = yData[j];
2121 double nextScanAheadZ = zData ? zData[j] : 0;
2122 double nextScanAheadM = mData[j];
2123 const double scanAheadSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( scanAheadX, scanAheadY, scanAheadZ, nextScanAheadX, nextScanAheadY, nextScanAheadZ )
2124 : QgsGeometryUtilsBase::distance2D( scanAheadX, scanAheadY, nextScanAheadX, nextScanAheadY );
2125 scanAheadSegmentLengths.emplace_back( scanAheadSegmentLength );
2126 distanceToNextValidM += scanAheadSegmentLength;
2127
2128 if ( !std::isnan( nextScanAheadM ) )
2129 {
2130 nextValidM = nextScanAheadM;
2131 break;
2132 }
2133
2134 scanAheadX = nextScanAheadX;
2135 scanAheadY = nextScanAheadY;
2136 scanAheadZ = nextScanAheadZ;
2137 ++j;
2138 }
2139
2140 if ( std::isnan( nextValidM ) )
2141 {
2142 // no more valid m values, so just fill remainder of vertices with previous valid m value
2143 *xOutData++ = thisX;
2144 *yOutData++ = thisY;
2145 *mOutData++ = lastValidM;
2146 if ( zOutData )
2147 *zOutData++ = thisZ;
2148 ++i;
2149 for ( ; i < totalPoints; ++i )
2150 {
2151 *xOutData++ = *xData++;
2152 *yOutData++ = *yData++;
2153 *mOutData++ = lastValidM;
2154 if ( zOutData )
2155 *zOutData++ = *zData++;
2156 }
2157 break;
2158 }
2159 else
2160 {
2161 // interpolate along segments
2162 const double delta = ( nextValidM - lastValidM ) / distanceToNextValidM;
2163 *xOutData++ = thisX;
2164 *yOutData++ = thisY;
2165 *mOutData++ = lastValidM + delta * scanAheadSegmentLengths[0];
2166 double totalScanAheadLength = scanAheadSegmentLengths[0];
2167 if ( zOutData )
2168 *zOutData++ = thisZ;
2169 for ( int k = 1; k <= j; ++i, ++k )
2170 {
2171 thisX = *xData++;
2172 thisY = *yData++;
2173 *xOutData++ = thisX;
2174 *yOutData++ = thisY;
2175 totalScanAheadLength += scanAheadSegmentLengths[k];
2176 *mOutData++ = lastValidM + delta * totalScanAheadLength;
2177 mData++;
2178 if ( zOutData )
2179 *zOutData++ = *zData++;
2180 }
2181 lastValidM = nextValidM;
2182 }
2183 }
2184
2185 prevX = thisX;
2186 prevY = thisY;
2187 prevZ = thisZ;
2188 ++i;
2189 }
2190 return std::make_unique< QgsLineString >( xOut, yOut, zOut, mOut );
2191}
2192
2194{
2195 // Convert QgsVertexId to simple vertex numbers for linestrings (single ring, single part)
2196 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
2197 return -1.0;
2198
2199 const int fromVertexNumber = fromVertex.vertex;
2200 const int toVertexNumber = toVertex.vertex;
2201
2202 // Ensure fromVertex < toVertex for simplicity
2203 if ( fromVertexNumber > toVertexNumber )
2204 {
2205 return distanceBetweenVertices( QgsVertexId( 0, 0, toVertexNumber ), QgsVertexId( 0, 0, fromVertexNumber ) );
2206 }
2207
2208 const int nPoints = numPoints();
2209 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
2210 return -1.0;
2211
2212 if ( fromVertexNumber == toVertexNumber )
2213 return 0.0;
2214
2215 const bool is3DGeometry = is3D();
2216 const double *xData = mX.constData();
2217 const double *yData = mY.constData();
2218 const double *zData = is3DGeometry ? mZ.constData() : nullptr;
2219 double totalDistance = 0.0;
2220
2221 // For linestring, just accumulate Euclidean distances between consecutive points
2222 for ( int i = fromVertexNumber; i < toVertexNumber; ++i )
2223 {
2224 double dx = xData[i + 1] - xData[i];
2225 double dy = yData[i + 1] - yData[i];
2226 double dz = 0.0;
2227
2228 if ( is3DGeometry )
2229 {
2230 dz = zData[i + 1] - zData[i];
2231 }
2232
2233 totalDistance += std::sqrt( dx * dx + dy * dy + dz * dz );
2234 }
2235
2236 return totalDistance;
2237}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2226
VertexType
Types of vertex.
Definition qgis.h:3276
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3277
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5061
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ LineString25D
LineString25D.
Definition qgis.h:362
@ LineStringM
LineStringM.
Definition qgis.h:330
@ Point
Point.
Definition qgis.h:296
@ LineString
LineString.
Definition qgis.h:297
@ LineStringZM
LineStringZM.
Definition qgis.h:346
@ Unknown
Unknown.
Definition qgis.h:295
@ PointZ
PointZ.
Definition qgis.h:313
@ LineStringZ
LineStringZ.
Definition qgis.h:314
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
virtual bool convertTo(Qgis::WkbType type)
Converts the geometry to a specified type.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
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.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
bool contains(const QgsBox3D &other) const
Returns true when box contains other box.
Definition qgsbox3d.cpp:164
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:388
Compound curve geometry type.
void addCurve(QgsCurve *c, bool extendPrevious=false)
Adds a curve to the geometry (takes ownership).
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition qgscurve.cpp:298
double mSummedUpArea3D
Definition qgscurve.h:408
bool mHasCachedSummedUpArea
Definition qgscurve.h:405
virtual bool isRing() const
Returns true if the curve is a ring.
Definition qgscurve.cpp:65
bool mHasCachedSummedUpArea3D
Definition qgscurve.h:407
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
Definition qgscurve.cpp:247
bool snapToGridPrivate(double hSpacing, double vSpacing, double dSpacing, double mSpacing, const QVector< double > &srcX, const QVector< double > &srcY, const QVector< double > &srcZ, const QVector< double > &srcM, QVector< double > &outX, QVector< double > &outY, QVector< double > &outZ, QVector< double > &outM, bool removeRedundantPoints) const
Helper function for QgsCurve subclasses to snap to grids.
Definition qgscurve.cpp:323
QgsBox3D mBoundingBox
Cached bounding box.
Definition qgscurve.h:403
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
Definition qgscurve.cpp:266
double mSummedUpArea
Definition qgscurve.h:406
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 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 json pointsToJson(const QgsPointSequence &points, int precision, Qgis::GeoJsonProfile profile)
Returns coordinates as json object.
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.
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 type of a point within the curve.
bool isClosed() const override
Returns true if the curve is closed.
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.
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.
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.
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.
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.
void extend(double startDistance, double endDistance, double startDeflection=0, double endDeflection=0)
Extends the line geometry by extrapolating out the start or end of the line by a specified distance.
void sumUpArea3D(double &sum) const override
Calculates the shoelace/triangle formula sum for the points in the linestring.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
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...
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...
bool boundingBoxIntersects(const QgsRectangle &rectangle) const override
Returns true if the bounding box of this geometry intersects with a rectangle.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
QString geometryType() const override
Returns a unique string representing the geometry type.
std::unique_ptr< QgsLineString > measuredLine(double start, double end) const
Re-write the measure ordinate (or add one, if it isn't already there) interpolating the measure betwe...
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.
void close()
Closes the line string by appending the first point to the end of the line, if it is not already clos...
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.
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.
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.
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.
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.
Q_DECL_DEPRECATED QgsBox3D calculateBoundingBox3d() const
Calculates the minimal 3D bounding box for the geometry.
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.
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.
json asJsonObject(int precision=17, Qgis::GeoJsonProfile profile=Qgis::GeoJsonProfile::Legacy) const override
Returns a json object representation of the geometry with the given precision and profile.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:387
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:376
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
bool dropMValue() override
Drops any measure values which exist in the geometry.
QVector< double > mM
int numPoints() const override
Returns the number of points in the curve.
bool isEmpty() const override
Returns true if the geometry is empty.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the simple curve does not have m values.
void splitCurveAtVertexProtected(int index, QVector< double > &x1, QVector< double > &y1, QVector< double > &z1, QVector< double > &m1, QVector< double > &x2, QVector< double > &y2, QVector< double > &z2, QVector< double > &m2) const
Returns coordinate vectors for the split curves.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QVector< double > mZ
const double * yData() const
Returns a const pointer to the y vertex data.
QgsPoint startPoint() const override
Returns the starting point of the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the simple curve.
QVector< double > mX
QgsSimpleCurve * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
const double * xData() const
Returns a const pointer to the x vertex data.
QVector< double > mY
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the simple curve does not have z values.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
void normalize()
Normalizes the current vector in place.
static QgsVector3D crossProduct(const QgsVector3D &v1, const QgsVector3D &v2)
Returns the cross product of two vectors.
static Qgis::WkbType zmType(Qgis::WkbType type, bool hasZ, bool hasM)
Returns the modified input geometry type according to hasZ / hasM.
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.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7324
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7417
QVector< QgsPoint > QgsPointSequence
void simplifySection(int i, int j, const double *x, const double *y, std::vector< bool > &usePoint, const double distanceToleranceSquared, const double epsilon)
QLineF segment(int index, QRectF rect, double radius)
double distance2D(const QgsPolylineXY &coords)
Definition qgstracer.cpp:50
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:94
int ring
Ring number.
Definition qgsvertexid.h:97