LCOV - code coverage report
Current view: top level - core/geometry - qgslinestring.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 1122 1230 91.2 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           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                 :       2314 : QgsLineString::QgsLineString()
      45                 :       4628 : {
      46                 :       2314 :   mWkbType = QgsWkbTypes::LineString;
      47                 :       2314 : }
      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                 :       1377 : QgsLineString::QgsLineString( const QVector<double> &x, const QVector<double> &y, const QVector<double> &z, const QVector<double> &m, bool is25DType )
      87                 :       2754 : {
      88                 :       1377 :   mWkbType = QgsWkbTypes::LineString;
      89                 :       1377 :   int pointCount = std::min( x.size(), y.size() );
      90                 :       1377 :   if ( x.size() == pointCount )
      91                 :            :   {
      92                 :       1376 :     mX = x;
      93                 :       1376 :   }
      94                 :            :   else
      95                 :            :   {
      96                 :          1 :     mX = x.mid( 0, pointCount );
      97                 :            :   }
      98                 :       1377 :   if ( y.size() == pointCount )
      99                 :            :   {
     100                 :       1376 :     mY = y;
     101                 :       1376 :   }
     102                 :            :   else
     103                 :            :   {
     104                 :          1 :     mY = y.mid( 0, pointCount );
     105                 :            :   }
     106                 :       1377 :   if ( !z.isEmpty() && z.count() >= pointCount )
     107                 :            :   {
     108                 :         37 :     mWkbType = is25DType ? QgsWkbTypes::LineString25D : QgsWkbTypes::LineStringZ;
     109                 :         37 :     if ( z.size() == pointCount )
     110                 :            :     {
     111                 :         36 :       mZ = z;
     112                 :         36 :     }
     113                 :            :     else
     114                 :            :     {
     115                 :          1 :       mZ = z.mid( 0, pointCount );
     116                 :            :     }
     117                 :         37 :   }
     118                 :       1377 :   if ( !m.isEmpty() && m.count() >= pointCount )
     119                 :            :   {
     120                 :          5 :     mWkbType = QgsWkbTypes::addM( mWkbType );
     121                 :          5 :     if ( m.size() == pointCount )
     122                 :            :     {
     123                 :          4 :       mM = m;
     124                 :          4 :     }
     125                 :            :     else
     126                 :            :     {
     127                 :          1 :       mM = m.mid( 0, pointCount );
     128                 :            :     }
     129                 :          5 :   }
     130                 :       1377 : }
     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                 :        821 : QgsLineString *QgsLineString::clone() const
     296                 :            : {
     297                 :        821 :   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                 :       2328 : bool QgsLineString::isEmpty() const
     311                 :            : {
     312                 :       2328 :   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                 :       1865 : bool QgsLineString::isClosed() const
     379                 :            : {
     380                 :       1865 :   if ( mX.empty() )
     381                 :          7 :     return false;
     382                 :            : 
     383                 :       3597 :   bool closed = qgsDoubleNear( mX.first(), mX.last() ) &&
     384                 :       1739 :                 qgsDoubleNear( mY.first(), mY.last() );
     385                 :       1858 :   if ( is3D() && closed )
     386                 :        112 :     closed &= qgsDoubleNear( mZ.first(), mZ.last() ) || ( std::isnan( mZ.first() ) && std::isnan( mZ.last() ) );
     387                 :       1858 :   return closed;
     388                 :       1865 : }
     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                 :        713 : QString QgsLineString::asWkt( int precision ) const
     612                 :            : {
     613                 :        713 :   QString wkt = wktTypeStr() + ' ';
     614                 :            : 
     615                 :        713 :   if ( isEmpty() )
     616                 :        404 :     wkt += QLatin1String( "EMPTY" );
     617                 :            :   else
     618                 :            :   {
     619                 :        309 :     QgsPointSequence pts;
     620                 :        309 :     points( pts );
     621                 :        309 :     wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
     622                 :        309 :   }
     623                 :        713 :   return wkt;
     624                 :        713 : }
     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                 :         83 : QgsPoint QgsLineString::startPoint() const
     792                 :            : {
     793                 :         83 :   if ( numPoints() < 1 )
     794                 :            :   {
     795                 :          1 :     return QgsPoint();
     796                 :            :   }
     797                 :         82 :   return pointN( 0 );
     798                 :         83 : }
     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                 :     141062 : int QgsLineString::numPoints() const
     823                 :            : {
     824                 :     141062 :   return mX.size();
     825                 :            : }
     826                 :            : 
     827                 :        162 : int QgsLineString::nCoordinates() const
     828                 :            : {
     829                 :        162 :   return mX.size();
     830                 :            : }
     831                 :            : 
     832                 :     230190 : QgsPoint QgsLineString::pointN( int i ) const
     833                 :            : {
     834                 :     230190 :   if ( i < 0 || i >= mX.size() )
     835                 :            :   {
     836                 :          6 :     return QgsPoint();
     837                 :            :   }
     838                 :            : 
     839                 :     230184 :   double x = mX.at( i );
     840                 :     230184 :   double y = mY.at( i );
     841                 :     230184 :   double z = std::numeric_limits<double>::quiet_NaN();
     842                 :     230184 :   double m = std::numeric_limits<double>::quiet_NaN();
     843                 :            : 
     844                 :     230184 :   bool hasZ = is3D();
     845                 :     230184 :   if ( hasZ )
     846                 :            :   {
     847                 :       2805 :     z = mZ.at( i );
     848                 :       2805 :   }
     849                 :     230184 :   bool hasM = isMeasure();
     850                 :     230184 :   if ( hasM )
     851                 :            :   {
     852                 :       2674 :     m = mM.at( i );
     853                 :       2674 :   }
     854                 :            : 
     855                 :     230184 :   QgsWkbTypes::Type t = QgsWkbTypes::Point;
     856                 :     230184 :   if ( mWkbType == QgsWkbTypes::LineString25D )
     857                 :            :   {
     858                 :         32 :     t = QgsWkbTypes::Point25D;
     859                 :         32 :   }
     860                 :     230152 :   else if ( hasZ && hasM )
     861                 :            :   {
     862                 :       2518 :     t = QgsWkbTypes::PointZM;
     863                 :       2518 :   }
     864                 :     227634 :   else if ( hasZ )
     865                 :            :   {
     866                 :        255 :     t = QgsWkbTypes::PointZ;
     867                 :        255 :   }
     868                 :     227379 :   else if ( hasM )
     869                 :            :   {
     870                 :        156 :     t = QgsWkbTypes::PointM;
     871                 :        156 :   }
     872                 :     230184 :   return QgsPoint( t, x, y, z, m );
     873                 :     230190 : }
     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                 :        526 : void QgsLineString::points( QgsPointSequence &pts ) const
     918                 :            : {
     919                 :        526 :   pts.clear();
     920                 :        526 :   int nPoints = numPoints();
     921                 :        526 :   pts.reserve( nPoints );
     922                 :       6754 :   for ( int i = 0; i < nPoints; ++i )
     923                 :            :   {
     924                 :       6228 :     pts.push_back( pointN( i ) );
     925                 :       6228 :   }
     926                 :        526 : }
     927                 :            : 
     928                 :        998 : void QgsLineString::setPoints( const QgsPointSequence &points )
     929                 :            : {
     930                 :        998 :   clearCache(); //set bounding box invalid
     931                 :            : 
     932                 :        998 :   if ( points.isEmpty() )
     933                 :            :   {
     934                 :          5 :     clear();
     935                 :          5 :     return;
     936                 :            :   }
     937                 :            : 
     938                 :            :   //get wkb type from first point
     939                 :        993 :   const QgsPoint &firstPt = points.at( 0 );
     940                 :        993 :   bool hasZ = firstPt.is3D();
     941                 :        993 :   bool hasM = firstPt.isMeasure();
     942                 :            : 
     943                 :        993 :   setZMTypeFromSubGeometry( &firstPt, QgsWkbTypes::LineString );
     944                 :            : 
     945                 :        993 :   mX.resize( points.size() );
     946                 :        993 :   mY.resize( points.size() );
     947                 :        993 :   if ( hasZ )
     948                 :            :   {
     949                 :        268 :     mZ.resize( points.size() );
     950                 :        268 :   }
     951                 :            :   else
     952                 :            :   {
     953                 :        725 :     mZ.clear();
     954                 :            :   }
     955                 :        993 :   if ( hasM )
     956                 :            :   {
     957                 :        219 :     mM.resize( points.size() );
     958                 :        219 :   }
     959                 :            :   else
     960                 :            :   {
     961                 :        774 :     mM.clear();
     962                 :            :   }
     963                 :            : 
     964                 :      31456 :   for ( int i = 0; i < points.size(); ++i )
     965                 :            :   {
     966                 :      30463 :     mX[i] = points.at( i ).x();
     967                 :      30463 :     mY[i] = points.at( i ).y();
     968                 :      30463 :     if ( hasZ )
     969                 :            :     {
     970                 :       3761 :       double z = points.at( i ).z();
     971                 :       3761 :       mZ[i] = std::isnan( z ) ? 0 : z;
     972                 :       3761 :     }
     973                 :      30463 :     if ( hasM )
     974                 :            :     {
     975                 :       3555 :       double m = points.at( i ).m();
     976                 :       3555 :       mM[i] = std::isnan( m ) ? 0 : m;
     977                 :       3555 :     }
     978                 :      30463 :   }
     979                 :        998 : }
     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                 :        719 : QString QgsLineString::geometryType() const
    1314                 :            : {
    1315                 :       1438 :   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                 :        138 : void QgsLineString::addVertex( const QgsPoint &pt )
    1466                 :            : {
    1467                 :        138 :   if ( mWkbType == QgsWkbTypes::Unknown || mX.isEmpty() )
    1468                 :            :   {
    1469                 :         60 :     setZMTypeFromSubGeometry( &pt, QgsWkbTypes::LineString );
    1470                 :         60 :   }
    1471                 :            : 
    1472                 :        138 :   mX.append( pt.x() );
    1473                 :        138 :   mY.append( pt.y() );
    1474                 :        138 :   if ( is3D() )
    1475                 :            :   {
    1476                 :         17 :     mZ.append( pt.z() );
    1477                 :         17 :   }
    1478                 :        138 :   if ( isMeasure() )
    1479                 :            :   {
    1480                 :         12 :     mM.append( pt.m() );
    1481                 :         12 :   }
    1482                 :        138 :   clearCache(); //set bounding box invalid
    1483                 :        138 : }
    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                 :      13366 : bool QgsLineString::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
    1563                 :            : {
    1564                 :      13366 :   if ( node < 0 || node >= numPoints() )
    1565                 :            :   {
    1566                 :         14 :     return false;
    1567                 :            :   }
    1568                 :      13352 :   point = pointN( node );
    1569                 :      13352 :   type = QgsVertexId::SegmentVertex;
    1570                 :      13352 :   return true;
    1571                 :      13366 : }
    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                 :         39 : void QgsLineString::importVerticesFromWkb( const QgsConstWkbPtr &wkb )
    1628                 :            : {
    1629                 :         39 :   bool hasZ = is3D();
    1630                 :         39 :   bool hasM = isMeasure();
    1631                 :         39 :   int nVertices = 0;
    1632                 :         39 :   wkb >> nVertices;
    1633                 :         39 :   mX.resize( nVertices );
    1634                 :         39 :   mY.resize( nVertices );
    1635                 :         39 :   hasZ ? mZ.resize( nVertices ) : mZ.clear();
    1636                 :         39 :   hasM ? mM.resize( nVertices ) : mM.clear();
    1637                 :         39 :   double *x = mX.data();
    1638                 :         39 :   double *y = mY.data();
    1639                 :         39 :   double *m = hasM ? mM.data() : nullptr;
    1640                 :         39 :   double *z = hasZ ? mZ.data() : nullptr;
    1641                 :        192 :   for ( int i = 0; i < nVertices; ++i )
    1642                 :            :   {
    1643                 :        154 :     wkb >> *x++;
    1644                 :        154 :     wkb >> *y++;
    1645                 :        154 :     if ( hasZ )
    1646                 :            :     {
    1647                 :         82 :       wkb >> *z++;
    1648                 :         82 :     }
    1649                 :        153 :     if ( hasM )
    1650                 :            :     {
    1651                 :         72 :       wkb >> *m++;
    1652                 :         72 :     }
    1653                 :        153 :   }
    1654                 :         38 :   clearCache(); //set bounding box invalid
    1655                 :         38 : }
    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                 :         48 : void QgsLineString::close()
    1664                 :            : {
    1665                 :         48 :   if ( numPoints() < 1 || isClosed() )
    1666                 :            :   {
    1667                 :         25 :     return;
    1668                 :            :   }
    1669                 :         23 :   addVertex( startPoint() );
    1670                 :         48 : }
    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 : }

Generated by: LCOV version 1.14