QGIS API Documentation 4.3.0-Master (bf28115e945)
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 )
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 // start of line
1413 if ( extendStart )
1414 {
1415 const double currentLen = std::sqrt( std::pow( mX.at( 0 ) - mX.at( 1 ), 2 ) + std::pow( mY.at( 0 ) - mY.at( 1 ), 2 ) );
1416 const double newLen = currentLen + startDistance;
1417 mX[0] = mX.at( 1 ) + ( mX.at( 0 ) - mX.at( 1 ) ) / currentLen * newLen;
1418 mY[0] = mY.at( 1 ) + ( mY.at( 0 ) - mY.at( 1 ) ) / currentLen * newLen;
1419 }
1420 // end of line
1421 if ( extendEnd )
1422 {
1423 const int last = mX.size() - 1;
1424 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 ) );
1425 const double newLen = currentLen + endDistance;
1426 mX[last] = mX.at( last - 1 ) + ( mX.at( last ) - mX.at( last - 1 ) ) / currentLen * newLen;
1427 mY[last] = mY.at( last - 1 ) + ( mY.at( last ) - mY.at( last - 1 ) ) / currentLen * newLen;
1428 }
1429
1430 if ( extendStart || extendEnd )
1431 clearCache(); //set bounding box invalid
1432}
1433
1435{
1436 auto result = std::make_unique< QgsLineString >();
1437 result->mWkbType = mWkbType;
1438 return result.release();
1439}
1440
1442{
1443 return u"LineString"_s;
1444}
1445
1446/***************************************************************************
1447 * This class is considered CRITICAL and any change MUST be accompanied with
1448 * full unit tests.
1449 * See details in QEP #17
1450 ****************************************************************************/
1451
1452bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
1453{
1454 if ( position.vertex < 0 || position.vertex > mX.size() )
1455 {
1456 return false;
1457 }
1458
1459 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1460 {
1462 }
1463
1464 mX.insert( position.vertex, vertex.x() );
1465 mY.insert( position.vertex, vertex.y() );
1466 if ( is3D() )
1467 {
1468 mZ.insert( position.vertex, vertex.z() );
1469 }
1470 if ( isMeasure() )
1471 {
1472 mM.insert( position.vertex, vertex.m() );
1473 }
1474 clearCache(); //set bounding box invalid
1475 return true;
1476}
1477
1479{
1480 if ( position.vertex >= mX.size() || position.vertex < 0 )
1481 {
1482 return false;
1483 }
1484
1485 mX.remove( position.vertex );
1486 mY.remove( position.vertex );
1487 if ( is3D() )
1488 {
1489 mZ.remove( position.vertex );
1490 }
1491 if ( isMeasure() )
1492 {
1493 mM.remove( position.vertex );
1494 }
1495
1496 if ( numPoints() == 1 )
1497 {
1498 clear();
1499 }
1500
1501 clearCache(); //set bounding box invalid
1502 return true;
1503}
1504
1505bool QgsLineString::deleteVertices( const QSet<QgsVertexId> &positions )
1506{
1507 if ( positions.isEmpty() )
1508 {
1509 return false;
1510 }
1511
1512 QList<QgsVertexId> vertices( positions.begin(), positions.end() );
1513
1514 for ( QgsVertexId pos : positions )
1515 {
1516 if ( !hasVertex( pos ) )
1517 {
1518 return false;
1519 }
1520 }
1521
1522 if ( numPoints() <= vertices.size() + 1 )
1523 {
1524 clear();
1525 return true;
1526 }
1527
1528 std::sort( vertices.begin(), vertices.end(), []( const QgsVertexId &a, const QgsVertexId &b ) { return a.vertex > b.vertex; } );
1529
1530 for ( QgsVertexId position : vertices )
1531 {
1532 mX.remove( position.vertex );
1533 mY.remove( position.vertex );
1534 if ( is3D() )
1535 {
1536 mZ.remove( position.vertex );
1537 }
1538 if ( isMeasure() )
1539 {
1540 mM.remove( position.vertex );
1541 }
1542 }
1543
1544 clearCache(); //set bounding box invalid
1545 return true;
1546}
1547
1548/***************************************************************************
1549 * This class is considered CRITICAL and any change MUST be accompanied with
1550 * full unit tests.
1551 * See details in QEP #17
1552 ****************************************************************************/
1553
1555{
1556 if ( mWkbType == Qgis::WkbType::Unknown || mX.isEmpty() )
1557 {
1559 }
1560
1561 mX.append( pt.x() );
1562 mY.append( pt.y() );
1563 if ( is3D() )
1564 {
1565 mZ.append( pt.z() );
1566 }
1567 if ( isMeasure() )
1568 {
1569 mM.append( pt.m() );
1570 }
1571 clearCache(); //set bounding box invalid
1572}
1573
1574double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1575{
1576 double sqrDist = std::numeric_limits<double>::max();
1577 double leftOfDist = std::numeric_limits<double>::max();
1578 int prevLeftOf = 0;
1579 double prevLeftOfX = 0.0;
1580 double prevLeftOfY = 0.0;
1581 double testDist = 0;
1582 double segmentPtX, segmentPtY;
1583
1584 if ( leftOf )
1585 *leftOf = 0;
1586
1587 const int size = mX.size();
1588 if ( size == 0 || size == 1 )
1589 {
1590 vertexAfter = QgsVertexId( 0, 0, 0 );
1591 return -1;
1592 }
1593
1594 const double *xData = mX.constData();
1595 const double *yData = mY.constData();
1596 for ( int i = 1; i < size; ++i )
1597 {
1598 double prevX = xData[i - 1];
1599 double prevY = yData[i - 1];
1600 double currentX = xData[i];
1601 double currentY = yData[i];
1602 testDist = QgsGeometryUtilsBase::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon );
1603 if ( testDist < sqrDist )
1604 {
1605 sqrDist = testDist;
1606 segmentPt.setX( segmentPtX );
1607 segmentPt.setY( segmentPtY );
1608 vertexAfter.part = 0;
1609 vertexAfter.ring = 0;
1610 vertexAfter.vertex = i;
1611 }
1612 if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
1613 {
1614 int left = QgsGeometryUtilsBase::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
1615 // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
1616 // so don't set leftOf in this case, and hope that there's another segment that's the same distance
1617 // where we can perform the check
1618 if ( left != 0 )
1619 {
1620 if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 )
1621 {
1622 // we have two possible segments each with equal distance to point, but they disagree
1623 // on whether or not the point is to the left of them.
1624 // so we test the segments themselves and flip the result.
1625 // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
1626 *leftOf = -QgsGeometryUtilsBase::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY );
1627 }
1628 else
1629 {
1630 *leftOf = left;
1631 }
1632 prevLeftOf = *leftOf;
1633 leftOfDist = testDist;
1634 prevLeftOfX = prevX;
1635 prevLeftOfY = prevY;
1636 }
1637 else if ( testDist < leftOfDist )
1638 {
1639 *leftOf = left;
1640 leftOfDist = testDist;
1641 prevLeftOf = 0;
1642 }
1643 }
1644 }
1645 return sqrDist;
1646}
1647
1648/***************************************************************************
1649 * This class is considered CRITICAL and any change MUST be accompanied with
1650 * full unit tests.
1651 * See details in QEP #17
1652 ****************************************************************************/
1653
1654bool QgsLineString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1655{
1656 if ( node < 0 || node >= numPoints() )
1657 {
1658 return false;
1659 }
1660 point = pointN( node );
1662 return true;
1663}
1664
1666{
1667 if ( mX.isEmpty() )
1668 return QgsPoint();
1669
1670 int numPoints = mX.count();
1671 if ( numPoints == 1 )
1672 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1673
1674 double totalLineLength = 0.0;
1675 double prevX = mX.at( 0 );
1676 double prevY = mY.at( 0 );
1677 double sumX = 0.0;
1678 double sumY = 0.0;
1679
1680 for ( int i = 1; i < numPoints; ++i )
1681 {
1682 double currentX = mX.at( i );
1683 double currentY = mY.at( i );
1684 double segmentLength = std::sqrt( std::pow( currentX - prevX, 2.0 ) + std::pow( currentY - prevY, 2.0 ) );
1685 if ( qgsDoubleNear( segmentLength, 0.0 ) )
1686 continue;
1687
1688 totalLineLength += segmentLength;
1689 sumX += segmentLength * ( currentX + prevX );
1690 sumY += segmentLength * ( currentY + prevY );
1691 prevX = currentX;
1692 prevY = currentY;
1693 }
1694 sumX *= 0.5;
1695 sumY *= 0.5;
1696
1697 if ( qgsDoubleNear( totalLineLength, 0.0 ) )
1698 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1699 else
1700 return QgsPoint( sumX / totalLineLength, sumY / totalLineLength );
1701}
1702
1703/***************************************************************************
1704 * This class is considered CRITICAL and any change MUST be accompanied with
1705 * full unit tests.
1706 * See details in QEP #17
1707 ****************************************************************************/
1708
1709void QgsLineString::sumUpArea( double &sum ) const
1710{
1712 {
1713 sum += mSummedUpArea;
1714 return;
1715 }
1716
1717 mSummedUpArea = 0;
1718 const int maxIndex = mX.size();
1719 if ( maxIndex < 2 )
1720 {
1722 return;
1723 }
1724
1725 const double *x = mX.constData();
1726 const double *y = mY.constData();
1727 double prevX = *x++;
1728 double prevY = *y++;
1729 for ( int i = 1; i < maxIndex; ++i )
1730 {
1731 mSummedUpArea += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1732 prevX = *x++;
1733 prevY = *y++;
1734 }
1735 mSummedUpArea *= 0.5;
1736
1738 sum += mSummedUpArea;
1739}
1740
1741void QgsLineString::sumUpArea3D( double &sum ) const
1742{
1744 {
1745 sum += mSummedUpArea3D;
1746 return;
1747 }
1748
1749 // No Z component. Fallback to the 2D version
1750 if ( mZ.isEmpty() )
1751 {
1752 double area2D = 0;
1753 sumUpArea( area2D );
1754 mSummedUpArea3D = area2D;
1756 sum += mSummedUpArea3D;
1757 return;
1758 }
1759
1760 mSummedUpArea3D = 0;
1761
1762 // Look for a reference unit normal
1763 QgsPoint ptA;
1764 QgsPoint ptB;
1765 QgsPoint ptC;
1766 if ( !QgsGeometryUtils::checkWeaklyFor3DPlane( this, ptA, ptB, ptC ) )
1767 {
1769 return;
1770 }
1771
1772 QgsVector3D vAB = QgsVector3D( ptB.x() - ptA.x(), ptB.y() - ptA.y(), ptB.z() - ptA.z() );
1773 QgsVector3D vAC = QgsVector3D( ptC.x() - ptA.x(), ptC.y() - ptA.y(), ptC.z() - ptA.z() );
1774 QgsVector3D planeNormal = QgsVector3D::crossProduct( vAB, vAC );
1775
1776 // Ensure a Consistent orientation: prioritize Z+, then Y+, then X+
1777 if ( !qgsDoubleNear( planeNormal.z(), 0.0 ) )
1778 {
1779 if ( planeNormal.z() < 0 )
1780 {
1781 planeNormal = -planeNormal;
1782 }
1783 }
1784 else if ( !qgsDoubleNear( planeNormal.y(), 0.0 ) )
1785 {
1786 if ( planeNormal.y() < 0 )
1787 planeNormal = -planeNormal;
1788 }
1789 else
1790 {
1791 if ( planeNormal.x() < 0 )
1792 planeNormal = -planeNormal;
1793 }
1794 planeNormal.normalize();
1795
1796 const double *x = mX.constData();
1797 const double *y = mY.constData();
1798 const double *z = mZ.constData();
1799
1800 double prevX = *x++;
1801 double prevY = *y++;
1802 double prevZ = *z++;
1803
1804 double normalX = 0.0;
1805 double normalY = 0.0; // #spellok - Y component of normal vector
1806 double normalZ = 0.0;
1807
1808 for ( unsigned int i = 1; i < mX.size(); ++i )
1809 {
1810 normalX += prevY * ( *z - prevZ ) - prevZ * ( *y - prevY );
1811 normalY += prevZ * ( *x - prevX ) - prevX * ( *z - prevZ ); // #spellok
1812 normalZ += prevX * ( *y - prevY ) - prevY * ( *x - prevX );
1813
1814 prevX = *x++;
1815 prevY = *y++;
1816 prevZ = *z++;
1817 }
1818
1819 mSummedUpArea3D = 0.5 * ( normalX * planeNormal.x() + normalY * planeNormal.y() + normalZ * planeNormal.z() ); // #spellok
1820
1822 sum += mSummedUpArea3D;
1823}
1824
1825/***************************************************************************
1826 * This class is considered CRITICAL and any change MUST be accompanied with
1827 * full unit tests.
1828 * See details in QEP #17
1829 ****************************************************************************/
1830
1832{
1833 if ( numPoints() < 1 || isClosed() )
1834 {
1835 return;
1836 }
1837 addVertex( startPoint() );
1838}
1839
1841{
1842 if ( mX.count() < 2 )
1843 {
1844 //undefined
1845 return 0.0;
1846 }
1847
1848 if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
1849 {
1850 if ( isClosed() )
1851 {
1852 double previousX = mX.at( numPoints() - 2 );
1853 double previousY = mY.at( numPoints() - 2 );
1854 double currentX = mX.at( 0 );
1855 double currentY = mY.at( 0 );
1856 double afterX = mX.at( 1 );
1857 double afterY = mY.at( 1 );
1858 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1859 }
1860 else if ( vertex.vertex == 0 )
1861 {
1862 return QgsGeometryUtilsBase::lineAngle( mX.at( 0 ), mY.at( 0 ), mX.at( 1 ), mY.at( 1 ) );
1863 }
1864 else
1865 {
1866 int a = numPoints() - 2;
1867 int b = numPoints() - 1;
1868 return QgsGeometryUtilsBase::lineAngle( mX.at( a ), mY.at( a ), mX.at( b ), mY.at( b ) );
1869 }
1870 }
1871 else
1872 {
1873 double previousX = mX.at( vertex.vertex - 1 );
1874 double previousY = mY.at( vertex.vertex - 1 );
1875 double currentX = mX.at( vertex.vertex );
1876 double currentY = mY.at( vertex.vertex );
1877 double afterX = mX.at( vertex.vertex + 1 );
1878 double afterY = mY.at( vertex.vertex + 1 );
1879 return QgsGeometryUtilsBase::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1880 }
1881}
1882
1884{
1885 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 1 )
1886 return 0.0;
1887
1888 double dx = mX.at( startVertex.vertex + 1 ) - mX.at( startVertex.vertex );
1889 double dy = mY.at( startVertex.vertex + 1 ) - mY.at( startVertex.vertex );
1890 return std::sqrt( dx * dx + dy * dy );
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 ( type == mWkbType )
1902 return true;
1903
1904 clearCache();
1905 if ( type == Qgis::WkbType::LineString25D )
1906 {
1907 //special handling required for conversion to LineString25D
1908 dropMValue();
1909 addZValue( std::numeric_limits<double>::quiet_NaN() );
1911 return true;
1912 }
1913 else
1914 {
1915 return QgsCurve::convertTo( type );
1916 }
1917}
1918
1919std::unique_ptr< QgsLineString > QgsLineString::measuredLine( double start, double end ) const
1920{
1921 const int nbpoints = numPoints();
1922 std::unique_ptr< QgsLineString > cloned( clone() );
1923
1924 if ( !cloned->convertTo( QgsWkbTypes::addM( mWkbType ) ) )
1925 {
1926 return cloned;
1927 }
1928
1929 if ( isEmpty() || ( nbpoints < 2 ) )
1930 {
1931 return cloned;
1932 }
1933
1934 const double range = end - start;
1935 double lineLength = length();
1936 double lengthSoFar = 0.0;
1937
1938
1939 double *mOut = cloned->mM.data();
1940 *mOut++ = start;
1941 for ( int i = 1; i < nbpoints; ++i )
1942 {
1943 lengthSoFar += QgsGeometryUtilsBase::distance2D( mX[i - 1], mY[i - 1], mX[i], mY[i] );
1944 if ( lineLength > 0.0 )
1945 *mOut++ = start + range * lengthSoFar / lineLength;
1946 else if ( lineLength == 0.0 && nbpoints > 1 )
1947 *mOut++ = start + range * i / ( nbpoints - 1 );
1948 else
1949 *mOut++ = 0.0;
1950 }
1951
1952 return cloned;
1953}
1954
1955std::unique_ptr< QgsLineString > QgsLineString::interpolateM( bool use3DDistance ) const
1956{
1957 if ( !isMeasure() )
1958 return nullptr;
1959
1960 const int totalPoints = numPoints();
1961 if ( totalPoints < 2 )
1962 return std::unique_ptr< QgsLineString >( clone() );
1963
1964 const double *xData = mX.constData();
1965 const double *yData = mY.constData();
1966 const double *mData = mM.constData();
1967 const double *zData = is3D() ? mZ.constData() : nullptr;
1968 use3DDistance &= static_cast< bool >( zData );
1969
1970 QVector< double > xOut( totalPoints );
1971 QVector< double > yOut( totalPoints );
1972 QVector< double > mOut( totalPoints );
1973 QVector< double > zOut( static_cast< bool >( zData ) ? totalPoints : 0 );
1974
1975 double *xOutData = xOut.data();
1976 double *yOutData = yOut.data();
1977 double *mOutData = mOut.data();
1978 double *zOutData = static_cast< bool >( zData ) ? zOut.data() : nullptr;
1979
1980 int i = 0;
1981 double currentSegmentLength = 0;
1982 double lastValidM = std::numeric_limits< double >::quiet_NaN();
1983 double prevX = *xData;
1984 double prevY = *yData;
1985 double prevZ = zData ? *zData : 0;
1986 while ( i < totalPoints )
1987 {
1988 double thisX = *xData++;
1989 double thisY = *yData++;
1990 double thisZ = zData ? *zData++ : 0;
1991 double thisM = *mData++;
1992
1993 currentSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( prevX, prevY, prevZ, thisX, thisY, thisZ ) : QgsGeometryUtilsBase::distance2D( prevX, prevY, thisX, thisY );
1994
1995 if ( !std::isnan( thisM ) )
1996 {
1997 *xOutData++ = thisX;
1998 *yOutData++ = thisY;
1999 *mOutData++ = thisM;
2000 if ( zOutData )
2001 *zOutData++ = thisZ;
2002 lastValidM = thisM;
2003 }
2004 else if ( i == 0 )
2005 {
2006 // nan m value at start of line, read ahead to find first non-nan value and backfill
2007 int j = 0;
2008 double scanAheadM = thisM;
2009 while ( i + j + 1 < totalPoints && std::isnan( scanAheadM ) )
2010 {
2011 scanAheadM = mData[j];
2012 ++j;
2013 }
2014 if ( std::isnan( scanAheadM ) )
2015 {
2016 // no valid m values in line
2017 return nullptr;
2018 }
2019 *xOutData++ = thisX;
2020 *yOutData++ = thisY;
2021 *mOutData++ = scanAheadM;
2022 if ( zOutData )
2023 *zOutData++ = thisZ;
2024 for ( ; i < j; ++i )
2025 {
2026 thisX = *xData++;
2027 thisY = *yData++;
2028 *xOutData++ = thisX;
2029 *yOutData++ = thisY;
2030 *mOutData++ = scanAheadM;
2031 mData++;
2032 if ( zOutData )
2033 *zOutData++ = *zData++;
2034 }
2035 lastValidM = scanAheadM;
2036 }
2037 else
2038 {
2039 // nan m value in middle of line, read ahead till next non-nan value and interpolate
2040 int j = 0;
2041 double scanAheadX = thisX;
2042 double scanAheadY = thisY;
2043 double scanAheadZ = thisZ;
2044 double distanceToNextValidM = currentSegmentLength;
2045 std::vector< double > scanAheadSegmentLengths;
2046 scanAheadSegmentLengths.emplace_back( currentSegmentLength );
2047
2048 double nextValidM = std::numeric_limits< double >::quiet_NaN();
2049 while ( i + j < totalPoints - 1 )
2050 {
2051 double nextScanAheadX = xData[j];
2052 double nextScanAheadY = yData[j];
2053 double nextScanAheadZ = zData ? zData[j] : 0;
2054 double nextScanAheadM = mData[j];
2055 const double scanAheadSegmentLength = use3DDistance ? QgsGeometryUtilsBase::distance3D( scanAheadX, scanAheadY, scanAheadZ, nextScanAheadX, nextScanAheadY, nextScanAheadZ )
2056 : QgsGeometryUtilsBase::distance2D( scanAheadX, scanAheadY, nextScanAheadX, nextScanAheadY );
2057 scanAheadSegmentLengths.emplace_back( scanAheadSegmentLength );
2058 distanceToNextValidM += scanAheadSegmentLength;
2059
2060 if ( !std::isnan( nextScanAheadM ) )
2061 {
2062 nextValidM = nextScanAheadM;
2063 break;
2064 }
2065
2066 scanAheadX = nextScanAheadX;
2067 scanAheadY = nextScanAheadY;
2068 scanAheadZ = nextScanAheadZ;
2069 ++j;
2070 }
2071
2072 if ( std::isnan( nextValidM ) )
2073 {
2074 // no more valid m values, so just fill remainder of vertices with previous valid m value
2075 *xOutData++ = thisX;
2076 *yOutData++ = thisY;
2077 *mOutData++ = lastValidM;
2078 if ( zOutData )
2079 *zOutData++ = thisZ;
2080 ++i;
2081 for ( ; i < totalPoints; ++i )
2082 {
2083 *xOutData++ = *xData++;
2084 *yOutData++ = *yData++;
2085 *mOutData++ = lastValidM;
2086 if ( zOutData )
2087 *zOutData++ = *zData++;
2088 }
2089 break;
2090 }
2091 else
2092 {
2093 // interpolate along segments
2094 const double delta = ( nextValidM - lastValidM ) / distanceToNextValidM;
2095 *xOutData++ = thisX;
2096 *yOutData++ = thisY;
2097 *mOutData++ = lastValidM + delta * scanAheadSegmentLengths[0];
2098 double totalScanAheadLength = scanAheadSegmentLengths[0];
2099 if ( zOutData )
2100 *zOutData++ = thisZ;
2101 for ( int k = 1; k <= j; ++i, ++k )
2102 {
2103 thisX = *xData++;
2104 thisY = *yData++;
2105 *xOutData++ = thisX;
2106 *yOutData++ = thisY;
2107 totalScanAheadLength += scanAheadSegmentLengths[k];
2108 *mOutData++ = lastValidM + delta * totalScanAheadLength;
2109 mData++;
2110 if ( zOutData )
2111 *zOutData++ = *zData++;
2112 }
2113 lastValidM = nextValidM;
2114 }
2115 }
2116
2117 prevX = thisX;
2118 prevY = thisY;
2119 prevZ = thisZ;
2120 ++i;
2121 }
2122 return std::make_unique< QgsLineString >( xOut, yOut, zOut, mOut );
2123}
2124
2126{
2127 // Convert QgsVertexId to simple vertex numbers for linestrings (single ring, single part)
2128 if ( fromVertex.part != 0 || fromVertex.ring != 0 || toVertex.part != 0 || toVertex.ring != 0 )
2129 return -1.0;
2130
2131 const int fromVertexNumber = fromVertex.vertex;
2132 const int toVertexNumber = toVertex.vertex;
2133
2134 // Ensure fromVertex < toVertex for simplicity
2135 if ( fromVertexNumber > toVertexNumber )
2136 {
2137 return distanceBetweenVertices( QgsVertexId( 0, 0, toVertexNumber ), QgsVertexId( 0, 0, fromVertexNumber ) );
2138 }
2139
2140 const int nPoints = numPoints();
2141 if ( fromVertexNumber < 0 || fromVertexNumber >= nPoints || toVertexNumber < 0 || toVertexNumber >= nPoints )
2142 return -1.0;
2143
2144 if ( fromVertexNumber == toVertexNumber )
2145 return 0.0;
2146
2147 const bool is3DGeometry = is3D();
2148 const double *xData = mX.constData();
2149 const double *yData = mY.constData();
2150 const double *zData = is3DGeometry ? mZ.constData() : nullptr;
2151 double totalDistance = 0.0;
2152
2153 // For linestring, just accumulate Euclidean distances between consecutive points
2154 for ( int i = fromVertexNumber; i < toVertexNumber; ++i )
2155 {
2156 double dx = xData[i + 1] - xData[i];
2157 double dy = yData[i + 1] - yData[i];
2158 double dz = 0.0;
2159
2160 if ( is3DGeometry )
2161 {
2162 dz = zData[i + 1] - zData[i];
2163 }
2164
2165 totalDistance += std::sqrt( dx * dx + dy * dy + dz * dz );
2166 }
2167
2168 return totalDistance;
2169}
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2210
VertexType
Types of vertex.
Definition qgis.h:3260
@ Segment
The actual start or end point of a segment.
Definition qgis.h:3261
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5045
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 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.
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:7288
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7381
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