QGIS API Documentation 4.1.0-Master (659fe69c07c)
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 ) const
804{
806 points( pts );
807 return { { "type", "LineString" }, { "coordinates", QgsGeometryUtils::pointsToJson( pts, precision ) } };
808}
809
810QString QgsLineString::asKml( int precision ) const
811{
812 QString kml;
813 if ( isRing() )
814 {
815 kml.append( "<LinearRing>"_L1 );
816 }
817 else
818 {
819 kml.append( "<LineString>"_L1 );
820 }
821 bool z = is3D();
822 kml.append( "<altitudeMode>"_L1 );
823 if ( z )
824 {
825 kml.append( "absolute"_L1 );
826 }
827 else
828 {
829 kml.append( "clampToGround"_L1 );
830 }
831 kml.append( "</altitudeMode>"_L1 );
832 kml.append( "<coordinates>"_L1 );
833
834 int nPoints = mX.size();
835 for ( int i = 0; i < nPoints; ++i )
836 {
837 if ( i > 0 )
838 {
839 kml.append( " "_L1 );
840 }
841 kml.append( qgsDoubleToString( mX[i], precision ) );
842 kml.append( ","_L1 );
843 kml.append( qgsDoubleToString( mY[i], precision ) );
844 if ( z )
845 {
846 kml.append( ","_L1 );
847 kml.append( qgsDoubleToString( mZ[i], precision ) );
848 }
849 else
850 {
851 kml.append( ",0"_L1 );
852 }
853 }
854 kml.append( "</coordinates>"_L1 );
855 if ( isRing() )
856 {
857 kml.append( "</LinearRing>"_L1 );
858 }
859 else
860 {
861 kml.append( "</LineString>"_L1 );
862 }
863 return kml;
864}
865
866/***************************************************************************
867 * This class is considered CRITICAL and any change MUST be accompanied with
868 * full unit tests.
869 * See details in QEP #17
870 ****************************************************************************/
871
873{
874 double total = 0;
875 const int size = mX.size();
876 if ( size < 2 )
877 return 0;
878
879 const double *x = mX.constData();
880 const double *y = mY.constData();
881 double dx, dy;
882
883 double prevX = *x++;
884 double prevY = *y++;
885
886 for ( int i = 1; i < size; ++i )
887 {
888 dx = *x - prevX;
889 dy = *y - prevY;
890 total += std::sqrt( dx * dx + dy * dy );
891
892 prevX = *x++;
893 prevY = *y++;
894 }
895 return total;
896}
897
898std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsLineString::splitCurveAtVertex( int index ) const
899{
900 QVector< double > x1, y1, z1, m1;
901 QVector< double > x2, y2, z2, m2;
902 QgsSimpleCurve::splitCurveAtVertexProtected( index, x1, y1, z1, m1, x2, y2, z2, m2 );
903
904 std::unique_ptr< QgsLineString > first;
905 if ( x1.isEmpty() || ( x1.size() < 2 && x2.size() >= 2 ) )
906 first = std::make_unique< QgsLineString >();
907 else
908 first = std::make_unique< QgsLineString >( x1, y1, z1, m1 );
909
910 std::unique_ptr< QgsLineString > second;
911 if ( x2.isEmpty() || x2.size() < 2 )
912 second = std::make_unique< QgsLineString >();
913 else
914 second = std::make_unique< QgsLineString >( x2, y2, z2, m2 );
915
916 return std::make_tuple( std::move( first ), std::move( second ) );
917}
918
919QVector<QgsLineString *> QgsLineString::splitToDisjointXYParts() const
920{
921 const double *allPointsX = xData();
922 const double *allPointsY = yData();
923 size_t allPointsCount = numPoints();
924 QVector<double> partX;
925 QVector<double> partY;
926 QSet<QgsPointXY> partPointSet;
927
928 QVector<QgsLineString *> disjointParts;
929 for ( size_t i = 0; i < allPointsCount; i++ )
930 {
931 const QgsPointXY point( *allPointsX++, *allPointsY++ );
932 if ( partPointSet.contains( point ) )
933 {
934 // This point is used multiple times, cut the curve and add the
935 // current part
936 disjointParts.push_back( new QgsLineString( partX, partY ) );
937 // Now start a new part containing the last line
938 partX = { partX.last() };
939 partY = { partY.last() };
940 partPointSet = { QgsPointXY( partX[0], partY[0] ) };
941 }
942 partX.push_back( point.x() );
943 partY.push_back( point.y() );
944 partPointSet.insert( point );
945 }
946 // Add the last part (if we didn't stop by closing the loop)
947 if ( partX.size() > 1 || disjointParts.size() == 0 )
948 disjointParts.push_back( new QgsLineString( partX, partY ) );
949
950 return disjointParts;
951}
952
954{
955 if ( is3D() )
956 {
957 double total = 0;
958 const int size = mX.size();
959 if ( size < 2 )
960 return 0;
961
962 const double *x = mX.constData();
963 const double *y = mY.constData();
964 const double *z = mZ.constData();
965 double dx, dy, dz;
966
967 double prevX = *x++;
968 double prevY = *y++;
969 double prevZ = *z++;
970
971 for ( int i = 1; i < size; ++i )
972 {
973 dx = *x - prevX;
974 dy = *y - prevY;
975 dz = *z - prevZ;
976 total += std::sqrt( dx * dx + dy * dy + dz * dz );
977
978 prevX = *x++;
979 prevY = *y++;
980 prevZ = *z++;
981 }
982 return total;
983 }
984 else
985 {
986 return length();
987 }
988}
989
990/***************************************************************************
991 * This class is considered CRITICAL and any change MUST be accompanied with
992 * full unit tests.
993 * See details in QEP #17
994 ****************************************************************************/
995
997{
998 Q_UNUSED( tolerance )
999 Q_UNUSED( toleranceType )
1000 return clone();
1001}
1002
1003/***************************************************************************
1004 * This class is considered CRITICAL and any change MUST be accompanied with
1005 * full unit tests.
1006 * See details in QEP #17
1007 ****************************************************************************/
1008
1009void QgsLineString::setPoints( size_t size, const double *x, const double *y, const double *z, const double *m )
1010{
1011 clearCache(); //set bounding box invalid
1012
1013 if ( size == 0 )
1014 {
1015 clear();
1016 return;
1017 }
1018
1019 const bool hasZ = static_cast< bool >( z );
1020 const bool hasM = static_cast< bool >( m );
1021
1022 if ( hasZ && hasM )
1023 {
1025 }
1026 else if ( hasZ )
1027 {
1029 }
1030 else if ( hasM )
1031 {
1033 }
1034 else
1035 {
1037 }
1038
1039 mX.resize( size );
1040 mY.resize( size );
1041 double *destX = mX.data();
1042 double *destY = mY.data();
1043 double *destZ = nullptr;
1044 if ( hasZ )
1045 {
1046 mZ.resize( size );
1047 destZ = mZ.data();
1048 }
1049 else
1050 {
1051 mZ.clear();
1052 }
1053 double *destM = nullptr;
1054 if ( hasM )
1055 {
1056 mM.resize( size );
1057 destM = mM.data();
1058 }
1059 else
1060 {
1061 mM.clear();
1062 }
1063
1064 for ( size_t i = 0; i < size; ++i )
1065 {
1066 *destX++ = *x++;
1067 *destY++ = *y++;
1068 if ( hasZ )
1069 {
1070 *destZ++ = *z++;
1071 }
1072 if ( hasM )
1073 {
1074 *destM++ = *m++;
1075 }
1076 }
1077}
1078
1079/***************************************************************************
1080 * This class is considered CRITICAL and any change MUST be accompanied with
1081 * full unit tests.
1082 * See details in QEP #17
1083 ****************************************************************************/
1084
1086{
1087 return qgis::down_cast< QgsLineString *>( QgsSimpleCurve::reversed() );
1088}
1089
1091 const double distance, const std::function<bool( double, double, double, double, double, double, double, double, double, double, double, double )> &visitPoint
1092) const
1093{
1094 if ( distance < 0 )
1095 return;
1096
1097 double distanceTraversed = 0;
1098 const int totalPoints = numPoints();
1099 if ( totalPoints == 0 )
1100 return;
1101
1102 const double *x = mX.constData();
1103 const double *y = mY.constData();
1104 const double *z = is3D() ? mZ.constData() : nullptr;
1105 const double *m = isMeasure() ? mM.constData() : nullptr;
1106
1107 double prevX = *x++;
1108 double prevY = *y++;
1109 double prevZ = z ? *z++ : 0.0;
1110 double prevM = m ? *m++ : 0.0;
1111
1112 if ( qgsDoubleNear( distance, 0.0 ) )
1113 {
1114 visitPoint( prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM );
1115 return;
1116 }
1117
1118 double pZ = std::numeric_limits<double>::quiet_NaN();
1119 double pM = std::numeric_limits<double>::quiet_NaN();
1120 double nextPointDistance = distance;
1121 const double eps = 4 * nextPointDistance * std::numeric_limits<double>::epsilon();
1122 for ( int i = 1; i < totalPoints; ++i )
1123 {
1124 double thisX = *x++;
1125 double thisY = *y++;
1126 double thisZ = z ? *z++ : 0.0;
1127 double thisM = m ? *m++ : 0.0;
1128
1129 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1130 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength, eps ) )
1131 {
1132 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1133 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
1134 double pX, pY;
1136 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 );
1137
1138 if ( !visitPoint( pX, pY, pZ, pM, prevX, prevY, prevZ, prevM, thisX, thisY, thisZ, thisM ) )
1139 return;
1140
1141 nextPointDistance += distance;
1142 }
1143
1144 distanceTraversed += segmentLength;
1145 prevX = thisX;
1146 prevY = thisY;
1147 prevZ = thisZ;
1148 prevM = thisM;
1149 }
1150}
1151
1152QgsPoint *QgsLineString::interpolatePoint( const double distance ) const
1153{
1154 if ( distance < 0 )
1155 return nullptr;
1156
1158 if ( is3D() )
1159 pointType = Qgis::WkbType::PointZ;
1160 if ( isMeasure() )
1161 pointType = QgsWkbTypes::addM( pointType );
1162
1163 std::unique_ptr< QgsPoint > res;
1164 visitPointsByRegularDistance( distance, [&]( double x, double y, double z, double m, double, double, double, double, double, double, double, double ) -> bool {
1165 res = std::make_unique< QgsPoint >( pointType, x, y, z, m );
1166 return false;
1167 } );
1168 return res.release();
1169}
1170
1171bool QgsLineString::lineLocatePointByM( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance ) const
1172{
1173 return lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, false );
1174}
1175
1176bool QgsLineString::lineLocatePointByMPrivate( double m, double &x, double &y, double &z, double &distanceFromStart, bool use3DDistance, bool haveInterpolatedM ) const
1177{
1178 if ( !isMeasure() )
1179 return false;
1180
1181 distanceFromStart = 0;
1182 const int totalPoints = numPoints();
1183 if ( totalPoints == 0 )
1184 return false;
1185
1186 const double *xData = mX.constData();
1187 const double *yData = mY.constData();
1188 const double *mData = mM.constData();
1189
1190 const double *zData = is3D() ? mZ.constData() : nullptr;
1191 use3DDistance &= static_cast< bool >( zData );
1192
1193 double prevX = *xData++;
1194 double prevY = *yData++;
1195 double prevZ = zData ? *zData++ : 0;
1196 double prevM = *mData++;
1197
1198 int i = 1;
1199 while ( i < totalPoints )
1200 {
1201 double thisX = *xData++;
1202 double thisY = *yData++;
1203 double thisZ = zData ? *zData++ : 0;
1204 double thisM = *mData++;
1205 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( thisX, thisY, thisZ, prevX, prevY, prevZ ) : QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1206
1207 if ( std::isnan( thisM ) )
1208 {
1209 if ( haveInterpolatedM )
1210 return false;
1211
1212 // if we hit a NaN m value, interpolate m to fill the blanks and then re-try
1213 std::unique_ptr< QgsLineString > interpolatedM( interpolateM( use3DDistance ) );
1214 return interpolatedM->lineLocatePointByMPrivate( m, x, y, z, distanceFromStart, use3DDistance, true );
1215 }
1216 else
1217 {
1218 // check if target m value falls within this segment's range
1219 if ( ( prevM < m && thisM > m ) || ( prevM > m && thisM < m ) || qgsDoubleNear( prevM, m ) || qgsDoubleNear( thisM, m ) )
1220 {
1221 // use centroid for constant value m segments
1222 if ( qgsDoubleNear( thisM, m ) && ( i < totalPoints - 1 ) && qgsDoubleNear( *mData, m ) )
1223 {
1224 distanceFromStart += segmentLength;
1225 // scan ahead till we find a vertex with a different m
1226 double totalLengthOfSegmentsWithConstantM = 0;
1227 for ( int j = 0; j < ( totalPoints - i ); ++j )
1228 {
1229 if ( !qgsDoubleNear( *( mData + j ), m ) )
1230 break;
1231
1232 const double segmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( *( xData + j - 1 ), *( yData + j - 1 ), *( zData + j - 1 ), *( xData + j ), *( yData + j ), *( zData + j ) )
1233 : QgsGeometryUtilsBase::distance2D( *( xData + j - 1 ), *( yData + j - 1 ), *( xData + j ), *( yData + j ) );
1234 totalLengthOfSegmentsWithConstantM += segmentLength;
1235 }
1236
1237 distanceFromStart += totalLengthOfSegmentsWithConstantM / 2;
1238 std::unique_ptr< QgsPoint> point( interpolatePoint( distanceFromStart ) );
1239 if ( !point )
1240 return false;
1241 x = point->x();
1242 y = point->y();
1243 z = point->z();
1244 return true;
1245 }
1246
1247 const double delta = ( m - prevM ) / ( thisM - prevM );
1248
1249 const double distanceToPoint = delta * segmentLength;
1250
1251 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, x, y );
1252 z = prevZ + ( thisZ - prevZ ) * delta;
1253 distanceFromStart += distanceToPoint;
1254 return true;
1255 }
1256 }
1257
1258 distanceFromStart += segmentLength;
1259 prevX = thisX;
1260 prevY = thisY;
1261 prevZ = thisZ;
1262 prevM = thisM;
1263 ++i;
1264 }
1265 return false;
1266}
1267
1268QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
1269{
1270 if ( startDistance < 0 && endDistance < 0 )
1271 return createEmptyWithSameType();
1272
1273 endDistance = std::max( startDistance, endDistance );
1274
1275 const int totalPoints = numPoints();
1276 if ( totalPoints == 0 )
1277 return clone();
1278
1279 QVector< QgsPoint > substringPoints;
1280 substringPoints.reserve( totalPoints );
1281
1283 if ( is3D() )
1284 pointType = Qgis::WkbType::PointZ;
1285 if ( isMeasure() )
1286 pointType = QgsWkbTypes::addM( pointType );
1287
1288 const double *x = mX.constData();
1289 const double *y = mY.constData();
1290 const double *z = is3D() ? mZ.constData() : nullptr;
1291 const double *m = isMeasure() ? mM.constData() : nullptr;
1292
1293 double distanceTraversed = 0;
1294 double prevX = *x++;
1295 double prevY = *y++;
1296 double prevZ = z ? *z++ : 0.0;
1297 double prevM = m ? *m++ : 0.0;
1298 bool foundStart = false;
1299
1300 if ( startDistance < 0 )
1301 startDistance = 0;
1302
1303 for ( int i = 1; i < totalPoints; ++i )
1304 {
1305 double thisX = *x++;
1306 double thisY = *y++;
1307 double thisZ = z ? *z++ : 0.0;
1308 double thisM = m ? *m++ : 0.0;
1309
1310 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1311
1312 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1313 {
1314 // start point falls on this segment
1315 const double distanceToStart = startDistance - distanceTraversed;
1316 double startX, startY;
1317 double startZ = 0;
1318 double startM = 0;
1320 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 );
1321 substringPoints << QgsPoint( pointType, startX, startY, startZ, startM );
1322 foundStart = true;
1323 }
1324 if ( foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1325 {
1326 // end point falls on this segment
1327 const double distanceToEnd = endDistance - distanceTraversed;
1328 double endX, endY;
1329 double endZ = 0;
1330 double endM = 0;
1332 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 );
1333 substringPoints << QgsPoint( pointType, endX, endY, endZ, endM );
1334 }
1335 else if ( foundStart )
1336 {
1337 substringPoints << QgsPoint( pointType, thisX, thisY, thisZ, thisM );
1338 }
1339
1340 prevX = thisX;
1341 prevY = thisY;
1342 prevZ = thisZ;
1343 prevM = thisM;
1344 distanceTraversed += segmentLength;
1345 if ( distanceTraversed >= endDistance )
1346 break;
1347 }
1348
1349 // start point is the last node
1350 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1351 {
1352 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM ) << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1353 }
1354
1355 return new QgsLineString( substringPoints );
1356}
1357
1358/***************************************************************************
1359 * This class is considered CRITICAL and any change MUST be accompanied with
1360 * full unit tests.
1361 * See details in QEP #17
1362 ****************************************************************************/
1363
1364void QgsLineString::draw( QPainter &p ) const
1365{
1366 p.drawPolyline( asQPolygonF() );
1367}
1368
1369void QgsLineString::addToPainterPath( QPainterPath &path ) const
1370{
1371 int nPoints = numPoints();
1372 if ( nPoints < 1 )
1373 {
1374 return;
1375 }
1376
1377 if ( path.isEmpty() || path.currentPosition() != QPointF( mX.at( 0 ), mY.at( 0 ) ) )
1378 {
1379 path.moveTo( mX.at( 0 ), mY.at( 0 ) );
1380 }
1381
1382 for ( int i = 1; i < nPoints; ++i )
1383 {
1384 path.lineTo( mX.at( i ), mY.at( i ) );
1385 }
1386}
1387
1388void QgsLineString::drawAsPolygon( QPainter &p ) const
1389{
1390 p.drawPolygon( asQPolygonF() );
1391}
1392
1394{
1395 QgsCompoundCurve *compoundCurve = new QgsCompoundCurve();
1396 compoundCurve->addCurve( clone() );
1397 return compoundCurve;
1398}
1399
1400void QgsLineString::extend( double startDistance, double endDistance )
1401{
1402 if ( mX.size() < 2 || mY.size() < 2 )
1403 return;
1404
1405 const bool extendStart = startDistance > 0;
1406 const bool extendEnd = endDistance > 0;
1407
1408 // start of line
1409 if ( extendStart )
1410 {
1411 const double currentLen = std::sqrt( std::pow( mX.at( 0 ) - mX.at( 1 ), 2 ) + std::pow( mY.at( 0 ) - mY.at( 1 ), 2 ) );
1412 const double newLen = currentLen + startDistance;
1413 mX[0] = mX.at( 1 ) + ( mX.at( 0 ) - mX.at( 1 ) ) / currentLen * newLen;
1414 mY[0] = mY.at( 1 ) + ( mY.at( 0 ) - mY.at( 1 ) ) / currentLen * newLen;
1415 }
1416 // end of line
1417 if ( extendEnd )
1418 {
1419 const int last = mX.size() - 1;
1420 const double currentLen = std::sqrt( std::pow( mX.at( last ) - mX.at( last - 1 ), 2 ) + std::pow( mY.at( last ) - mY.at( last - 1 ), 2 ) );
1421 const double newLen = currentLen + endDistance;
1422 mX[last] = mX.at( last - 1 ) + ( mX.at( last ) - mX.at( last - 1 ) ) / currentLen * newLen;
1423 mY[last] = mY.at( last - 1 ) + ( mY.at( last ) - mY.at( last - 1 ) ) / currentLen * newLen;
1424 }
1425
1426 if ( extendStart || extendEnd )
1427 clearCache(); //set bounding box invalid
1428}
1429
1431{
1432 auto result = std::make_unique< QgsLineString >();
1433 result->mWkbType = mWkbType;
1434 return result.release();
1435}
1436
1438{
1439 return u"LineString"_s;
1440}
1441
1442/***************************************************************************
1443 * This class is considered CRITICAL and any change MUST be accompanied with
1444 * full unit tests.
1445 * See details in QEP #17
1446 ****************************************************************************/
1447
1448bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
1449{
1450 if ( position.vertex < 0 || position.vertex > mX.size() )
1451 {
1452 return false;
1453 }
1454
1455 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1456 {
1458 }
1459
1460 mX.insert( position.vertex, vertex.x() );
1461 mY.insert( position.vertex, vertex.y() );
1462 if ( is3D() )
1463 {
1464 mZ.insert( position.vertex, vertex.z() );
1465 }
1466 if ( isMeasure() )
1467 {
1468 mM.insert( position.vertex, vertex.m() );
1469 }
1470 clearCache(); //set bounding box invalid
1471 return true;
1472}
1473
1475{
1476 if ( position.vertex >= mX.size() || position.vertex < 0 )
1477 {
1478 return false;
1479 }
1480
1481 mX.remove( position.vertex );
1482 mY.remove( position.vertex );
1483 if ( is3D() )
1484 {
1485 mZ.remove( position.vertex );
1486 }
1487 if ( isMeasure() )
1488 {
1489 mM.remove( position.vertex );
1490 }
1491
1492 if ( numPoints() == 1 )
1493 {
1494 clear();
1495 }
1496
1497 clearCache(); //set bounding box invalid
1498 return true;
1499}
1500
1501bool QgsLineString::deleteVertices( const QSet<QgsVertexId> &positions )
1502{
1503 if ( positions.isEmpty() )
1504 {
1505 return false;
1506 }
1507
1508 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
1509
1510 for ( QgsVertexId pos : positions )
1511 {
1512 if ( !hasVertex( pos ) )
1513 {
1514 return false;
1515 }
1516 }
1517
1518 if ( numPoints() <= vertices.size() + 1 )
1519 {
1520 clear();
1521 return true;
1522 }
1523
1524 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex > b.vertex; } );
1525
1526 for ( QgsVertexId position : vertices )
1527 {
1528 mX.remove( position.vertex );
1529 mY.remove( position.vertex );
1530 if ( is3D() )
1531 {
1532 mZ.remove( position.vertex );
1533 }
1534 if ( isMeasure() )
1535 {
1536 mM.remove( position.vertex );
1537 }
1538 }
1539
1540 clearCache(); //set bounding box invalid
1541 return true;
1542}
1543
1544/***************************************************************************
1545 * This class is considered CRITICAL and any change MUST be accompanied with
1546 * full unit tests.
1547 * See details in QEP #17
1548 ****************************************************************************/
1549
1551{
1552 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1553 {
1555 }
1556
1557 mX.append( pt.x() );
1558 mY.append( pt.y() );
1559 if ( is3D() )
1560 {
1561 mZ.append( pt.z() );
1562 }
1563 if ( isMeasure() )
1564 {
1565 mM.append( pt.m() );
1566 }
1567 clearCache(); //set bounding box invalid
1568}
1569
1570double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1571{
1572 double sqrDist = std::numeric_limits<double>::max();
1573 double leftOfDist = std::numeric_limits<double>::max();
1574 int prevLeftOf = 0;
1575 double prevLeftOfX = 0.0;
1576 double prevLeftOfY = 0.0;
1577 double testDist = 0;
1578 double segmentPtX, segmentPtY;
1579
1580 if ( leftOf )
1581 *leftOf = 0;
1582
1583 const int size = mX.size();
1584 if ( size == 0 || size == 1 )
1585 {
1586 vertexAfter = QgsVertexId( 0, 0, 0 );
1587 return -1;
1588 }
1589
1590 const double *xData = mX.constData();
1591 const double *yData = mY.constData();
1592 for ( int i = 1; i < size; ++i )
1593 {
1594 double prevX = xData[i - 1];
1595 double prevY = yData[i - 1];
1596 double currentX = xData[i];
1597 double currentY = yData[i];
1598 testDist = QgsGeometryUtilsBase::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon );
1599 if ( testDist < sqrDist )
1600 {
1601 sqrDist = testDist;
1602 segmentPt.setX( segmentPtX );
1603 segmentPt.setY( segmentPtY );
1604 vertexAfter.part = 0;
1605 vertexAfter.ring = 0;
1606 vertexAfter.vertex = i;
1607 }
1608 if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
1609 {
1610 int left = QgsGeometryUtilsBase::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
1611 // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
1612 // so don't set leftOf in this case, and hope that there's another segment that's the same distance
1613 // where we can perform the check
1614 if ( left != 0 )
1615 {
1616 if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 )
1617 {
1618 // we have two possible segments each with equal distance to point, but they disagree
1619 // on whether or not the point is to the left of them.
1620 // so we test the segments themselves and flip the result.
1621 // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
1622 *leftOf = -QgsGeometryUtilsBase::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY );
1623 }
1624 else
1625 {
1626 *leftOf = left;
1627 }
1628 prevLeftOf = *leftOf;
1629 leftOfDist = testDist;
1630 prevLeftOfX = prevX;
1631 prevLeftOfY = prevY;
1632 }
1633 else if ( testDist < leftOfDist )
1634 {
1635 *leftOf = left;
1636 leftOfDist = testDist;
1637 prevLeftOf = 0;
1638 }
1639 }
1640 }
1641 return sqrDist;
1642}
1643
1644/***************************************************************************
1645 * This class is considered CRITICAL and any change MUST be accompanied with
1646 * full unit tests.
1647 * See details in QEP #17
1648 ****************************************************************************/
1649
1650bool QgsLineString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1651{
1652 if ( node < 0 || node >= numPoints() )
1653 {
1654 return false;
1655 }
1656 point = pointN( node );
1658 return true;
1659}
1660
1662{
1663 if ( mX.isEmpty() )
1664 return QgsPoint();
1665
1666 int numPoints = mX.count();
1667 if ( numPoints == 1 )
1668 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1669
1670 double totalLineLength = 0.0;
1671 double prevX = mX.at( 0 );
1672 double prevY = mY.at( 0 );
1673 double sumX = 0.0;
1674 double sumY = 0.0;
1675
1676 for ( int i = 1; i < numPoints; ++i )
1677 {
1678 double currentX = mX.at( i );
1679 double currentY = mY.at( i );
1680 double segmentLength = std::sqrt( std::pow( currentX - prevX, 2.0 ) + std::pow( currentY - prevY, 2.0 ) );
1681 if ( qgsDoubleNear( segmentLength, 0.0 ) )
1682 continue;
1683
1684 totalLineLength += segmentLength;
1685 sumX += segmentLength * ( currentX + prevX );
1686 sumY += segmentLength * ( currentY + prevY );
1687 prevX = currentX;
1688 prevY = currentY;
1689 }
1690 sumX *= 0.5;
1691 sumY *= 0.5;
1692
1693 if ( qgsDoubleNear( totalLineLength, 0.0 ) )
1694 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1695 else
1696 return QgsPoint( sumX / totalLineLength, sumY / totalLineLength );
1697}
1698
1699/***************************************************************************
1700 * This class is considered CRITICAL and any change MUST be accompanied with
1701 * full unit tests.
1702 * See details in QEP #17
1703 ****************************************************************************/
1704
1705void QgsLineString::sumUpArea( double &sum ) const
1706{
1708 {
1709 sum += mSummedUpArea;
1710 return;
1711 }
1712
1713 mSummedUpArea = 0;
1714 const int maxIndex = mX.size();
1715 if ( maxIndex < 2 )
1716 {
1718 return;
1719 }
1720
1721 const double *x = mX.constData();
1722 const double *y = mY.constData();
1723 double prevX = *x++;
1724 double prevY = *y++;
1725 for ( int i = 1; i < maxIndex; ++i )
1726 {
1727 mSummedUpArea += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1728 prevX = *x++;
1729 prevY = *y++;
1730 }
1731 mSummedUpArea *= 0.5;
1732
1734 sum += mSummedUpArea;
1735}
1736
1737void QgsLineString::sumUpArea3D( double &sum ) const
1738{
1740 {
1741 sum += mSummedUpArea3D;
1742 return;
1743 }
1744
1745 // No Z component. Fallback to the 2D version
1746 if ( mZ.isEmpty() )
1747 {
1748 double area2D = 0;
1749 sumUpArea( area2D );
1750 mSummedUpArea3D = area2D;
1752 sum += mSummedUpArea3D;
1753 return;
1754 }
1755
1756 mSummedUpArea3D = 0;
1757
1758 // Look for a reference unit normal
1759 QgsPoint ptA;
1760 QgsPoint ptB;
1761 QgsPoint ptC;
1762 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
1763 {
1765 return;
1766 }
1767
1768 QgsVector3D vAB = QgsVector3D( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
1769 QgsVector3D vAC = QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() );
1770 QgsVector3D planeNormal = QgsVector3D::crossProduct( vAB, vAC );
1771
1772 // Ensure a Consistent orientation: prioritize Z+, then Y+, then X+
1773 if ( !qgsDoubleNear( planeNormal.z(), 0.0 ) )
1774 {
1775 if ( planeNormal.z() < 0 )
1776 {
1777 planeNormal = -planeNormal;
1778 }
1779 }
1780 else if ( !qgsDoubleNear( planeNormal.y(), 0.0 ) )
1781 {
1782 if ( planeNormal.y() < 0 )
1783 planeNormal = -planeNormal;
1784 }
1785 else
1786 {
1787 if ( planeNormal.x() < 0 )
1788 planeNormal = -planeNormal;
1789 }
1790 planeNormal.normalize();
1791
1792 const double *x = mX.constData();
1793 const double *y = mY.constData();
1794 const double *z = mZ.constData();
1795
1796 double prevX = *x++;
1797 double prevY = *y++;
1798 double prevZ = *z++;
1799
1800 double normalX = 0.0;
1801 double normalY = 0.0; // #spellok - Y component of normal vector
1802 double normalZ = 0.0;
1803
1804 for ( unsigned int i = 1; i < mX.size(); ++i )
1805 {
1806 normalX += prevY * ( *z - prevZ ) - prevZ * ( *y - prevY );
1807 normalY += prevZ * ( *x - prevX ) - prevX * ( *z - prevZ ); // #spellok
1808 normalZ += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1809
1810 prevX = *x++;
1811 prevY = *y++;
1812 prevZ = *z++;
1813 }
1814
1815 mSummedUpArea3D = 0.5 * ( normalX * planeNormal.x() + normalY * planeNormal.y() + normalZ * planeNormal.z() ); // #spellok
1816
1818 sum += mSummedUpArea3D;
1819}
1820
1821/***************************************************************************
1822 * This class is considered CRITICAL and any change MUST be accompanied with
1823 * full unit tests.
1824 * See details in QEP #17
1825 ****************************************************************************/
1826
1828{
1829 if ( numPoints() < 1 || isClosed() )
1830 {
1831 return;
1832 }
1833 addVertex( startPoint() );
1834}
1835
1837{
1838 if ( mX.count() < 2 )
1839 {
1840 //undefined
1841 return 0.0;
1842 }
1843
1844 if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
1845 {
1846 if ( isClosed() )
1847 {
1848 double previousX = mX.at( numPoints() - 2 );
1849 double previousY = mY.at( numPoints() - 2 );
1850 double currentX = mX.at( 0 );
1851 double currentY = mY.at( 0 );
1852 double afterX = mX.at( 1 );
1853 double afterY = mY.at( 1 );
1854 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1855 }
1856 else if ( vertex.vertex == 0 )
1857 {
1858 return QgsGeometryUtilsBase::lineAngle( mX.at( 0 ), mY.at( 0 ), mX.at( 1 ), mY.at( 1 ) );
1859 }
1860 else
1861 {
1862 int a = numPoints() - 2;
1863 int b = numPoints() - 1;
1864 return QgsGeometryUtilsBase::lineAngle( mX.at( a ), mY.at( a ), mX.at( b ), mY.at( b ) );
1865 }
1866 }
1867 else
1868 {
1869 double previousX = mX.at( vertex.vertex - 1 );
1870 double previousY = mY.at( vertex.vertex - 1 );
1871 double currentX = mX.at( vertex.vertex );
1872 double currentY = mY.at( vertex.vertex );
1873 double afterX = mX.at( vertex.vertex + 1 );
1874 double afterY = mY.at( vertex.vertex + 1 );
1875 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1876 }
1877}
1878
1880{
1881 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 1 )
1882 return 0.0;
1883
1884 double dx = mX.at( startVertex.vertex + 1 ) - mX.at( startVertex.vertex );
1885 double dy = mY.at( startVertex.vertex + 1 ) - mY.at( startVertex.vertex );
1886 return std::sqrt( dx * dx + dy * dy );
1887}
1888
1889/***************************************************************************
1890 * This class is considered CRITICAL and any change MUST be accompanied with
1891 * full unit tests.
1892 * See details in QEP #17
1893 ****************************************************************************/
1894
1896{
1897 if ( type == mWkbType )
1898 return true;
1899
1900 clearCache();
1901 if ( type == Qgis::WkbType::LineString25D )
1902 {
1903 //special handling required for conversion to LineString25D
1904 dropMValue();
1905 addZValue( std::numeric_limits<double>::quiet_NaN() );
1907 return true;
1908 }
1909 else
1910 {
1911 return QgsCurve::convertTo( type );
1912 }
1913}
1914
1915std::unique_ptr< QgsLineString > QgsLineString::measuredLine( double start, double end ) const
1916{
1917 const int nbpoints = numPoints();
1918 std::unique_ptr< QgsLineString > cloned( clone() );
1919
1920 if ( !cloned->convertTo( QgsWkbTypes::addM( mWkbType ) ) )
1921 {
1922 return cloned;
1923 }
1924
1925 if ( isEmpty() || ( nbpoints < 2 ) )
1926 {
1927 return cloned;
1928 }
1929
1930 const double range = end - start;
1931 double lineLength = length();
1932 double lengthSoFar = 0.0;
1933
1934
1935 double *mOut = cloned->mM.data();
1936 *mOut++ = start;
1937 for ( int i = 1; i < nbpoints; ++i )
1938 {
1939 lengthSoFar += QgsGeometryUtilsBase::distance2D( mX[i - 1], mY[i - 1], mX[i], mY[i] );
1940 if ( lineLength > 0.0 )
1941 *mOut++ = start + range * lengthSoFar / lineLength;
1942 else if ( lineLength == 0.0 && nbpoints > 1 )
1943 *mOut++ = start + range * i / ( nbpoints - 1 );
1944 else
1945 *mOut++ = 0.0;
1946 }
1947
1948 return cloned;
1949}
1950
1951std::unique_ptr< QgsLineString > QgsLineString::interpolateM( bool use3DDistance ) const
1952{
1953 if ( !isMeasure() )
1954 return nullptr;
1955
1956 const int totalPoints = numPoints();
1957 if ( totalPoints < 2 )
1958 return std::unique_ptr< QgsLineString >( clone() );
1959
1960 const double *xData = mX.constData();
1961 const double *yData = mY.constData();
1962 const double *mData = mM.constData();
1963 const double *zData = is3D() ? mZ.constData() : nullptr;
1964 use3DDistance &= static_cast< bool >( zData );
1965
1966 QVector< double > xOut( totalPoints );
1967 QVector< double > yOut( totalPoints );
1968 QVector< double > mOut( totalPoints );
1969 QVector< double > zOut( static_cast< bool >( zData ) ? totalPoints : 0 );
1970
1971 double *xOutData = xOut.data();
1972 double *yOutData = yOut.data();
1973 double *mOutData = mOut.data();
1974 double *zOutData = static_cast< bool >( zData ) ? zOut.data() : nullptr;
1975
1976 int i = 0;
1977 double currentSegmentLength = 0;
1978 double lastValidM = std::numeric_limits< double >::quiet_NaN();
1979 double prevX = *xData;
1980 double prevY = *yData;
1981 double prevZ = zData ? *zData : 0;
1982 while ( i < totalPoints )
1983 {
1984 double thisX = *xData++;
1985 double thisY = *yData++;
1986 double thisZ = zData ? *zData++ : 0;
1987 double thisM = *mData++;
1988
1989 currentSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( prevX, prevY, prevZ, thisX, thisY, thisZ ) : QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
1990
1991 if ( !std::isnan( thisM ) )
1992 {
1993 *xOutData++ = thisX;
1994 *yOutData++ = thisY;
1995 *mOutData++ = thisM;
1996 if ( zOutData )
1997 *zOutData++ = thisZ;
1998 lastValidM = thisM;
1999 }
2000 else if ( i == 0 )
2001 {
2002 // nan m value at start of line, read ahead to find first non-nan value and backfill
2003 int j = 0;
2004 double scanAheadM = thisM;
2005 while ( i + j + 1 < totalPoints && std::isnan( scanAheadM ) )
2006 {
2007 scanAheadM = mData[j];
2008 ++j;
2009 }
2010 if ( std::isnan( scanAheadM ) )
2011 {
2012 // no valid m values in line
2013 return nullptr;
2014 }
2015 *xOutData++ = thisX;
2016 *yOutData++ = thisY;
2017 *mOutData++ = scanAheadM;
2018 if ( zOutData )
2019 *zOutData++ = thisZ;
2020 for ( ; i < j; ++i )
2021 {
2022 thisX = *xData++;
2023 thisY = *yData++;
2024 *xOutData++ = thisX;
2025 *yOutData++ = thisY;
2026 *mOutData++ = scanAheadM;
2027 mData++;
2028 if ( zOutData )
2029 *zOutData++ = *zData++;
2030 }
2031 lastValidM = scanAheadM;
2032 }
2033 else
2034 {
2035 // nan m value in middle of line, read ahead till next non-nan value and interpolate
2036 int j = 0;
2037 double scanAheadX = thisX;
2038 double scanAheadY = thisY;
2039 double scanAheadZ = thisZ;
2040 double distanceToNextValidM = currentSegmentLength;
2041 std::vector< double > scanAheadSegmentLengths;
2042 scanAheadSegmentLengths.emplace_back( currentSegmentLength );
2043
2044 double nextValidM = std::numeric_limits< double >::quiet_NaN();
2045 while ( i + j < totalPoints - 1 )
2046 {
2047 double nextScanAheadX = xData[j];
2048 double nextScanAheadY = yData[j];
2049 double nextScanAheadZ = zData ? zData[j] : 0;
2050 double nextScanAheadM = mData[j];
2051 const double scanAheadSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( scanAheadX, scanAheadY, scanAheadZ, nextScanAheadX, nextScanAheadY, nextScanAheadZ )
2052 : QgsGeometryUtilsBase::distance2D( scanAheadX, scanAheadY, nextScanAheadX, nextScanAheadY );
2053 scanAheadSegmentLengths.emplace_back( scanAheadSegmentLength );
2054 distanceToNextValidM += scanAheadSegmentLength;
2055
2056 if ( !std::isnan( nextScanAheadM ) )
2057 {
2058 nextValidM = nextScanAheadM;
2059 break;
2060 }
2061
2062 scanAheadX = nextScanAheadX;
2063 scanAheadY = nextScanAheadY;
2064 scanAheadZ = nextScanAheadZ;
2065 ++j;
2066 }
2067
2068 if ( std::isnan( nextValidM ) )
2069 {
2070 // no more valid m values, so just fill remainder of vertices with previous valid m value
2071 *xOutData++ = thisX;
2072 *yOutData++ = thisY;
2073 *mOutData++ = lastValidM;
2074 if ( zOutData )
2075 *zOutData++ = thisZ;
2076 ++i;
2077 for ( ; i < totalPoints; ++i )
2078 {
2079 *xOutData++ = *xData++;
2080 *yOutData++ = *yData++;
2081 *mOutData++ = lastValidM;
2082 if ( zOutData )
2083 *zOutData++ = *zData++;
2084 }
2085 break;
2086 }
2087 else
2088 {
2089 // interpolate along segments
2090 const double delta = ( nextValidM - lastValidM ) / distanceToNextValidM;
2091 *xOutData++ = thisX;
2092 *yOutData++ = thisY;
2093 *mOutData++ = lastValidM + delta * scanAheadSegmentLengths[0];
2094 double totalScanAheadLength = scanAheadSegmentLengths[0];
2095 if ( zOutData )
2096 *zOutData++ = thisZ;
2097 for ( int k = 1; k <= j; ++i, ++k )
2098 {
2099 thisX = *xData++;
2100 thisY = *yData++;
2101 *xOutData++ = thisX;
2102 *yOutData++ = thisY;
2103 totalScanAheadLength += scanAheadSegmentLengths[k];
2104 *mOutData++ = lastValidM + delta * totalScanAheadLength;
2105 mData++;
2106 if ( zOutData )
2107 *zOutData++ = *zData++;
2108 }
2109 lastValidM = nextValidM;
2110 }
2111 }
2112
2113 prevX = thisX;
2114 prevY = thisY;
2115 prevZ = thisZ;
2116 ++i;
2117 }
2118 return std::make_unique< QgsLineString >( xOut, yOut, zOut, mOut );
2119}
2120
2122{
2123 // Convert QgsVertexId to simple vertex numbers for linestrings (single ring, single part)
2124 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
2125 return -1.0;
2126
2127 const int fromVertexNumber = fromVertex.vertex;
2128 const int toVertexNumber = toVertex.vertex;
2129
2130 // Ensure fromVertex < toVertex for simplicity
2131 if ( fromVertexNumber > toVertexNumber )
2132 {
2133 return distanceBetweenVertices( QgsVertexId( 0, 0, toVertexNumber ), QgsVertexId( 0, 0, fromVertexNumber ) );
2134 }
2135
2136 const int nPoints = numPoints();
2137 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
2138 return -1.0;
2139
2140 if ( fromVertexNumber == toVertexNumber )
2141 return 0.0;
2142
2143 const bool is3DGeometry = is3D();
2144 const double *xData = mX.constData();
2145 const double *yData = mY.constData();
2146 const double *zData = is3DGeometry ? mZ.constData() : nullptr;
2147 double totalDistance = 0.0;
2148
2149 // For linestring, just accumulate Euclidean distances between consecutive points
2150 for ( int i = fromVertexNumber; i < toVertexNumber; ++i )
2151 {
2152 double dx = xData[i + 1] - xData[i];
2153 double dy = yData[i + 1] - yData[i];
2154 double dz = 0.0;
2155
2156 if ( is3DGeometry )
2157 {
2158 dz = zData[i + 1] - zData[i];
2159 }
2160
2161 totalDistance += std::sqrt( dx * dx + dy * dy + dz * dz );
2162 }
2163
2164 return totalDistance;
2165}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2197
VertexType
Types of vertex.
Definition qgis.h:3247
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3248
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 json pointsToJson(const QgsPointSequence &points, int precision)
Returns coordinates as json object.
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.
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 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.
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.
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.
json asJsonObject(int precision=17) const override
Returns a json object representation of 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.
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:7247
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7340
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