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