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"
21 #include "qgscoordinatetransform.h"
22 #include "qgsgeometryutils.h"
23 #include "qgsmaptopixel.h"
24 #include "qgswkbptr.h"
25 #include "qgslinesegment.h"
26 #include "qgsgeometrytransformer.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
38 /***************************************************************************
39 * This class is considered CRITICAL and any change MUST be accompanied with
40 * full unit tests.
41 * See details in QEP #17
42 ****************************************************************************/
43
QgsLineString()44 QgsLineString::QgsLineString()
45 {
46 mWkbType = QgsWkbTypes::LineString;
47 }
48
QgsLineString(const QVector<QgsPoint> & points)49 QgsLineString::QgsLineString( const QVector<QgsPoint> &points )
50 {
51 if ( points.isEmpty() )
52 {
53 mWkbType = QgsWkbTypes::LineString;
54 return;
55 }
56 QgsWkbTypes::Type ptType = points.at( 0 ).wkbType();
57 mWkbType = QgsWkbTypes::zmType( QgsWkbTypes::LineString, QgsWkbTypes::hasZ( ptType ), QgsWkbTypes::hasM( ptType ) );
58 mX.resize( points.count() );
59 mY.resize( points.count() );
60 double *x = mX.data();
61 double *y = mY.data();
62 double *z = nullptr;
63 double *m = nullptr;
64 if ( QgsWkbTypes::hasZ( mWkbType ) )
65 {
66 mZ.resize( points.count() );
67 z = mZ.data();
68 }
69 if ( QgsWkbTypes::hasM( mWkbType ) )
70 {
71 mM.resize( points.count() );
72 m = mM.data();
73 }
74
75 for ( const QgsPoint &pt : points )
76 {
77 *x++ = pt.x();
78 *y++ = pt.y();
79 if ( z )
80 *z++ = pt.z();
81 if ( m )
82 *m++ = pt.m();
83 }
84 }
85
QgsLineString(const QVector<double> & x,const QVector<double> & y,const QVector<double> & z,const QVector<double> & m,bool is25DType)86 QgsLineString::QgsLineString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m, bool is25DType )
87 {
88 mWkbType = QgsWkbTypes::LineString;
89 int pointCount = std::min( x.size(), y.size() );
90 if ( x.size() == pointCount )
91 {
92 mX = x;
93 }
94 else
95 {
96 mX = x.mid( 0, pointCount );
97 }
98 if ( y.size() == pointCount )
99 {
100 mY = y;
101 }
102 else
103 {
104 mY = y.mid( 0, pointCount );
105 }
106 if ( !z.isEmpty() && z.count() >= pointCount )
107 {
108 mWkbType = is25DType ? QgsWkbTypes::LineString25D : QgsWkbTypes::LineStringZ;
109 if ( z.size() == pointCount )
110 {
111 mZ = z;
112 }
113 else
114 {
115 mZ = z.mid( 0, pointCount );
116 }
117 }
118 if ( !m.isEmpty() && m.count() >= pointCount )
119 {
120 mWkbType = QgsWkbTypes::addM( mWkbType );
121 if ( m.size() == pointCount )
122 {
123 mM = m;
124 }
125 else
126 {
127 mM = m.mid( 0, pointCount );
128 }
129 }
130 }
131
QgsLineString(const QgsPoint & p1,const QgsPoint & p2)132 QgsLineString::QgsLineString( const QgsPoint &p1, const QgsPoint &p2 )
133 {
134 mWkbType = QgsWkbTypes::LineString;
135 mX.resize( 2 );
136 mX[ 0 ] = p1.x();
137 mX[ 1 ] = p2.x();
138 mY.resize( 2 );
139 mY[ 0 ] = p1.y();
140 mY[ 1 ] = p2.y();
141 if ( p1.is3D() )
142 {
143 mWkbType = QgsWkbTypes::addZ( mWkbType );
144 mZ.resize( 2 );
145 mZ[ 0 ] = p1.z();
146 mZ[ 1 ] = p2.z();
147 }
148 if ( p1.isMeasure() )
149 {
150 mWkbType = QgsWkbTypes::addM( mWkbType );
151 mM.resize( 2 );
152 mM[ 0 ] = p1.m();
153 mM[ 1 ] = p2.m();
154 }
155 }
156
QgsLineString(const QVector<QgsPointXY> & points)157 QgsLineString::QgsLineString( const QVector<QgsPointXY> &points )
158 {
159 mWkbType = QgsWkbTypes::LineString;
160 mX.reserve( points.size() );
161 mY.reserve( points.size() );
162 for ( const QgsPointXY &p : points )
163 {
164 mX << p.x();
165 mY << p.y();
166 }
167 }
168
QgsLineString(const QgsLineSegment2D & segment)169 QgsLineString::QgsLineString( const QgsLineSegment2D &segment )
170 {
171 mWkbType = QgsWkbTypes::LineString;
172 mX.resize( 2 );
173 mY.resize( 2 );
174 mX[0] = segment.startX();
175 mX[1] = segment.endX();
176 mY[0] = segment.startY();
177 mY[1] = segment.endY();
178 }
179
cubicInterpolate(double a,double b,double A,double B,double C,double D)180 static double cubicInterpolate( double a, double b,
181 double A, double B, double C, double D )
182 {
183 return A * b * b * b + 3 * B * b * b * a + 3 * C * b * a * a + D * a * a * a;
184 }
185
fromBezierCurve(const QgsPoint & start,const QgsPoint & controlPoint1,const QgsPoint & controlPoint2,const QgsPoint & end,int segments)186 QgsLineString *QgsLineString::fromBezierCurve( const QgsPoint &start, const QgsPoint &controlPoint1, const QgsPoint &controlPoint2, const QgsPoint &end, int segments )
187 {
188 if ( segments == 0 )
189 return new QgsLineString();
190
191 QVector<double> x;
192 x.resize( segments + 1 );
193 QVector<double> y;
194 y.resize( segments + 1 );
195 QVector<double> z;
196 double *zData = nullptr;
197 if ( start.is3D() && end.is3D() && controlPoint1.is3D() && controlPoint2.is3D() )
198 {
199 z.resize( segments + 1 );
200 zData = z.data();
201 }
202 QVector<double> m;
203 double *mData = nullptr;
204 if ( start.isMeasure() && end.isMeasure() && controlPoint1.isMeasure() && controlPoint2.isMeasure() )
205 {
206 m.resize( segments + 1 );
207 mData = m.data();
208 }
209
210 double *xData = x.data();
211 double *yData = y.data();
212 const double step = 1.0 / segments;
213 double a = 0;
214 double b = 1.0;
215 for ( int i = 0; i < segments; i++, a += step, b -= step )
216 {
217 if ( i == 0 )
218 {
219 *xData++ = start.x();
220 *yData++ = start.y();
221 if ( zData )
222 *zData++ = start.z();
223 if ( mData )
224 *mData++ = start.m();
225 }
226 else
227 {
228 *xData++ = cubicInterpolate( a, b, start.x(), controlPoint1.x(), controlPoint2.x(), end.x() );
229 *yData++ = cubicInterpolate( a, b, start.y(), controlPoint1.y(), controlPoint2.y(), end.y() );
230 if ( zData )
231 *zData++ = cubicInterpolate( a, b, start.z(), controlPoint1.z(), controlPoint2.z(), end.z() );
232 if ( mData )
233 *mData++ = cubicInterpolate( a, b, start.m(), controlPoint1.m(), controlPoint2.m(), end.m() );
234 }
235 }
236
237 *xData = end.x();
238 *yData = end.y();
239 if ( zData )
240 *zData = end.z();
241 if ( mData )
242 *mData = end.m();
243
244 return new QgsLineString( x, y, z, m );
245 }
246
fromQPolygonF(const QPolygonF & polygon)247 QgsLineString *QgsLineString::fromQPolygonF( const QPolygonF &polygon )
248 {
249 QVector< double > x;
250 QVector< double > y;
251 x.resize( polygon.count() );
252 y.resize( polygon.count() );
253 double *xData = x.data();
254 double *yData = y.data();
255
256 const QPointF *src = polygon.data();
257 for ( int i = 0 ; i < polygon.size(); ++ i )
258 {
259 *xData++ = src->x();
260 *yData++ = src->y();
261 src++;
262 }
263
264 return new QgsLineString( x, y );
265 }
266
equals(const QgsCurve & other) const267 bool QgsLineString::equals( const QgsCurve &other ) const
268 {
269 const QgsLineString *otherLine = qgsgeometry_cast< const QgsLineString * >( &other );
270 if ( !otherLine )
271 return false;
272
273 if ( mWkbType != otherLine->mWkbType )
274 return false;
275
276 if ( mX.count() != otherLine->mX.count() )
277 return false;
278
279 for ( int i = 0; i < mX.count(); ++i )
280 {
281 if ( !qgsDoubleNear( mX.at( i ), otherLine->mX.at( i ) )
282 || !qgsDoubleNear( mY.at( i ), otherLine->mY.at( i ) ) )
283 return false;
284
285 if ( is3D() && !qgsDoubleNear( mZ.at( i ), otherLine->mZ.at( i ) ) )
286 return false;
287
288 if ( isMeasure() && !qgsDoubleNear( mM.at( i ), otherLine->mM.at( i ) ) )
289 return false;
290 }
291
292 return true;
293 }
294
clone() const295 QgsLineString *QgsLineString::clone() const
296 {
297 return new QgsLineString( *this );
298 }
299
clear()300 void QgsLineString::clear()
301 {
302 mX.clear();
303 mY.clear();
304 mZ.clear();
305 mM.clear();
306 mWkbType = QgsWkbTypes::LineString;
307 clearCache();
308 }
309
isEmpty() const310 bool QgsLineString::isEmpty() const
311 {
312 return mX.isEmpty();
313 }
314
indexOf(const QgsPoint & point) const315 int QgsLineString::indexOf( const QgsPoint &point ) const
316 {
317 const int size = mX.size();
318 if ( size == 0 )
319 return -1;
320
321 const double *x = mX.constData();
322 const double *y = mY.constData();
323 const bool useZ = is3D();
324 const bool useM = isMeasure();
325 const double *z = useZ ? mZ.constData() : nullptr;
326 const double *m = useM ? mM.constData() : nullptr;
327
328 for ( int i = 0; i < size; ++i )
329 {
330 if ( qgsDoubleNear( *x, point.x() )
331 && qgsDoubleNear( *y, point.y() )
332 && ( !useZ || qgsDoubleNear( *z, point.z() ) )
333 && ( !useM || qgsDoubleNear( *m, point.m() ) ) )
334 return i;
335
336 x++;
337 y++;
338 if ( useZ )
339 z++;
340 if ( useM )
341 m++;
342 }
343 return -1;
344 }
345
isValid(QString & error,Qgis::GeometryValidityFlags flags) const346 bool QgsLineString::isValid( QString &error, Qgis::GeometryValidityFlags flags ) const
347 {
348 if ( !isEmpty() && ( numPoints() < 2 ) )
349 {
350 error = QObject::tr( "LineString has less than 2 points and is not empty." );
351 return false;
352 }
353 return QgsCurve::isValid( error, flags );
354 }
355
snappedToGrid(double hSpacing,double vSpacing,double dSpacing,double mSpacing) const356 QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
357 {
358 // prepare result
359 std::unique_ptr<QgsLineString> result { createEmptyWithSameType() };
360
361 bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM,
362 result->mX, result->mY, result->mZ, result->mM );
363 if ( res )
364 return result.release();
365 else
366 return nullptr;
367 }
368
removeDuplicateNodes(double epsilon,bool useZValues)369 bool QgsLineString::removeDuplicateNodes( double epsilon, bool useZValues )
370 {
371 if ( mX.count() <= 2 )
372 return false; // don't create degenerate lines
373 bool result = false;
374 double prevX = mX.at( 0 );
375 double prevY = mY.at( 0 );
376 bool hasZ = is3D();
377 bool useZ = hasZ && useZValues;
378 double prevZ = useZ ? mZ.at( 0 ) : 0;
379 int i = 1;
380 int remaining = mX.count();
381 while ( i < remaining )
382 {
383 double currentX = mX.at( i );
384 double currentY = mY.at( i );
385 double currentZ = useZ ? mZ.at( i ) : 0;
386 if ( qgsDoubleNear( currentX, prevX, epsilon ) &&
387 qgsDoubleNear( currentY, prevY, epsilon ) &&
388 ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
389 {
390 result = true;
391 // remove point
392 mX.removeAt( i );
393 mY.removeAt( i );
394 if ( hasZ )
395 mZ.removeAt( i );
396 remaining--;
397 }
398 else
399 {
400 prevX = currentX;
401 prevY = currentY;
402 prevZ = currentZ;
403 i++;
404 }
405 }
406 return result;
407 }
408
isClosed2D() const409 bool QgsLineString::isClosed2D() const
410 {
411 if ( mX.empty() )
412 return false;
413
414 return qgsDoubleNear( mX.first(), mX.last() ) &&
415 qgsDoubleNear( mY.first(), mY.last() );
416 }
417
isClosed() const418 bool QgsLineString::isClosed() const
419 {
420 bool closed = isClosed2D();
421
422 if ( is3D() && closed )
423 closed &= qgsDoubleNear( mZ.first(), mZ.last() ) || ( std::isnan( mZ.first() ) && std::isnan( mZ.last() ) );
424 return closed;
425 }
426
boundingBoxIntersects(const QgsRectangle & rectangle) const427 bool QgsLineString::boundingBoxIntersects( const QgsRectangle &rectangle ) const
428 {
429 if ( mX.empty() )
430 return false;
431
432 if ( !mBoundingBox.isNull() )
433 {
434 return mBoundingBox.intersects( rectangle );
435 }
436 const int nb = mX.size();
437
438 // We are a little fancy here!
439 if ( nb > 40 )
440 {
441 // if a large number of vertices, take some sample vertices at 1/5th increments through the linestring
442 // and test whether any are inside the rectangle. Maybe we can shortcut a lot of iterations by doing this!
443 // (why 1/5th? it's picked so that it works nicely for polygon rings which are almost rectangles, so the vertex extremities
444 // will fall on approximately these vertex indices)
445 if ( rectangle.contains( mX.at( 0 ), mY.at( 0 ) ) ||
446 rectangle.contains( mX.at( static_cast< int >( nb * 0.2 ) ), mY.at( static_cast< int >( nb * 0.2 ) ) ) ||
447 rectangle.contains( mX.at( static_cast< int >( nb * 0.4 ) ), mY.at( static_cast< int >( nb * 0.4 ) ) ) ||
448 rectangle.contains( mX.at( static_cast< int >( nb * 0.6 ) ), mY.at( static_cast< int >( nb * 0.6 ) ) ) ||
449 rectangle.contains( mX.at( static_cast< int >( nb * 0.8 ) ), mY.at( static_cast< int >( nb * 0.8 ) ) ) ||
450 rectangle.contains( mX.at( nb - 1 ), mY.at( nb - 1 ) ) )
451 return true;
452 }
453
454 // Be even MORE fancy! Given that bounding box calculation is non-free, cached, and we don't
455 // already have it, we start performing the bounding box calculation while we are testing whether
456 // each point falls inside the rectangle. That way if we end up testing the majority of the points
457 // anyway, we can update the cached bounding box with the results we've calculated along the way
458 // and save future calls to calculate the bounding box!
459 double xmin = std::numeric_limits<double>::max();
460 double ymin = std::numeric_limits<double>::max();
461 double xmax = -std::numeric_limits<double>::max();
462 double ymax = -std::numeric_limits<double>::max();
463
464 const double *x = mX.constData();
465 const double *y = mY.constData();
466 bool foundPointInRectangle = false;
467 for ( int i = 0; i < nb; ++i )
468 {
469 const double px = *x++;
470 xmin = std::min( xmin, px );
471 xmax = std::max( xmax, px );
472 const double py = *y++;
473 ymin = std::min( ymin, py );
474 ymax = std::max( ymax, py );
475
476 if ( !foundPointInRectangle && rectangle.contains( px, py ) )
477 {
478 foundPointInRectangle = true;
479
480 // now... we have a choice to make. If we've already looped through the majority of the points
481 // in this linestring then let's just continue to iterate through the remainder so that we can
482 // complete the overall bounding box calculation we've already mostly done. If however we're only
483 // just at the start of iterating the vertices, we shortcut out early and leave the bounding box
484 // uncalculated
485 if ( i < nb * 0.5 )
486 return true;
487 }
488 }
489
490 // at this stage we now know the overall bounding box of the linestring, so let's cache
491 // it so we don't ever have to calculate this again. We've done all the hard work anyway!
492 mBoundingBox = QgsRectangle( xmin, ymin, xmax, ymax, false );
493
494 if ( foundPointInRectangle )
495 return true;
496
497 // NOTE: if none of the points in the line actually fell inside the rectangle, it doesn't
498 // exclude that the OVERALL bounding box of the linestring itself intersects the rectangle!!
499 // So we fall back to the parent class method which compares the overall bounding box against
500 // the rectangle... and this will be very cheap now that we've already calculated and cached
501 // the linestring's bounding box!
502 return QgsCurve::boundingBoxIntersects( rectangle );
503 }
504
collectDuplicateNodes(double epsilon,bool useZValues) const505 QVector< QgsVertexId > QgsLineString::collectDuplicateNodes( double epsilon, bool useZValues ) const
506 {
507 QVector< QgsVertexId > res;
508 if ( mX.count() <= 1 )
509 return res;
510
511 const double *x = mX.constData();
512 const double *y = mY.constData();
513 bool hasZ = is3D();
514 bool useZ = hasZ && useZValues;
515 const double *z = useZ ? mZ.constData() : nullptr;
516
517 double prevX = *x++;
518 double prevY = *y++;
519 double prevZ = z ? *z++ : 0;
520
521 QgsVertexId id;
522 for ( int i = 1; i < mX.count(); ++i )
523 {
524 double currentX = *x++;
525 double currentY = *y++;
526 double currentZ = useZ ? *z++ : 0;
527 if ( qgsDoubleNear( currentX, prevX, epsilon ) &&
528 qgsDoubleNear( currentY, prevY, epsilon ) &&
529 ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
530 {
531 id.vertex = i;
532 res << id;
533 }
534 else
535 {
536 prevX = currentX;
537 prevY = currentY;
538 prevZ = currentZ;
539 }
540 }
541 return res;
542 }
543
asQPolygonF() const544 QPolygonF QgsLineString::asQPolygonF() const
545 {
546 const int nb = mX.size();
547 QPolygonF points( nb );
548
549 const double *x = mX.constData();
550 const double *y = mY.constData();
551 QPointF *dest = points.data();
552 for ( int i = 0; i < nb; ++i )
553 {
554 *dest++ = QPointF( *x++, *y++ );
555 }
556 return points;
557 }
558
fromWkb(QgsConstWkbPtr & wkbPtr)559 bool QgsLineString::fromWkb( QgsConstWkbPtr &wkbPtr )
560 {
561 if ( !wkbPtr )
562 {
563 return false;
564 }
565
566 QgsWkbTypes::Type type = wkbPtr.readHeader();
567 if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::LineString )
568 {
569 return false;
570 }
571 mWkbType = type;
572 importVerticesFromWkb( wkbPtr );
573 return true;
574 }
575
calculateBoundingBox() const576 QgsRectangle QgsLineString::calculateBoundingBox() const
577 {
578 if ( mX.empty() )
579 return QgsRectangle();
580
581 auto result = std::minmax_element( mX.begin(), mX.end() );
582 const double xmin = *result.first;
583 const double xmax = *result.second;
584 result = std::minmax_element( mY.begin(), mY.end() );
585 const double ymin = *result.first;
586 const double ymax = *result.second;
587 return QgsRectangle( xmin, ymin, xmax, ymax, false );
588 }
589
scroll(int index)590 void QgsLineString::scroll( int index )
591 {
592 const int size = mX.size();
593 if ( index < 1 || index >= size - 1 )
594 return;
595
596 const bool useZ = is3D();
597 const bool useM = isMeasure();
598
599 QVector<double> newX( size );
600 QVector<double> newY( size );
601 QVector<double> newZ( useZ ? size : 0 );
602 QVector<double> newM( useM ? size : 0 );
603 auto it = std::copy( mX.constBegin() + index, mX.constEnd() - 1, newX.begin() );
604 it = std::copy( mX.constBegin(), mX.constBegin() + index, it );
605 *it = *newX.constBegin();
606 mX = std::move( newX );
607
608 it = std::copy( mY.constBegin() + index, mY.constEnd() - 1, newY.begin() );
609 it = std::copy( mY.constBegin(), mY.constBegin() + index, it );
610 *it = *newY.constBegin();
611 mY = std::move( newY );
612 if ( useZ )
613 {
614 it = std::copy( mZ.constBegin() + index, mZ.constEnd() - 1, newZ.begin() );
615 it = std::copy( mZ.constBegin(), mZ.constBegin() + index, it );
616 *it = *newZ.constBegin();
617 mZ = std::move( newZ );
618 }
619 if ( useM )
620 {
621 it = std::copy( mM.constBegin() + index, mM.constEnd() - 1, newM.begin() );
622 it = std::copy( mM.constBegin(), mM.constBegin() + index, it );
623 *it = *newM.constBegin();
624 mM = std::move( newM );
625 }
626 }
627
628 /***************************************************************************
629 * This class is considered CRITICAL and any change MUST be accompanied with
630 * full unit tests.
631 * See details in QEP #17
632 ****************************************************************************/
fromWkt(const QString & wkt)633 bool QgsLineString::fromWkt( const QString &wkt )
634 {
635 clear();
636
637 QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
638
639 if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::LineString )
640 return false;
641 mWkbType = parts.first;
642
643 QString secondWithoutParentheses = parts.second;
644 secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
645 parts.second = parts.second.remove( '(' ).remove( ')' );
646 if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
647 secondWithoutParentheses.isEmpty() )
648 return true;
649
650 QgsPointSequence points = QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() );
651 // There is a non number in the coordinates sequence
652 // LineString ( A b, 1 2)
653 if ( points.isEmpty() )
654 return false;
655
656 setPoints( points );
657 return true;
658 }
659
wkbSize(QgsAbstractGeometry::WkbFlags) const660 int QgsLineString::wkbSize( QgsAbstractGeometry::WkbFlags ) const
661 {
662 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
663 binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
664 return binarySize;
665 }
666
asWkb(WkbFlags flags) const667 QByteArray QgsLineString::asWkb( WkbFlags flags ) const
668 {
669 QByteArray wkbArray;
670 wkbArray.resize( QgsLineString::wkbSize( flags ) );
671 QgsWkbPtr wkb( wkbArray );
672 wkb << static_cast<char>( QgsApplication::endian() );
673 wkb << static_cast<quint32>( wkbType() );
674 QgsPointSequence pts;
675 points( pts );
676 QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
677 return wkbArray;
678 }
679
680 /***************************************************************************
681 * This class is considered CRITICAL and any change MUST be accompanied with
682 * full unit tests.
683 * See details in QEP #17
684 ****************************************************************************/
685
asWkt(int precision) const686 QString QgsLineString::asWkt( int precision ) const
687 {
688 QString wkt = wktTypeStr() + ' ';
689
690 if ( isEmpty() )
691 wkt += QLatin1String( "EMPTY" );
692 else
693 {
694 QgsPointSequence pts;
695 points( pts );
696 wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
697 }
698 return wkt;
699 }
700
asGml2(QDomDocument & doc,int precision,const QString & ns,const AxisOrder axisOrder) const701 QDomElement QgsLineString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
702 {
703 QgsPointSequence pts;
704 points( pts );
705
706 QDomElement elemLineString = doc.createElementNS( ns, QStringLiteral( "LineString" ) );
707
708 if ( isEmpty() )
709 return elemLineString;
710
711 elemLineString.appendChild( QgsGeometryUtils::pointsToGML2( pts, doc, precision, ns, axisOrder ) );
712
713 return elemLineString;
714 }
715
asGml3(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const716 QDomElement QgsLineString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
717 {
718 QgsPointSequence pts;
719 points( pts );
720
721 QDomElement elemLineString = doc.createElementNS( ns, QStringLiteral( "LineString" ) );
722
723 if ( isEmpty() )
724 return elemLineString;
725
726 elemLineString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
727 return elemLineString;
728 }
729
asJsonObject(int precision) const730 json QgsLineString::asJsonObject( int precision ) const
731 {
732 QgsPointSequence pts;
733 points( pts );
734 return
735 {
736 { "type", "LineString" },
737 { "coordinates", QgsGeometryUtils::pointsToJson( pts, precision ) }
738 };
739 }
740
asKml(int precision) const741 QString QgsLineString::asKml( int precision ) const
742 {
743 QString kml;
744 if ( isRing() )
745 {
746 kml.append( QLatin1String( "<LinearRing>" ) );
747 }
748 else
749 {
750 kml.append( QLatin1String( "<LineString>" ) );
751 }
752 bool z = is3D();
753 kml.append( QLatin1String( "<altitudeMode>" ) );
754 if ( z )
755 {
756 kml.append( QLatin1String( "absolute" ) );
757 }
758 else
759 {
760 kml.append( QLatin1String( "clampToGround" ) );
761 }
762 kml.append( QLatin1String( "</altitudeMode>" ) );
763 kml.append( QLatin1String( "<coordinates>" ) );
764
765 int nPoints = mX.size();
766 for ( int i = 0; i < nPoints; ++i )
767 {
768 if ( i > 0 )
769 {
770 kml.append( QLatin1String( " " ) );
771 }
772 kml.append( qgsDoubleToString( mX[i], precision ) );
773 kml.append( QLatin1String( "," ) );
774 kml.append( qgsDoubleToString( mY[i], precision ) );
775 if ( z )
776 {
777 kml.append( QLatin1String( "," ) );
778 kml.append( qgsDoubleToString( mZ[i], precision ) );
779 }
780 else
781 {
782 kml.append( QLatin1String( ",0" ) );
783 }
784 }
785 kml.append( QLatin1String( "</coordinates>" ) );
786 if ( isRing() )
787 {
788 kml.append( QLatin1String( "</LinearRing>" ) );
789 }
790 else
791 {
792 kml.append( QLatin1String( "</LineString>" ) );
793 }
794 return kml;
795 }
796
797 /***************************************************************************
798 * This class is considered CRITICAL and any change MUST be accompanied with
799 * full unit tests.
800 * See details in QEP #17
801 ****************************************************************************/
802
length() const803 double QgsLineString::length() const
804 {
805 double total = 0;
806 const int size = mX.size();
807 if ( size < 2 )
808 return 0;
809
810 const double *x = mX.constData();
811 const double *y = mY.constData();
812 double dx, dy;
813
814 double prevX = *x++;
815 double prevY = *y++;
816
817 for ( int i = 1; i < size; ++i )
818 {
819 dx = *x - prevX;
820 dy = *y - prevY;
821 total += std::sqrt( dx * dx + dy * dy );
822
823 prevX = *x++;
824 prevY = *y++;
825 }
826 return total;
827 }
828
splitCurveAtVertex(int index) const829 std::tuple<std::unique_ptr<QgsCurve>, std::unique_ptr<QgsCurve> > QgsLineString::splitCurveAtVertex( int index ) const
830 {
831 const bool useZ = is3D();
832 const bool useM = isMeasure();
833
834 const int size = mX.size();
835 if ( size == 0 )
836 return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >() );
837
838 index = std::clamp( index, 0, size - 1 );
839
840 const int part1Size = index + 1;
841 QVector< double > x1( part1Size );
842 QVector< double > y1( part1Size );
843 QVector< double > z1( useZ ? part1Size : 0 );
844 QVector< double > m1( useM ? part1Size : 0 );
845
846 const double *sourceX = mX.constData();
847 const double *sourceY = mY.constData();
848 const double *sourceZ = useZ ? mZ.constData() : nullptr;
849 const double *sourceM = useM ? mM.constData() : nullptr;
850
851 double *destX = x1.data();
852 double *destY = y1.data();
853 double *destZ = useZ ? z1.data() : nullptr;
854 double *destM = useM ? m1.data() : nullptr;
855
856 std::copy( sourceX, sourceX + part1Size, destX );
857 std::copy( sourceY, sourceY + part1Size, destY );
858 if ( useZ )
859 std::copy( sourceZ, sourceZ + part1Size, destZ );
860 if ( useM )
861 std::copy( sourceM, sourceM + part1Size, destM );
862
863 const int part2Size = size - index;
864 if ( part2Size < 2 )
865 return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >() );
866
867 QVector< double > x2( part2Size );
868 QVector< double > y2( part2Size );
869 QVector< double > z2( useZ ? part2Size : 0 );
870 QVector< double > m2( useM ? part2Size : 0 );
871 destX = x2.data();
872 destY = y2.data();
873 destZ = useZ ? z2.data() : nullptr;
874 destM = useM ? m2.data() : nullptr;
875 std::copy( sourceX + index, sourceX + size, destX );
876 std::copy( sourceY + index, sourceY + size, destY );
877 if ( useZ )
878 std::copy( sourceZ + index, sourceZ + size, destZ );
879 if ( useM )
880 std::copy( sourceM + index, sourceM + size, destM );
881
882 if ( part1Size < 2 )
883 return std::make_tuple( std::make_unique< QgsLineString >(), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
884 else
885 return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) );
886 }
887
length3D() const888 double QgsLineString::length3D() const
889 {
890 if ( is3D() )
891 {
892 double total = 0;
893 const int size = mX.size();
894 if ( size < 2 )
895 return 0;
896
897 const double *x = mX.constData();
898 const double *y = mY.constData();
899 const double *z = mZ.constData();
900 double dx, dy, dz;
901
902 double prevX = *x++;
903 double prevY = *y++;
904 double prevZ = *z++;
905
906 for ( int i = 1; i < size; ++i )
907 {
908 dx = *x - prevX;
909 dy = *y - prevY;
910 dz = *z - prevZ;
911 total += std::sqrt( dx * dx + dy * dy + dz * dz );
912
913 prevX = *x++;
914 prevY = *y++;
915 prevZ = *z++;
916 }
917 return total;
918 }
919 else
920 {
921 return length();
922 }
923 }
924
startPoint() const925 QgsPoint QgsLineString::startPoint() const
926 {
927 if ( numPoints() < 1 )
928 {
929 return QgsPoint();
930 }
931 return pointN( 0 );
932 }
933
endPoint() const934 QgsPoint QgsLineString::endPoint() const
935 {
936 if ( numPoints() < 1 )
937 {
938 return QgsPoint();
939 }
940 return pointN( numPoints() - 1 );
941 }
942
943 /***************************************************************************
944 * This class is considered CRITICAL and any change MUST be accompanied with
945 * full unit tests.
946 * See details in QEP #17
947 ****************************************************************************/
948
curveToLine(double tolerance,SegmentationToleranceType toleranceType) const949 QgsLineString *QgsLineString::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
950 {
951 Q_UNUSED( tolerance )
952 Q_UNUSED( toleranceType )
953 return clone();
954 }
955
numPoints() const956 int QgsLineString::numPoints() const
957 {
958 return mX.size();
959 }
960
nCoordinates() const961 int QgsLineString::nCoordinates() const
962 {
963 return mX.size();
964 }
965
pointN(int i) const966 QgsPoint QgsLineString::pointN( int i ) const
967 {
968 if ( i < 0 || i >= mX.size() )
969 {
970 return QgsPoint();
971 }
972
973 double x = mX.at( i );
974 double y = mY.at( i );
975 double z = std::numeric_limits<double>::quiet_NaN();
976 double m = std::numeric_limits<double>::quiet_NaN();
977
978 bool hasZ = is3D();
979 if ( hasZ )
980 {
981 z = mZ.at( i );
982 }
983 bool hasM = isMeasure();
984 if ( hasM )
985 {
986 m = mM.at( i );
987 }
988
989 QgsWkbTypes::Type t = QgsWkbTypes::Point;
990 if ( mWkbType == QgsWkbTypes::LineString25D )
991 {
992 t = QgsWkbTypes::Point25D;
993 }
994 else if ( hasZ && hasM )
995 {
996 t = QgsWkbTypes::PointZM;
997 }
998 else if ( hasZ )
999 {
1000 t = QgsWkbTypes::PointZ;
1001 }
1002 else if ( hasM )
1003 {
1004 t = QgsWkbTypes::PointM;
1005 }
1006 return QgsPoint( t, x, y, z, m );
1007 }
1008
1009 /***************************************************************************
1010 * This class is considered CRITICAL and any change MUST be accompanied with
1011 * full unit tests.
1012 * See details in QEP #17
1013 ****************************************************************************/
1014
xAt(int index) const1015 double QgsLineString::xAt( int index ) const
1016 {
1017 if ( index >= 0 && index < mX.size() )
1018 return mX.at( index );
1019 else
1020 return 0.0;
1021 }
1022
yAt(int index) const1023 double QgsLineString::yAt( int index ) const
1024 {
1025 if ( index >= 0 && index < mY.size() )
1026 return mY.at( index );
1027 else
1028 return 0.0;
1029 }
1030
setXAt(int index,double x)1031 void QgsLineString::setXAt( int index, double x )
1032 {
1033 if ( index >= 0 && index < mX.size() )
1034 mX[ index ] = x;
1035 clearCache();
1036 }
1037
setYAt(int index,double y)1038 void QgsLineString::setYAt( int index, double y )
1039 {
1040 if ( index >= 0 && index < mY.size() )
1041 mY[ index ] = y;
1042 clearCache();
1043 }
1044
1045 /***************************************************************************
1046 * This class is considered CRITICAL and any change MUST be accompanied with
1047 * full unit tests.
1048 * See details in QEP #17
1049 ****************************************************************************/
1050
points(QgsPointSequence & pts) const1051 void QgsLineString::points( QgsPointSequence &pts ) const
1052 {
1053 pts.clear();
1054 int nPoints = numPoints();
1055 pts.reserve( nPoints );
1056 for ( int i = 0; i < nPoints; ++i )
1057 {
1058 pts.push_back( pointN( i ) );
1059 }
1060 }
1061
setPoints(const QgsPointSequence & points)1062 void QgsLineString::setPoints( const QgsPointSequence &points )
1063 {
1064 clearCache(); //set bounding box invalid
1065
1066 if ( points.isEmpty() )
1067 {
1068 clear();
1069 return;
1070 }
1071
1072 //get wkb type from first point
1073 const QgsPoint &firstPt = points.at( 0 );
1074 bool hasZ = firstPt.is3D();
1075 bool hasM = firstPt.isMeasure();
1076
1077 setZMTypeFromSubGeometry( &firstPt, QgsWkbTypes::LineString );
1078
1079 mX.resize( points.size() );
1080 mY.resize( points.size() );
1081 if ( hasZ )
1082 {
1083 mZ.resize( points.size() );
1084 }
1085 else
1086 {
1087 mZ.clear();
1088 }
1089 if ( hasM )
1090 {
1091 mM.resize( points.size() );
1092 }
1093 else
1094 {
1095 mM.clear();
1096 }
1097
1098 for ( int i = 0; i < points.size(); ++i )
1099 {
1100 mX[i] = points.at( i ).x();
1101 mY[i] = points.at( i ).y();
1102 if ( hasZ )
1103 {
1104 double z = points.at( i ).z();
1105 mZ[i] = std::isnan( z ) ? 0 : z;
1106 }
1107 if ( hasM )
1108 {
1109 double m = points.at( i ).m();
1110 mM[i] = std::isnan( m ) ? 0 : m;
1111 }
1112 }
1113 }
1114
1115 /***************************************************************************
1116 * This class is considered CRITICAL and any change MUST be accompanied with
1117 * full unit tests.
1118 * See details in QEP #17
1119 ****************************************************************************/
1120
append(const QgsLineString * line)1121 void QgsLineString::append( const QgsLineString *line )
1122 {
1123 if ( !line )
1124 {
1125 return;
1126 }
1127
1128 if ( numPoints() < 1 )
1129 {
1130 setZMTypeFromSubGeometry( line, QgsWkbTypes::LineString );
1131 }
1132
1133 // do not store duplicate points
1134 if ( numPoints() > 0 &&
1135 line->numPoints() > 0 &&
1136 endPoint() == line->startPoint() )
1137 {
1138 mX.pop_back();
1139 mY.pop_back();
1140
1141 if ( is3D() )
1142 {
1143 mZ.pop_back();
1144 }
1145 if ( isMeasure() )
1146 {
1147 mM.pop_back();
1148 }
1149 }
1150
1151 mX += line->mX;
1152 mY += line->mY;
1153
1154 if ( is3D() )
1155 {
1156 if ( line->is3D() )
1157 {
1158 mZ += line->mZ;
1159 }
1160 else
1161 {
1162 // if append line does not have z coordinates, fill with NaN to match number of points in final line
1163 mZ.insert( mZ.count(), mX.size() - mZ.size(), std::numeric_limits<double>::quiet_NaN() );
1164 }
1165 }
1166
1167 if ( isMeasure() )
1168 {
1169 if ( line->isMeasure() )
1170 {
1171 mM += line->mM;
1172 }
1173 else
1174 {
1175 // if append line does not have m values, fill with NaN to match number of points in final line
1176 mM.insert( mM.count(), mX.size() - mM.size(), std::numeric_limits<double>::quiet_NaN() );
1177 }
1178 }
1179
1180 clearCache(); //set bounding box invalid
1181 }
1182
reversed() const1183 QgsLineString *QgsLineString::reversed() const
1184 {
1185 QgsLineString *copy = clone();
1186 std::reverse( copy->mX.begin(), copy->mX.end() );
1187 std::reverse( copy->mY.begin(), copy->mY.end() );
1188 if ( copy->is3D() )
1189 {
1190 std::reverse( copy->mZ.begin(), copy->mZ.end() );
1191 }
1192 if ( copy->isMeasure() )
1193 {
1194 std::reverse( copy->mM.begin(), copy->mM.end() );
1195 }
1196 return copy;
1197 }
1198
visitPointsByRegularDistance(const double distance,const std::function<bool (double,double,double,double,double,double,double,double,double,double,double,double)> & visitPoint) const1199 void QgsLineString::visitPointsByRegularDistance( const double distance, const std::function<bool ( double, double, double, double, double, double, double, double, double, double, double, double )> &visitPoint ) const
1200 {
1201 if ( distance < 0 )
1202 return;
1203
1204 double distanceTraversed = 0;
1205 const int totalPoints = numPoints();
1206 if ( totalPoints == 0 )
1207 return;
1208
1209 const double *x = mX.constData();
1210 const double *y = mY.constData();
1211 const double *z = is3D() ? mZ.constData() : nullptr;
1212 const double *m = isMeasure() ? mM.constData() : nullptr;
1213
1214 double prevX = *x++;
1215 double prevY = *y++;
1216 double prevZ = z ? *z++ : 0.0;
1217 double prevM = m ? *m++ : 0.0;
1218
1219 if ( qgsDoubleNear( distance, 0.0 ) )
1220 {
1221 visitPoint( prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM, prevX, prevY, prevZ, prevM );
1222 return;
1223 }
1224
1225 double pZ = std::numeric_limits<double>::quiet_NaN();
1226 double pM = std::numeric_limits<double>::quiet_NaN();
1227 double nextPointDistance = distance;
1228 for ( int i = 1; i < totalPoints; ++i )
1229 {
1230 double thisX = *x++;
1231 double thisY = *y++;
1232 double thisZ = z ? *z++ : 0.0;
1233 double thisM = m ? *m++ : 0.0;
1234
1235 const double segmentLength = std::sqrt( ( thisX - prevX ) * ( thisX - prevX ) + ( thisY - prevY ) * ( thisY - prevY ) );
1236 while ( nextPointDistance < distanceTraversed + segmentLength || qgsDoubleNear( nextPointDistance, distanceTraversed + segmentLength ) )
1237 {
1238 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1239 const double distanceToPoint = std::min( nextPointDistance - distanceTraversed, segmentLength );
1240 double pX, pY;
1241 QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY,
1242 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &pZ : nullptr,
1243 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &pM : nullptr );
1244
1245 if ( !visitPoint( pX, pY, pZ, pM, prevX, prevY, prevZ, prevM, thisX, thisY, thisZ, thisM ) )
1246 return;
1247
1248 nextPointDistance += distance;
1249 }
1250
1251 distanceTraversed += segmentLength;
1252 prevX = thisX;
1253 prevY = thisY;
1254 prevZ = thisZ;
1255 prevM = thisM;
1256 }
1257 }
1258
interpolatePoint(const double distance) const1259 QgsPoint *QgsLineString::interpolatePoint( const double distance ) const
1260 {
1261 if ( distance < 0 )
1262 return nullptr;
1263
1264 QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1265 if ( is3D() )
1266 pointType = QgsWkbTypes::PointZ;
1267 if ( isMeasure() )
1268 pointType = QgsWkbTypes::addM( pointType );
1269
1270 std::unique_ptr< QgsPoint > res;
1271 visitPointsByRegularDistance( distance, [ & ]( double x, double y, double z, double m, double, double, double, double, double, double, double, double )->bool
1272 {
1273 res = std::make_unique< QgsPoint >( pointType, x, y, z, m );
1274 return false;
1275 } );
1276 return res.release();
1277 }
1278
curveSubstring(double startDistance,double endDistance) const1279 QgsLineString *QgsLineString::curveSubstring( double startDistance, double endDistance ) const
1280 {
1281 if ( startDistance < 0 && endDistance < 0 )
1282 return createEmptyWithSameType();
1283
1284 endDistance = std::max( startDistance, endDistance );
1285
1286 const int totalPoints = numPoints();
1287 if ( totalPoints == 0 )
1288 return clone();
1289
1290 QVector< QgsPoint > substringPoints;
1291 substringPoints.reserve( totalPoints );
1292
1293 QgsWkbTypes::Type pointType = QgsWkbTypes::Point;
1294 if ( is3D() )
1295 pointType = QgsWkbTypes::PointZ;
1296 if ( isMeasure() )
1297 pointType = QgsWkbTypes::addM( pointType );
1298
1299 const double *x = mX.constData();
1300 const double *y = mY.constData();
1301 const double *z = is3D() ? mZ.constData() : nullptr;
1302 const double *m = isMeasure() ? mM.constData() : nullptr;
1303
1304 double distanceTraversed = 0;
1305 double prevX = *x++;
1306 double prevY = *y++;
1307 double prevZ = z ? *z++ : 0.0;
1308 double prevM = m ? *m++ : 0.0;
1309 bool foundStart = false;
1310
1311 if ( startDistance < 0 )
1312 startDistance = 0;
1313
1314 for ( int i = 1; i < totalPoints; ++i )
1315 {
1316 double thisX = *x++;
1317 double thisY = *y++;
1318 double thisZ = z ? *z++ : 0.0;
1319 double thisM = m ? *m++ : 0.0;
1320
1321 const double segmentLength = std::sqrt( ( thisX - prevX ) * ( thisX - prevX ) + ( thisY - prevY ) * ( thisY - prevY ) );
1322
1323 if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1324 {
1325 // start point falls on this segment
1326 const double distanceToStart = startDistance - distanceTraversed;
1327 double startX, startY;
1328 double startZ = 0;
1329 double startM = 0;
1330 QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToStart, startX, startY,
1331 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &startZ : nullptr,
1332 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &startM : nullptr );
1333 substringPoints << QgsPoint( pointType, startX, startY, startZ, startM );
1334 foundStart = true;
1335 }
1336 if ( foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1337 {
1338 // end point falls on this segment
1339 const double distanceToEnd = endDistance - distanceTraversed;
1340 double endX, endY;
1341 double endZ = 0;
1342 double endM = 0;
1343 QgsGeometryUtils::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToEnd, endX, endY,
1344 z ? &prevZ : nullptr, z ? &thisZ : nullptr, z ? &endZ : nullptr,
1345 m ? &prevM : nullptr, m ? &thisM : nullptr, m ? &endM : nullptr );
1346 substringPoints << QgsPoint( pointType, endX, endY, endZ, endM );
1347 }
1348 else if ( foundStart )
1349 {
1350 substringPoints << QgsPoint( pointType, thisX, thisY, thisZ, thisM );
1351 }
1352
1353 prevX = thisX;
1354 prevY = thisY;
1355 prevZ = thisZ;
1356 prevM = thisM;
1357 distanceTraversed += segmentLength;
1358 if ( distanceTraversed >= endDistance )
1359 break;
1360 }
1361
1362 // start point is the last node
1363 if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1364 {
1365 substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1366 << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1367 }
1368
1369 return new QgsLineString( substringPoints );
1370 }
1371
1372 /***************************************************************************
1373 * This class is considered CRITICAL and any change MUST be accompanied with
1374 * full unit tests.
1375 * See details in QEP #17
1376 ****************************************************************************/
1377
draw(QPainter & p) const1378 void QgsLineString::draw( QPainter &p ) const
1379 {
1380 p.drawPolyline( asQPolygonF() );
1381 }
1382
addToPainterPath(QPainterPath & path) const1383 void QgsLineString::addToPainterPath( QPainterPath &path ) const
1384 {
1385 int nPoints = numPoints();
1386 if ( nPoints < 1 )
1387 {
1388 return;
1389 }
1390
1391 if ( path.isEmpty() || path.currentPosition() != QPointF( mX.at( 0 ), mY.at( 0 ) ) )
1392 {
1393 path.moveTo( mX.at( 0 ), mY.at( 0 ) );
1394 }
1395
1396 for ( int i = 1; i < nPoints; ++i )
1397 {
1398 path.lineTo( mX.at( i ), mY.at( i ) );
1399 }
1400 }
1401
drawAsPolygon(QPainter & p) const1402 void QgsLineString::drawAsPolygon( QPainter &p ) const
1403 {
1404 p.drawPolygon( asQPolygonF() );
1405 }
1406
toCurveType() const1407 QgsCompoundCurve *QgsLineString::toCurveType() const
1408 {
1409 QgsCompoundCurve *compoundCurve = new QgsCompoundCurve();
1410 compoundCurve->addCurve( clone() );
1411 return compoundCurve;
1412 }
1413
extend(double startDistance,double endDistance)1414 void QgsLineString::extend( double startDistance, double endDistance )
1415 {
1416 if ( mX.size() < 2 || mY.size() < 2 )
1417 return;
1418
1419 // start of line
1420 if ( startDistance > 0 )
1421 {
1422 double currentLen = std::sqrt( std::pow( mX.at( 0 ) - mX.at( 1 ), 2 ) +
1423 std::pow( mY.at( 0 ) - mY.at( 1 ), 2 ) );
1424 double newLen = currentLen + startDistance;
1425 mX[ 0 ] = mX.at( 1 ) + ( mX.at( 0 ) - mX.at( 1 ) ) / currentLen * newLen;
1426 mY[ 0 ] = mY.at( 1 ) + ( mY.at( 0 ) - mY.at( 1 ) ) / currentLen * newLen;
1427 }
1428 // end of line
1429 if ( endDistance > 0 )
1430 {
1431 int last = mX.size() - 1;
1432 double currentLen = std::sqrt( std::pow( mX.at( last ) - mX.at( last - 1 ), 2 ) +
1433 std::pow( mY.at( last ) - mY.at( last - 1 ), 2 ) );
1434 double newLen = currentLen + endDistance;
1435 mX[ last ] = mX.at( last - 1 ) + ( mX.at( last ) - mX.at( last - 1 ) ) / currentLen * newLen;
1436 mY[ last ] = mY.at( last - 1 ) + ( mY.at( last ) - mY.at( last - 1 ) ) / currentLen * newLen;
1437 }
1438 }
1439
createEmptyWithSameType() const1440 QgsLineString *QgsLineString::createEmptyWithSameType() const
1441 {
1442 auto result = std::make_unique< QgsLineString >();
1443 result->mWkbType = mWkbType;
1444 return result.release();
1445 }
1446
compareToSameClass(const QgsAbstractGeometry * other) const1447 int QgsLineString::compareToSameClass( const QgsAbstractGeometry *other ) const
1448 {
1449 const QgsLineString *otherLine = qgsgeometry_cast<const QgsLineString *>( other );
1450 if ( !otherLine )
1451 return -1;
1452
1453 const int size = mX.size();
1454 const int otherSize = otherLine->mX.size();
1455 if ( size > otherSize )
1456 {
1457 return 1;
1458 }
1459 else if ( size < otherSize )
1460 {
1461 return -1;
1462 }
1463
1464 if ( is3D() && !otherLine->is3D() )
1465 return 1;
1466 else if ( !is3D() && otherLine->is3D() )
1467 return -1;
1468 const bool considerZ = is3D();
1469
1470 if ( isMeasure() && !otherLine->isMeasure() )
1471 return 1;
1472 else if ( !isMeasure() && otherLine->isMeasure() )
1473 return -1;
1474 const bool considerM = isMeasure();
1475
1476 for ( int i = 0; i < size; i++ )
1477 {
1478 const double x = mX[i];
1479 const double otherX = otherLine->mX[i];
1480 if ( x < otherX )
1481 {
1482 return -1;
1483 }
1484 else if ( x > otherX )
1485 {
1486 return 1;
1487 }
1488
1489 const double y = mY[i];
1490 const double otherY = otherLine->mY[i];
1491 if ( y < otherY )
1492 {
1493 return -1;
1494 }
1495 else if ( y > otherY )
1496 {
1497 return 1;
1498 }
1499
1500 if ( considerZ )
1501 {
1502 const double z = mZ[i];
1503 const double otherZ = otherLine->mZ[i];
1504
1505 if ( z < otherZ )
1506 {
1507 return -1;
1508 }
1509 else if ( z > otherZ )
1510 {
1511 return 1;
1512 }
1513 }
1514
1515 if ( considerM )
1516 {
1517 const double m = mM[i];
1518 const double otherM = otherLine->mM[i];
1519
1520 if ( m < otherM )
1521 {
1522 return -1;
1523 }
1524 else if ( m > otherM )
1525 {
1526 return 1;
1527 }
1528 }
1529 }
1530 return 0;
1531 }
1532
geometryType() const1533 QString QgsLineString::geometryType() const
1534 {
1535 return QStringLiteral( "LineString" );
1536 }
1537
dimension() const1538 int QgsLineString::dimension() const
1539 {
1540 return 1;
1541 }
1542
1543 /***************************************************************************
1544 * This class is considered CRITICAL and any change MUST be accompanied with
1545 * full unit tests.
1546 * See details in QEP #17
1547 ****************************************************************************/
1548
transform(const QgsCoordinateTransform & ct,Qgis::TransformDirection d,bool transformZ)1549 void QgsLineString::transform( const QgsCoordinateTransform &ct, Qgis::TransformDirection d, bool transformZ )
1550 {
1551 double *zArray = nullptr;
1552 bool hasZ = is3D();
1553 int nPoints = numPoints();
1554
1555 // it's possible that transformCoords will throw an exception - so we need to use
1556 // a smart pointer for the dummy z values in order to ensure that they always get cleaned up
1557 std::unique_ptr< double[] > dummyZ;
1558 if ( !hasZ || !transformZ )
1559 {
1560 dummyZ.reset( new double[nPoints]() );
1561 zArray = dummyZ.get();
1562 }
1563 else
1564 {
1565 zArray = mZ.data();
1566 }
1567 ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
1568 clearCache();
1569 }
1570
transform(const QTransform & t,double zTranslate,double zScale,double mTranslate,double mScale)1571 void QgsLineString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
1572 {
1573 int nPoints = numPoints();
1574 bool hasZ = is3D();
1575 bool hasM = isMeasure();
1576 double *x = mX.data();
1577 double *y = mY.data();
1578 double *z = hasZ ? mZ.data() : nullptr;
1579 double *m = hasM ? mM.data() : nullptr;
1580 for ( int i = 0; i < nPoints; ++i )
1581 {
1582 double xOut, yOut;
1583 t.map( *x, *y, &xOut, &yOut );
1584 *x++ = xOut;
1585 *y++ = yOut;
1586 if ( hasZ )
1587 {
1588 *z = *z * zScale + zTranslate;
1589 z++;
1590 }
1591 if ( hasM )
1592 {
1593 *m = *m * mScale + mTranslate;
1594 m++;
1595 }
1596 }
1597 clearCache();
1598 }
1599
1600 /***************************************************************************
1601 * This class is considered CRITICAL and any change MUST be accompanied with
1602 * full unit tests.
1603 * See details in QEP #17
1604 ****************************************************************************/
1605
insertVertex(QgsVertexId position,const QgsPoint & vertex)1606 bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
1607 {
1608 if ( position.vertex < 0 || position.vertex > mX.size() )
1609 {
1610 return false;
1611 }
1612
1613 if ( mWkbType == QgsWkbTypes::Unknown || mX.isEmpty() )
1614 {
1615 setZMTypeFromSubGeometry( &vertex, QgsWkbTypes::LineString );
1616 }
1617
1618 mX.insert( position.vertex, vertex.x() );
1619 mY.insert( position.vertex, vertex.y() );
1620 if ( is3D() )
1621 {
1622 mZ.insert( position.vertex, vertex.z() );
1623 }
1624 if ( isMeasure() )
1625 {
1626 mM.insert( position.vertex, vertex.m() );
1627 }
1628 clearCache(); //set bounding box invalid
1629 return true;
1630 }
1631
moveVertex(QgsVertexId position,const QgsPoint & newPos)1632 bool QgsLineString::moveVertex( QgsVertexId position, const QgsPoint &newPos )
1633 {
1634 if ( position.vertex < 0 || position.vertex >= mX.size() )
1635 {
1636 return false;
1637 }
1638 mX[position.vertex] = newPos.x();
1639 mY[position.vertex] = newPos.y();
1640 if ( is3D() && newPos.is3D() )
1641 {
1642 mZ[position.vertex] = newPos.z();
1643 }
1644 if ( isMeasure() && newPos.isMeasure() )
1645 {
1646 mM[position.vertex] = newPos.m();
1647 }
1648 clearCache(); //set bounding box invalid
1649 return true;
1650 }
1651
deleteVertex(QgsVertexId position)1652 bool QgsLineString::deleteVertex( QgsVertexId position )
1653 {
1654 if ( position.vertex >= mX.size() || position.vertex < 0 )
1655 {
1656 return false;
1657 }
1658
1659 mX.remove( position.vertex );
1660 mY.remove( position.vertex );
1661 if ( is3D() )
1662 {
1663 mZ.remove( position.vertex );
1664 }
1665 if ( isMeasure() )
1666 {
1667 mM.remove( position.vertex );
1668 }
1669
1670 if ( numPoints() == 1 )
1671 {
1672 clear();
1673 }
1674
1675 clearCache(); //set bounding box invalid
1676 return true;
1677 }
1678
1679 /***************************************************************************
1680 * This class is considered CRITICAL and any change MUST be accompanied with
1681 * full unit tests.
1682 * See details in QEP #17
1683 ****************************************************************************/
1684
addVertex(const QgsPoint & pt)1685 void QgsLineString::addVertex( const QgsPoint &pt )
1686 {
1687 if ( mWkbType == QgsWkbTypes::Unknown || mX.isEmpty() )
1688 {
1689 setZMTypeFromSubGeometry( &pt, QgsWkbTypes::LineString );
1690 }
1691
1692 mX.append( pt.x() );
1693 mY.append( pt.y() );
1694 if ( is3D() )
1695 {
1696 mZ.append( pt.z() );
1697 }
1698 if ( isMeasure() )
1699 {
1700 mM.append( pt.m() );
1701 }
1702 clearCache(); //set bounding box invalid
1703 }
1704
closestSegment(const QgsPoint & pt,QgsPoint & segmentPt,QgsVertexId & vertexAfter,int * leftOf,double epsilon) const1705 double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
1706 {
1707 double sqrDist = std::numeric_limits<double>::max();
1708 double leftOfDist = std::numeric_limits<double>::max();
1709 int prevLeftOf = 0;
1710 double prevLeftOfX = 0.0;
1711 double prevLeftOfY = 0.0;
1712 double testDist = 0;
1713 double segmentPtX, segmentPtY;
1714
1715 if ( leftOf )
1716 *leftOf = 0;
1717
1718 int size = mX.size();
1719 if ( size == 0 || size == 1 )
1720 {
1721 vertexAfter = QgsVertexId( 0, 0, 0 );
1722 return -1;
1723 }
1724 for ( int i = 1; i < size; ++i )
1725 {
1726 double prevX = mX.at( i - 1 );
1727 double prevY = mY.at( i - 1 );
1728 double currentX = mX.at( i );
1729 double currentY = mY.at( i );
1730 testDist = QgsGeometryUtils::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon );
1731 if ( testDist < sqrDist )
1732 {
1733 sqrDist = testDist;
1734 segmentPt.setX( segmentPtX );
1735 segmentPt.setY( segmentPtY );
1736 vertexAfter.part = 0;
1737 vertexAfter.ring = 0;
1738 vertexAfter.vertex = i;
1739 }
1740 if ( leftOf && qgsDoubleNear( testDist, sqrDist ) )
1741 {
1742 int left = QgsGeometryUtils::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY );
1743 // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment)
1744 // so don't set leftOf in this case, and hope that there's another segment that's the same distance
1745 // where we can perform the check
1746 if ( left != 0 )
1747 {
1748 if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 )
1749 {
1750 // we have two possible segments each with equal distance to point, but they disagree
1751 // on whether or not the point is to the left of them.
1752 // so we test the segments themselves and flip the result.
1753 // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline
1754 *leftOf = -QgsGeometryUtils::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY );
1755 }
1756 else
1757 {
1758 *leftOf = left;
1759 }
1760 prevLeftOf = *leftOf;
1761 leftOfDist = testDist;
1762 prevLeftOfX = prevX;
1763 prevLeftOfY = prevY;
1764 }
1765 else if ( testDist < leftOfDist )
1766 {
1767 *leftOf = left;
1768 leftOfDist = testDist;
1769 prevLeftOf = 0;
1770 }
1771 }
1772 }
1773 return sqrDist;
1774 }
1775
1776 /***************************************************************************
1777 * This class is considered CRITICAL and any change MUST be accompanied with
1778 * full unit tests.
1779 * See details in QEP #17
1780 ****************************************************************************/
1781
pointAt(int node,QgsPoint & point,Qgis::VertexType & type) const1782 bool QgsLineString::pointAt( int node, QgsPoint &point, Qgis::VertexType &type ) const
1783 {
1784 if ( node < 0 || node >= numPoints() )
1785 {
1786 return false;
1787 }
1788 point = pointN( node );
1789 type = Qgis::VertexType::Segment;
1790 return true;
1791 }
1792
centroid() const1793 QgsPoint QgsLineString::centroid() const
1794 {
1795 if ( mX.isEmpty() )
1796 return QgsPoint();
1797
1798 int numPoints = mX.count();
1799 if ( numPoints == 1 )
1800 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1801
1802 double totalLineLength = 0.0;
1803 double prevX = mX.at( 0 );
1804 double prevY = mY.at( 0 );
1805 double sumX = 0.0;
1806 double sumY = 0.0;
1807
1808 for ( int i = 1; i < numPoints ; ++i )
1809 {
1810 double currentX = mX.at( i );
1811 double currentY = mY.at( i );
1812 double segmentLength = std::sqrt( std::pow( currentX - prevX, 2.0 ) +
1813 std::pow( currentY - prevY, 2.0 ) );
1814 if ( qgsDoubleNear( segmentLength, 0.0 ) )
1815 continue;
1816
1817 totalLineLength += segmentLength;
1818 sumX += segmentLength * 0.5 * ( currentX + prevX );
1819 sumY += segmentLength * 0.5 * ( currentY + prevY );
1820 prevX = currentX;
1821 prevY = currentY;
1822 }
1823
1824 if ( qgsDoubleNear( totalLineLength, 0.0 ) )
1825 return QgsPoint( mX.at( 0 ), mY.at( 0 ) );
1826 else
1827 return QgsPoint( sumX / totalLineLength, sumY / totalLineLength );
1828
1829 }
1830
1831 /***************************************************************************
1832 * This class is considered CRITICAL and any change MUST be accompanied with
1833 * full unit tests.
1834 * See details in QEP #17
1835 ****************************************************************************/
1836
sumUpArea(double & sum) const1837 void QgsLineString::sumUpArea( double &sum ) const
1838 {
1839 int maxIndex = numPoints() - 1;
1840
1841 for ( int i = 0; i < maxIndex; ++i )
1842 {
1843 sum += 0.5 * ( mX.at( i ) * mY.at( i + 1 ) - mY.at( i ) * mX.at( i + 1 ) );
1844 }
1845 }
1846
importVerticesFromWkb(const QgsConstWkbPtr & wkb)1847 void QgsLineString::importVerticesFromWkb( const QgsConstWkbPtr &wkb )
1848 {
1849 bool hasZ = is3D();
1850 bool hasM = isMeasure();
1851 int nVertices = 0;
1852 wkb >> nVertices;
1853 mX.resize( nVertices );
1854 mY.resize( nVertices );
1855 hasZ ? mZ.resize( nVertices ) : mZ.clear();
1856 hasM ? mM.resize( nVertices ) : mM.clear();
1857 double *x = mX.data();
1858 double *y = mY.data();
1859 double *m = hasM ? mM.data() : nullptr;
1860 double *z = hasZ ? mZ.data() : nullptr;
1861 for ( int i = 0; i < nVertices; ++i )
1862 {
1863 wkb >> *x++;
1864 wkb >> *y++;
1865 if ( hasZ )
1866 {
1867 wkb >> *z++;
1868 }
1869 if ( hasM )
1870 {
1871 wkb >> *m++;
1872 }
1873 }
1874 clearCache(); //set bounding box invalid
1875 }
1876
1877 /***************************************************************************
1878 * This class is considered CRITICAL and any change MUST be accompanied with
1879 * full unit tests.
1880 * See details in QEP #17
1881 ****************************************************************************/
1882
close()1883 void QgsLineString::close()
1884 {
1885 if ( numPoints() < 1 || isClosed() )
1886 {
1887 return;
1888 }
1889 addVertex( startPoint() );
1890 }
1891
vertexAngle(QgsVertexId vertex) const1892 double QgsLineString::vertexAngle( QgsVertexId vertex ) const
1893 {
1894 if ( mX.count() < 2 )
1895 {
1896 //undefined
1897 return 0.0;
1898 }
1899
1900 if ( vertex.vertex == 0 || vertex.vertex >= ( numPoints() - 1 ) )
1901 {
1902 if ( isClosed() )
1903 {
1904 double previousX = mX.at( numPoints() - 2 );
1905 double previousY = mY.at( numPoints() - 2 );
1906 double currentX = mX.at( 0 );
1907 double currentY = mY.at( 0 );
1908 double afterX = mX.at( 1 );
1909 double afterY = mY.at( 1 );
1910 return QgsGeometryUtils::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1911 }
1912 else if ( vertex.vertex == 0 )
1913 {
1914 return QgsGeometryUtils::lineAngle( mX.at( 0 ), mY.at( 0 ), mX.at( 1 ), mY.at( 1 ) );
1915 }
1916 else
1917 {
1918 int a = numPoints() - 2;
1919 int b = numPoints() - 1;
1920 return QgsGeometryUtils::lineAngle( mX.at( a ), mY.at( a ), mX.at( b ), mY.at( b ) );
1921 }
1922 }
1923 else
1924 {
1925 double previousX = mX.at( vertex.vertex - 1 );
1926 double previousY = mY.at( vertex.vertex - 1 );
1927 double currentX = mX.at( vertex.vertex );
1928 double currentY = mY.at( vertex.vertex );
1929 double afterX = mX.at( vertex.vertex + 1 );
1930 double afterY = mY.at( vertex.vertex + 1 );
1931 return QgsGeometryUtils::averageAngle( previousX, previousY, currentX, currentY, afterX, afterY );
1932 }
1933 }
1934
segmentLength(QgsVertexId startVertex) const1935 double QgsLineString::segmentLength( QgsVertexId startVertex ) const
1936 {
1937 if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 1 )
1938 return 0.0;
1939
1940 double dx = mX.at( startVertex.vertex + 1 ) - mX.at( startVertex.vertex );
1941 double dy = mY.at( startVertex.vertex + 1 ) - mY.at( startVertex.vertex );
1942 return std::sqrt( dx * dx + dy * dy );
1943 }
1944
1945 /***************************************************************************
1946 * This class is considered CRITICAL and any change MUST be accompanied with
1947 * full unit tests.
1948 * See details in QEP #17
1949 ****************************************************************************/
1950
addZValue(double zValue)1951 bool QgsLineString::addZValue( double zValue )
1952 {
1953 if ( QgsWkbTypes::hasZ( mWkbType ) )
1954 return false;
1955
1956 clearCache();
1957 if ( mWkbType == QgsWkbTypes::Unknown )
1958 {
1959 mWkbType = QgsWkbTypes::LineStringZ;
1960 return true;
1961 }
1962
1963 mWkbType = QgsWkbTypes::addZ( mWkbType );
1964
1965 mZ.clear();
1966 int nPoints = numPoints();
1967 mZ.reserve( nPoints );
1968 for ( int i = 0; i < nPoints; ++i )
1969 {
1970 mZ << zValue;
1971 }
1972 return true;
1973 }
1974
addMValue(double mValue)1975 bool QgsLineString::addMValue( double mValue )
1976 {
1977 if ( QgsWkbTypes::hasM( mWkbType ) )
1978 return false;
1979
1980 clearCache();
1981 if ( mWkbType == QgsWkbTypes::Unknown )
1982 {
1983 mWkbType = QgsWkbTypes::LineStringM;
1984 return true;
1985 }
1986
1987 if ( mWkbType == QgsWkbTypes::LineString25D )
1988 {
1989 mWkbType = QgsWkbTypes::LineStringZM;
1990 }
1991 else
1992 {
1993 mWkbType = QgsWkbTypes::addM( mWkbType );
1994 }
1995
1996 mM.clear();
1997 int nPoints = numPoints();
1998 mM.reserve( nPoints );
1999 for ( int i = 0; i < nPoints; ++i )
2000 {
2001 mM << mValue;
2002 }
2003 return true;
2004 }
2005
dropZValue()2006 bool QgsLineString::dropZValue()
2007 {
2008 if ( !is3D() )
2009 return false;
2010
2011 clearCache();
2012 mWkbType = QgsWkbTypes::dropZ( mWkbType );
2013 mZ.clear();
2014 return true;
2015 }
2016
dropMValue()2017 bool QgsLineString::dropMValue()
2018 {
2019 if ( !isMeasure() )
2020 return false;
2021
2022 clearCache();
2023 mWkbType = QgsWkbTypes::dropM( mWkbType );
2024 mM.clear();
2025 return true;
2026 }
2027
swapXy()2028 void QgsLineString::swapXy()
2029 {
2030 std::swap( mX, mY );
2031 clearCache();
2032 }
2033
convertTo(QgsWkbTypes::Type type)2034 bool QgsLineString::convertTo( QgsWkbTypes::Type type )
2035 {
2036 if ( type == mWkbType )
2037 return true;
2038
2039 clearCache();
2040 if ( type == QgsWkbTypes::LineString25D )
2041 {
2042 //special handling required for conversion to LineString25D
2043 dropMValue();
2044 addZValue( std::numeric_limits<double>::quiet_NaN() );
2045 mWkbType = QgsWkbTypes::LineString25D;
2046 return true;
2047 }
2048 else
2049 {
2050 return QgsCurve::convertTo( type );
2051 }
2052 }
2053
transform(QgsAbstractGeometryTransformer * transformer,QgsFeedback * feedback)2054 bool QgsLineString::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
2055 {
2056 if ( !transformer )
2057 return false;
2058
2059 bool hasZ = is3D();
2060 bool hasM = isMeasure();
2061 int size = mX.size();
2062
2063 double *srcX = mX.data();
2064 double *srcY = mY.data();
2065 double *srcM = hasM ? mM.data() : nullptr;
2066 double *srcZ = hasZ ? mZ.data() : nullptr;
2067
2068 bool res = true;
2069 for ( int i = 0; i < size; ++i )
2070 {
2071 double x = *srcX;
2072 double y = *srcY;
2073 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
2074 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
2075 if ( !transformer->transformPoint( x, y, z, m ) )
2076 {
2077 res = false;
2078 break;
2079 }
2080
2081 *srcX++ = x;
2082 *srcY++ = y;
2083 if ( hasM )
2084 *srcM++ = m;
2085 if ( hasZ )
2086 *srcZ++ = z;
2087
2088 if ( feedback && feedback->isCanceled() )
2089 {
2090 res = false;
2091 break;
2092 }
2093 }
2094 clearCache();
2095 return res;
2096 }
2097
filterVertices(const std::function<bool (const QgsPoint &)> & filter)2098 void QgsLineString::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
2099 {
2100 bool hasZ = is3D();
2101 bool hasM = isMeasure();
2102 int size = mX.size();
2103
2104 double *srcX = mX.data();
2105 double *srcY = mY.data();
2106 double *srcM = hasM ? mM.data() : nullptr;
2107 double *srcZ = hasZ ? mZ.data() : nullptr;
2108
2109 double *destX = srcX;
2110 double *destY = srcY;
2111 double *destM = srcM;
2112 double *destZ = srcZ;
2113
2114 int filteredPoints = 0;
2115 for ( int i = 0; i < size; ++i )
2116 {
2117 double x = *srcX++;
2118 double y = *srcY++;
2119 double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
2120 double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
2121
2122 if ( filter( QgsPoint( x, y, z, m ) ) )
2123 {
2124 filteredPoints++;
2125 *destX++ = x;
2126 *destY++ = y;
2127 if ( hasM )
2128 *destM++ = m;
2129 if ( hasZ )
2130 *destZ++ = z;
2131 }
2132 }
2133
2134 mX.resize( filteredPoints );
2135 mY.resize( filteredPoints );
2136 if ( hasZ )
2137 mZ.resize( filteredPoints );
2138 if ( hasM )
2139 mM.resize( filteredPoints );
2140
2141 clearCache();
2142 }
2143
transformVertices(const std::function<QgsPoint (const QgsPoint &)> & transform)2144 void QgsLineString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
2145 {
2146 bool hasZ = is3D();
2147 bool hasM = isMeasure();
2148 int size = mX.size();
2149
2150 double *srcX = mX.data();
2151 double *srcY = mY.data();
2152 double *srcM = hasM ? mM.data() : nullptr;
2153 double *srcZ = hasZ ? mZ.data() : nullptr;
2154
2155 for ( int i = 0; i < size; ++i )
2156 {
2157 double x = *srcX;
2158 double y = *srcY;
2159 double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
2160 double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
2161 QgsPoint res = transform( QgsPoint( x, y, z, m ) );
2162 *srcX++ = res.x();
2163 *srcY++ = res.y();
2164 if ( hasM )
2165 *srcM++ = res.m();
2166 if ( hasZ )
2167 *srcZ++ = res.z();
2168 }
2169 clearCache();
2170 }
2171