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