LCOV - code coverage report
Current view: top level - core/geometry - qgscompoundcurve.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 599 666 89.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgscompoundcurve.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 "qgscompoundcurve.h"
      19                 :            : #include "qgsapplication.h"
      20                 :            : #include "qgscircularstring.h"
      21                 :            : #include "qgsgeometryutils.h"
      22                 :            : #include "qgslinestring.h"
      23                 :            : #include "qgswkbptr.h"
      24                 :            : #include "qgsfeedback.h"
      25                 :            : 
      26                 :            : #include <QJsonObject>
      27                 :            : #include <QPainter>
      28                 :            : #include <QPainterPath>
      29                 :            : #include <memory>
      30                 :            : #include <nlohmann/json.hpp>
      31                 :            : 
      32                 :       1127 : QgsCompoundCurve::QgsCompoundCurve()
      33                 :       2254 : {
      34                 :       1127 :   mWkbType = QgsWkbTypes::CompoundCurve;
      35                 :       1127 : }
      36                 :            : 
      37                 :        581 : QgsCompoundCurve::~QgsCompoundCurve()
      38                 :        581 : {
      39                 :        326 :   clear();
      40                 :        581 : }
      41                 :            : 
      42                 :         24 : bool QgsCompoundCurve::equals( const QgsCurve &other ) const
      43                 :            : {
      44                 :         24 :   const QgsCompoundCurve *otherCurve = qgsgeometry_cast< const QgsCompoundCurve * >( &other );
      45                 :         24 :   if ( !otherCurve )
      46                 :          1 :     return false;
      47                 :            : 
      48                 :         23 :   if ( mWkbType != otherCurve->mWkbType )
      49                 :          2 :     return false;
      50                 :            : 
      51                 :         21 :   if ( mCurves.size() != otherCurve->mCurves.size() )
      52                 :          5 :     return false;
      53                 :            : 
      54                 :         33 :   for ( int i = 0; i < mCurves.size(); ++i )
      55                 :            :   {
      56                 :         23 :     if ( *mCurves.at( i ) != *otherCurve->mCurves.at( i ) )
      57                 :          6 :       return false;
      58                 :         17 :   }
      59                 :            : 
      60                 :         10 :   return true;
      61                 :         24 : }
      62                 :            : 
      63                 :          3 : QgsCompoundCurve *QgsCompoundCurve::createEmptyWithSameType() const
      64                 :            : {
      65                 :          3 :   auto result = std::make_unique< QgsCompoundCurve >();
      66                 :          3 :   result->mWkbType = mWkbType;
      67                 :          3 :   return result.release();
      68                 :          3 : }
      69                 :            : 
      70                 :        451 : QString QgsCompoundCurve::geometryType() const
      71                 :            : {
      72                 :        902 :   return QStringLiteral( "CompoundCurve" );
      73                 :            : }
      74                 :            : 
      75                 :          5 : int QgsCompoundCurve::dimension() const
      76                 :            : {
      77                 :          5 :   return 1;
      78                 :            : }
      79                 :            : 
      80                 :          6 : QgsCompoundCurve::QgsCompoundCurve( const QgsCompoundCurve &curve ): QgsCurve( curve )
      81                 :         12 : {
      82                 :          6 :   mWkbType = curve.wkbType();
      83                 :          6 :   mCurves.reserve( curve.mCurves.size() );
      84                 :         14 :   for ( const QgsCurve *c : curve.mCurves )
      85                 :            :   {
      86                 :          8 :     mCurves.append( c->clone() );
      87                 :            :   }
      88                 :          6 : }
      89                 :            : 
      90                 :          1 : QgsCompoundCurve &QgsCompoundCurve::operator=( const QgsCompoundCurve &curve )
      91                 :            : {
      92                 :          1 :   if ( &curve != this )
      93                 :            :   {
      94                 :          1 :     clearCache();
      95                 :          1 :     QgsCurve::operator=( curve );
      96                 :          3 :     for ( const QgsCurve *c : curve.mCurves )
      97                 :            :     {
      98                 :          2 :       mCurves.append( c->clone() );
      99                 :            :     }
     100                 :          1 :   }
     101                 :          1 :   return *this;
     102                 :            : }
     103                 :            : 
     104                 :          6 : QgsCompoundCurve *QgsCompoundCurve::clone() const
     105                 :            : {
     106                 :          6 :   return new QgsCompoundCurve( *this );
     107                 :          0 : }
     108                 :            : 
     109                 :        821 : void QgsCompoundCurve::clear()
     110                 :            : {
     111                 :        821 :   mWkbType = QgsWkbTypes::CompoundCurve;
     112                 :        821 :   qDeleteAll( mCurves );
     113                 :        821 :   mCurves.clear();
     114                 :        821 :   clearCache();
     115                 :        821 : }
     116                 :            : 
     117                 :         12 : QgsRectangle QgsCompoundCurve::calculateBoundingBox() const
     118                 :            : {
     119                 :         12 :   if ( mCurves.empty() )
     120                 :            :   {
     121                 :          2 :     return QgsRectangle();
     122                 :            :   }
     123                 :            : 
     124                 :         10 :   QgsRectangle bbox = mCurves.at( 0 )->boundingBox();
     125                 :         12 :   for ( int i = 1; i < mCurves.size(); ++i )
     126                 :            :   {
     127                 :          2 :     QgsRectangle curveBox = mCurves.at( i )->boundingBox();
     128                 :          2 :     bbox.combineExtentWith( curveBox );
     129                 :          2 :   }
     130                 :         10 :   return bbox;
     131                 :         12 : }
     132                 :            : 
     133                 :          5 : bool QgsCompoundCurve::fromWkb( QgsConstWkbPtr &wkbPtr )
     134                 :            : {
     135                 :          5 :   clear();
     136                 :          5 :   if ( !wkbPtr )
     137                 :            :   {
     138                 :          1 :     return false;
     139                 :            :   }
     140                 :            : 
     141                 :          4 :   QgsWkbTypes::Type type = wkbPtr.readHeader();
     142                 :          4 :   if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CompoundCurve )
     143                 :            :   {
     144                 :          1 :     return false;
     145                 :            :   }
     146                 :          3 :   mWkbType = type;
     147                 :            : 
     148                 :            :   int nCurves;
     149                 :          3 :   wkbPtr >> nCurves;
     150                 :          3 :   QgsCurve *currentCurve = nullptr;
     151                 :          6 :   for ( int i = 0; i < nCurves; ++i )
     152                 :            :   {
     153                 :          3 :     QgsWkbTypes::Type curveType = wkbPtr.readHeader();
     154                 :          3 :     wkbPtr -= 1 + sizeof( int );
     155                 :          3 :     if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::LineString )
     156                 :            :     {
     157                 :          0 :       currentCurve = new QgsLineString();
     158                 :          0 :     }
     159                 :          3 :     else if ( QgsWkbTypes::flatType( curveType ) == QgsWkbTypes::CircularString )
     160                 :            :     {
     161                 :          3 :       currentCurve = new QgsCircularString();
     162                 :          3 :     }
     163                 :            :     else
     164                 :            :     {
     165                 :          0 :       return false;
     166                 :            :     }
     167                 :          3 :     currentCurve->fromWkb( wkbPtr );  // also updates wkbPtr
     168                 :          3 :     mCurves.append( currentCurve );
     169                 :          3 :   }
     170                 :          3 :   return true;
     171                 :          5 : }
     172                 :            : 
     173                 :        425 : bool QgsCompoundCurve::fromWkt( const QString &wkt )
     174                 :            : {
     175                 :        425 :   clear();
     176                 :            : 
     177                 :        425 :   QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
     178                 :            : 
     179                 :        425 :   if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::CompoundCurve )
     180                 :          1 :     return false;
     181                 :        424 :   mWkbType = parts.first;
     182                 :            : 
     183                 :        424 :   QString secondWithoutParentheses = parts.second;
     184                 :        424 :   secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
     185                 :        447 :   if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
     186                 :         23 :        secondWithoutParentheses.isEmpty() )
     187                 :        402 :     return true;
     188                 :            : 
     189                 :         55 :   QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
     190                 :            : 
     191                 :         22 :   const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
     192                 :         60 :   for ( const QString &childWkt : blocks )
     193                 :            :   {
     194                 :         40 :     QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
     195                 :            : 
     196                 :         40 :     if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::LineString )
     197                 :         20 :       mCurves.append( new QgsLineString() );
     198                 :         20 :     else if ( QgsWkbTypes::flatType( childParts.first ) == QgsWkbTypes::CircularString )
     199                 :         19 :       mCurves.append( new QgsCircularString() );
     200                 :            :     else
     201                 :            :     {
     202                 :          1 :       clear();
     203                 :          1 :       return false;
     204                 :            :     }
     205                 :         39 :     if ( !mCurves.back()->fromWkt( childWkt ) )
     206                 :            :     {
     207                 :          1 :       clear();
     208                 :          1 :       return false;
     209                 :            :     }
     210                 :         40 :   }
     211                 :            : 
     212                 :            :   //scan through curves and check if dimensionality of curves is different to compound curve.
     213                 :            :   //if so, update the type dimensionality of the compound curve to match
     214                 :         20 :   bool hasZ = false;
     215                 :         20 :   bool hasM = false;
     216                 :         52 :   for ( const QgsCurve *curve : std::as_const( mCurves ) )
     217                 :            :   {
     218                 :         35 :     hasZ = hasZ || curve->is3D();
     219                 :         35 :     hasM = hasM || curve->isMeasure();
     220                 :         35 :     if ( hasZ && hasM )
     221                 :          3 :       break;
     222                 :            :   }
     223                 :         20 :   if ( hasZ )
     224                 :          6 :     addZValue( 0 );
     225                 :         20 :   if ( hasM )
     226                 :          5 :     addMValue( 0 );
     227                 :            : 
     228                 :         20 :   return true;
     229                 :        425 : }
     230                 :            : 
     231                 :          6 : int QgsCompoundCurve::wkbSize( QgsAbstractGeometry::WkbFlags flags ) const
     232                 :            : {
     233                 :          6 :   int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
     234                 :         12 :   for ( const QgsCurve *curve : mCurves )
     235                 :            :   {
     236                 :          6 :     binarySize += curve->wkbSize( flags );
     237                 :            :   }
     238                 :          6 :   return binarySize;
     239                 :            : }
     240                 :            : 
     241                 :          3 : QByteArray QgsCompoundCurve::asWkb( WkbFlags flags ) const
     242                 :            : {
     243                 :          3 :   QByteArray wkbArray;
     244                 :          3 :   wkbArray.resize( QgsCompoundCurve::wkbSize( flags ) );
     245                 :          3 :   QgsWkbPtr wkb( wkbArray );
     246                 :          3 :   wkb << static_cast<char>( QgsApplication::endian() );
     247                 :          3 :   wkb << static_cast<quint32>( wkbType() );
     248                 :          3 :   wkb << static_cast<quint32>( mCurves.size() );
     249                 :          6 :   for ( const QgsCurve *curve : mCurves )
     250                 :            :   {
     251                 :          3 :     wkb << curve->asWkb( flags );
     252                 :            :   }
     253                 :          3 :   return wkbArray;
     254                 :          3 : }
     255                 :            : 
     256                 :        443 : QString QgsCompoundCurve::asWkt( int precision ) const
     257                 :            : {
     258                 :        443 :   QString wkt = wktTypeStr();
     259                 :        443 :   if ( isEmpty() )
     260                 :        402 :     wkt += QLatin1String( " EMPTY" );
     261                 :            :   else
     262                 :            :   {
     263                 :         41 :     wkt += QLatin1String( " (" );
     264                 :        108 :     for ( const QgsCurve *curve : mCurves )
     265                 :            :     {
     266                 :         67 :       QString childWkt = curve->asWkt( precision );
     267                 :         67 :       if ( qgsgeometry_cast<const QgsLineString *>( curve ) )
     268                 :            :       {
     269                 :            :         // Type names of linear geometries are omitted
     270                 :         29 :         childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
     271                 :         29 :       }
     272                 :         67 :       wkt += childWkt + ',';
     273                 :         67 :     }
     274                 :         41 :     if ( wkt.endsWith( ',' ) )
     275                 :            :     {
     276                 :         41 :       wkt.chop( 1 );
     277                 :         41 :     }
     278                 :         41 :     wkt += ')';
     279                 :            :   }
     280                 :        443 :   return wkt;
     281                 :        443 : }
     282                 :            : 
     283                 :          3 : QDomElement QgsCompoundCurve::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
     284                 :            : {
     285                 :            :   // GML2 does not support curves
     286                 :          3 :   std::unique_ptr< QgsLineString > line( curveToLine() );
     287                 :          3 :   QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
     288                 :          3 :   return gml;
     289                 :          3 : }
     290                 :            : 
     291                 :          3 : QDomElement QgsCompoundCurve::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
     292                 :            : {
     293                 :          6 :   QDomElement compoundCurveElem = doc.createElementNS( ns, QStringLiteral( "CompositeCurve" ) );
     294                 :            : 
     295                 :          3 :   if ( isEmpty() )
     296                 :          1 :     return compoundCurveElem;
     297                 :            : 
     298                 :          6 :   for ( const QgsCurve *curve : mCurves )
     299                 :            :   {
     300                 :          8 :     QDomElement curveMemberElem = doc.createElementNS( ns, QStringLiteral( "curveMember" ) );
     301                 :          4 :     QDomElement curveElem = curve->asGml3( doc, precision, ns, axisOrder );
     302                 :          4 :     curveMemberElem.appendChild( curveElem );
     303                 :          4 :     compoundCurveElem.appendChild( curveMemberElem );
     304                 :          4 :   }
     305                 :            : 
     306                 :          2 :   return compoundCurveElem;
     307                 :          3 : }
     308                 :            : 
     309                 :          3 : json QgsCompoundCurve::asJsonObject( int precision ) const
     310                 :            : {
     311                 :            :   // GeoJSON does not support curves
     312                 :          3 :   std::unique_ptr< QgsLineString > line( curveToLine() );
     313                 :          3 :   return line->asJsonObject( precision );
     314                 :          3 : }
     315                 :            : 
     316                 :          4 : double QgsCompoundCurve::length() const
     317                 :            : {
     318                 :          4 :   double length = 0;
     319                 :         10 :   for ( const QgsCurve *curve : mCurves )
     320                 :            :   {
     321                 :          6 :     length += curve->length();
     322                 :            :   }
     323                 :          4 :   return length;
     324                 :            : }
     325                 :            : 
     326                 :         22 : QgsPoint QgsCompoundCurve::startPoint() const
     327                 :            : {
     328                 :         22 :   if ( mCurves.empty() )
     329                 :            :   {
     330                 :          1 :     return QgsPoint();
     331                 :            :   }
     332                 :         21 :   return mCurves.at( 0 )->startPoint();
     333                 :         22 : }
     334                 :            : 
     335                 :         21 : QgsPoint QgsCompoundCurve::endPoint() const
     336                 :            : {
     337                 :         21 :   if ( mCurves.empty() )
     338                 :            :   {
     339                 :          1 :     return QgsPoint();
     340                 :            :   }
     341                 :         20 :   return mCurves.at( mCurves.size() - 1 )->endPoint();
     342                 :         21 : }
     343                 :            : 
     344                 :         34 : void QgsCompoundCurve::points( QgsPointSequence &pts ) const
     345                 :            : {
     346                 :         34 :   pts.clear();
     347                 :         34 :   if ( mCurves.empty() )
     348                 :            :   {
     349                 :          5 :     return;
     350                 :            :   }
     351                 :            : 
     352                 :         29 :   mCurves[0]->points( pts );
     353                 :         46 :   for ( int i = 1; i < mCurves.size(); ++i )
     354                 :            :   {
     355                 :         17 :     QgsPointSequence pList;
     356                 :         17 :     mCurves[i]->points( pList );
     357                 :         17 :     pList.removeFirst(); //first vertex already added in previous line
     358                 :         17 :     pts.append( pList );
     359                 :         17 :   }
     360                 :         34 : }
     361                 :            : 
     362                 :        112 : int QgsCompoundCurve::numPoints() const
     363                 :            : {
     364                 :        112 :   int nPoints = 0;
     365                 :        112 :   int nCurves = mCurves.size();
     366                 :        112 :   if ( nCurves < 1 )
     367                 :            :   {
     368                 :         18 :     return 0;
     369                 :            :   }
     370                 :            : 
     371                 :        211 :   for ( int i = 0; i < nCurves; ++i )
     372                 :            :   {
     373                 :        117 :     nPoints += mCurves.at( i )->numPoints() - 1; //last vertex is equal to first of next section
     374                 :        117 :   }
     375                 :         94 :   nPoints += 1; //last vertex was removed above
     376                 :         94 :   return nPoints;
     377                 :        112 : }
     378                 :            : 
     379                 :        475 : bool QgsCompoundCurve::isEmpty() const
     380                 :            : {
     381                 :        475 :   if ( mCurves.isEmpty() )
     382                 :        418 :     return true;
     383                 :            : 
     384                 :         57 :   for ( QgsCurve *curve : mCurves )
     385                 :            :   {
     386                 :         57 :     if ( !curve->isEmpty() )
     387                 :         57 :       return false;
     388                 :            :   }
     389                 :          0 :   return true;
     390                 :        475 : }
     391                 :            : 
     392                 :          4 : bool QgsCompoundCurve::isValid( QString &error, int flags ) const
     393                 :            : {
     394                 :          4 :   if ( mCurves.isEmpty() )
     395                 :          1 :     return true;
     396                 :            : 
     397                 :          8 :   for ( int i = 0; i < mCurves.size() ; ++i )
     398                 :            :   {
     399                 :          6 :     if ( !mCurves[i]->isValid( error, flags ) )
     400                 :            :     {
     401                 :          1 :       error = QObject::tr( "Curve[%1]: %2" ).arg( i + 1 ).arg( error );
     402                 :          1 :       return false;
     403                 :            :     }
     404                 :          5 :   }
     405                 :          2 :   return QgsCurve::isValid( error, flags );
     406                 :          4 : }
     407                 :            : 
     408                 :         19 : QgsLineString *QgsCompoundCurve::curveToLine( double tolerance, SegmentationToleranceType toleranceType ) const
     409                 :            : {
     410                 :         19 :   QgsLineString *line = new QgsLineString();
     411                 :         19 :   std::unique_ptr< QgsLineString > currentLine;
     412                 :         45 :   for ( const QgsCurve *curve : mCurves )
     413                 :            :   {
     414                 :         26 :     currentLine.reset( curve->curveToLine( tolerance, toleranceType ) );
     415                 :         26 :     line->append( currentLine.get() );
     416                 :            :   }
     417                 :         19 :   return line;
     418                 :         19 : }
     419                 :            : 
     420                 :          1 : QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
     421                 :            : {
     422                 :          1 :   std::unique_ptr<QgsCompoundCurve> result( createEmptyWithSameType() );
     423                 :            : 
     424                 :          3 :   for ( QgsCurve *curve : mCurves )
     425                 :            :   {
     426                 :          2 :     std::unique_ptr<QgsCurve> gridified( static_cast< QgsCurve * >( curve->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ) );
     427                 :          2 :     if ( gridified )
     428                 :            :     {
     429                 :          2 :       result->mCurves.append( gridified.release() );
     430                 :          2 :     }
     431                 :          2 :   }
     432                 :            : 
     433                 :          1 :   if ( result->mCurves.empty() )
     434                 :          0 :     return nullptr;
     435                 :            :   else
     436                 :          1 :     return result.release();
     437                 :          1 : }
     438                 :            : 
     439                 :         11 : bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
     440                 :            : {
     441                 :         11 :   bool result = false;
     442                 :         11 :   const QVector< QgsCurve * > curves = mCurves;
     443                 :         11 :   int i = 0;
     444                 :         11 :   QgsPoint lastEnd;
     445                 :         26 :   for ( QgsCurve *curve : curves )
     446                 :            :   {
     447                 :         15 :     result = curve->removeDuplicateNodes( epsilon, useZValues ) || result;
     448                 :         15 :     if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
     449                 :            :     {
     450                 :            :       // empty curve, remove it
     451                 :          1 :       delete mCurves.takeAt( i );
     452                 :          1 :       result = true;
     453                 :          1 :     }
     454                 :            :     else
     455                 :            :     {
     456                 :            :       // ensure this line starts exactly where previous line ended
     457                 :         14 :       if ( i > 0 )
     458                 :            :       {
     459                 :          4 :         curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
     460                 :          4 :       }
     461                 :         14 :       lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
     462                 :            :     }
     463                 :         15 :     i++;
     464                 :            :   }
     465                 :         11 :   return result;
     466                 :         11 : }
     467                 :            : 
     468                 :          0 : bool QgsCompoundCurve::boundingBoxIntersects( const QgsRectangle &rectangle ) const
     469                 :            : {
     470                 :          0 :   if ( mCurves.empty() )
     471                 :          0 :     return false;
     472                 :            : 
     473                 :            :   // if we already have the bounding box calculated, then this check is trivial!
     474                 :          0 :   if ( !mBoundingBox.isNull() )
     475                 :            :   {
     476                 :          0 :     return mBoundingBox.intersects( rectangle );
     477                 :            :   }
     478                 :            : 
     479                 :            :   // otherwise loop through each member curve and test the bounding box intersection.
     480                 :            :   // This gives us a chance to use optimisations which may be present on the individual
     481                 :            :   // curve subclasses, and at worst it will cause a calculation of the bounding box
     482                 :            :   // of each individual member curve which we would have to do anyway... (and these
     483                 :            :   // bounding boxes are cached, so would be reused without additional expense)
     484                 :          0 :   for ( const QgsCurve *curve : mCurves )
     485                 :            :   {
     486                 :          0 :     if ( curve->boundingBoxIntersects( rectangle ) )
     487                 :          0 :       return true;
     488                 :            :   }
     489                 :            : 
     490                 :            :   // even if we don't intersect the bounding box of any member curves, we may still intersect the
     491                 :            :   // bounding box of the overall compound curve.
     492                 :            :   // so here we fall back to the non-optimised base class check which has to first calculate
     493                 :            :   // the overall bounding box of the compound curve..
     494                 :          0 :   return QgsAbstractGeometry::boundingBoxIntersects( rectangle );
     495                 :          0 : }
     496                 :            : 
     497                 :         35 : const QgsCurve *QgsCompoundCurve::curveAt( int i ) const
     498                 :            : {
     499                 :         35 :   if ( i < 0 || i >= mCurves.size() )
     500                 :            :   {
     501                 :         10 :     return nullptr;
     502                 :            :   }
     503                 :         25 :   return mCurves.at( i );
     504                 :         35 : }
     505                 :            : 
     506                 :        202 : void QgsCompoundCurve::addCurve( QgsCurve *c, const bool extendPrevious )
     507                 :            : {
     508                 :        202 :   if ( !c )
     509                 :          1 :     return;
     510                 :            : 
     511                 :        201 :   if ( mCurves.empty() )
     512                 :            :   {
     513                 :        127 :     setZMTypeFromSubGeometry( c, QgsWkbTypes::CompoundCurve );
     514                 :        127 :   }
     515                 :            : 
     516                 :        201 :   if ( QgsWkbTypes::hasZ( mWkbType ) && !QgsWkbTypes::hasZ( c->wkbType() ) )
     517                 :            :   {
     518                 :          3 :     c->addZValue();
     519                 :          3 :   }
     520                 :        198 :   else if ( !QgsWkbTypes::hasZ( mWkbType ) && QgsWkbTypes::hasZ( c->wkbType() ) )
     521                 :            :   {
     522                 :          4 :     c->dropZValue();
     523                 :          4 :   }
     524                 :        201 :   if ( QgsWkbTypes::hasM( mWkbType ) && !QgsWkbTypes::hasM( c->wkbType() ) )
     525                 :            :   {
     526                 :          3 :     c->addMValue();
     527                 :          3 :   }
     528                 :        198 :   else if ( !QgsWkbTypes::hasM( mWkbType ) && QgsWkbTypes::hasM( c->wkbType() ) )
     529                 :            :   {
     530                 :          4 :     c->dropMValue();
     531                 :          4 :   }
     532                 :            : 
     533                 :        201 :   QgsLineString *previousLineString = !mCurves.empty() ? qgsgeometry_cast< QgsLineString * >( mCurves.constLast() ) : nullptr;
     534                 :        201 :   const QgsLineString *newLineString = qgsgeometry_cast< const QgsLineString * >( c );
     535                 :        201 :   const bool canExtendPrevious = extendPrevious && previousLineString && newLineString;
     536                 :        201 :   if ( canExtendPrevious )
     537                 :            :   {
     538                 :          2 :     previousLineString->append( newLineString );
     539                 :            :     // we are taking ownership, so delete the input curve
     540                 :          2 :     delete c;
     541                 :          2 :     c = nullptr;
     542                 :          2 :   }
     543                 :            :   else
     544                 :            :   {
     545                 :        199 :     mCurves.append( c );
     546                 :            :   }
     547                 :            : 
     548                 :        201 :   clearCache();
     549                 :        202 : }
     550                 :            : 
     551                 :         21 : void QgsCompoundCurve::removeCurve( int i )
     552                 :            : {
     553                 :         21 :   if ( i < 0 || i >= mCurves.size() )
     554                 :            :   {
     555                 :          5 :     return;
     556                 :            :   }
     557                 :            : 
     558                 :         16 :   delete mCurves.takeAt( i );
     559                 :         16 :   clearCache();
     560                 :         21 : }
     561                 :            : 
     562                 :          9 : void QgsCompoundCurve::addVertex( const QgsPoint &pt )
     563                 :            : {
     564                 :          9 :   if ( mCurves.isEmpty() || mWkbType == QgsWkbTypes::Unknown )
     565                 :            :   {
     566                 :          6 :     setZMTypeFromSubGeometry( &pt, QgsWkbTypes::CompoundCurve );
     567                 :          6 :   }
     568                 :            : 
     569                 :            :   //is last curve QgsLineString
     570                 :          9 :   QgsCurve *lastCurve = nullptr;
     571                 :          9 :   if ( !mCurves.isEmpty() )
     572                 :            :   {
     573                 :          3 :     lastCurve = mCurves.at( mCurves.size() - 1 );
     574                 :          3 :   }
     575                 :            : 
     576                 :          9 :   QgsLineString *line = nullptr;
     577                 :          9 :   if ( !lastCurve || QgsWkbTypes::flatType( lastCurve->wkbType() ) != QgsWkbTypes::LineString )
     578                 :            :   {
     579                 :          6 :     line = new QgsLineString();
     580                 :          6 :     mCurves.append( line );
     581                 :          6 :     if ( lastCurve )
     582                 :            :     {
     583                 :          0 :       line->addVertex( lastCurve->endPoint() );
     584                 :          0 :     }
     585                 :          6 :     lastCurve = line;
     586                 :          6 :   }
     587                 :            :   else //create new QgsLineString* with point in it
     588                 :            :   {
     589                 :          3 :     line = static_cast<QgsLineString *>( lastCurve );
     590                 :            :   }
     591                 :          9 :   line->addVertex( pt );
     592                 :          9 :   clearCache();
     593                 :          9 : }
     594                 :            : 
     595                 :          0 : void QgsCompoundCurve::draw( QPainter &p ) const
     596                 :            : {
     597                 :          0 :   for ( const QgsCurve *curve : mCurves )
     598                 :            :   {
     599                 :          0 :     curve->draw( p );
     600                 :            :   }
     601                 :          0 : }
     602                 :            : 
     603                 :          3 : void QgsCompoundCurve::transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d, bool transformZ )
     604                 :            : {
     605                 :          9 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
     606                 :            :   {
     607                 :          6 :     curve->transform( ct, d, transformZ );
     608                 :            :   }
     609                 :          3 :   clearCache();
     610                 :          3 : }
     611                 :            : 
     612                 :          1 : void QgsCompoundCurve::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
     613                 :            : {
     614                 :          3 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
     615                 :            :   {
     616                 :          2 :     curve->transform( t, zTranslate, zScale, mTranslate, mScale );
     617                 :            :   }
     618                 :          1 :   clearCache();
     619                 :          1 : }
     620                 :            : 
     621                 :          4 : void QgsCompoundCurve::addToPainterPath( QPainterPath &path ) const
     622                 :            : {
     623                 :          4 :   QPainterPath pp;
     624                 :          8 :   for ( const QgsCurve *curve : mCurves )
     625                 :            :   {
     626                 :          4 :     curve->addToPainterPath( pp );
     627                 :            :   }
     628                 :          4 :   path.addPath( pp );
     629                 :          4 : }
     630                 :            : 
     631                 :          0 : void QgsCompoundCurve::drawAsPolygon( QPainter &p ) const
     632                 :            : {
     633                 :          0 :   QPainterPath pp;
     634                 :          0 :   for ( const QgsCurve *curve : mCurves )
     635                 :            :   {
     636                 :          0 :     curve->addToPainterPath( pp );
     637                 :            :   }
     638                 :          0 :   p.drawPath( pp );
     639                 :          0 : }
     640                 :            : 
     641                 :         13 : bool QgsCompoundCurve::insertVertex( QgsVertexId position, const QgsPoint &vertex )
     642                 :            : {
     643                 :         13 :   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
     644                 :         13 :   if ( curveIds.empty() )
     645                 :            :   {
     646                 :          3 :     return false;
     647                 :            :   }
     648                 :         10 :   int curveId = curveIds.at( 0 ).first;
     649                 :         10 :   if ( curveId >= mCurves.size() )
     650                 :            :   {
     651                 :          0 :     return false;
     652                 :            :   }
     653                 :            : 
     654                 :         10 :   bool success = mCurves.at( curveId )->insertVertex( curveIds.at( 0 ).second, vertex );
     655                 :         10 :   if ( success )
     656                 :            :   {
     657                 :          8 :     clearCache(); //bbox changed
     658                 :          8 :   }
     659                 :         10 :   return success;
     660                 :         13 : }
     661                 :            : 
     662                 :         17 : bool QgsCompoundCurve::moveVertex( QgsVertexId position, const QgsPoint &newPos )
     663                 :            : {
     664                 :         17 :   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
     665                 :         17 :   QVector< QPair<int, QgsVertexId> >::const_iterator idIt = curveIds.constBegin();
     666                 :         28 :   for ( ; idIt != curveIds.constEnd(); ++idIt )
     667                 :            :   {
     668                 :         11 :     mCurves.at( idIt->first )->moveVertex( idIt->second, newPos );
     669                 :         11 :   }
     670                 :            : 
     671                 :         17 :   bool success = !curveIds.isEmpty();
     672                 :         17 :   if ( success )
     673                 :            :   {
     674                 :         11 :     clearCache(); //bbox changed
     675                 :         11 :   }
     676                 :         17 :   return success;
     677                 :         17 : }
     678                 :            : 
     679                 :          9 : bool QgsCompoundCurve::deleteVertex( QgsVertexId position )
     680                 :            : {
     681                 :          9 :   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( position );
     682                 :          9 :   if ( curveIds.size() == 1 )
     683                 :            :   {
     684                 :          4 :     if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
     685                 :            :     {
     686                 :          0 :       clearCache(); //bbox may have changed
     687                 :          0 :       return false;
     688                 :            :     }
     689                 :          4 :     if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 )
     690                 :            :     {
     691                 :          2 :       removeCurve( curveIds.at( 0 ).first );
     692                 :          2 :     }
     693                 :          4 :   }
     694                 :          5 :   else if ( curveIds.size() == 2 )
     695                 :            :   {
     696                 :            :     Q_ASSERT( curveIds.at( 1 ).first == curveIds.at( 0 ).first + 1 );
     697                 :            :     Q_ASSERT( curveIds.at( 0 ).second.vertex == mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 );
     698                 :            :     Q_ASSERT( curveIds.at( 1 ).second.vertex == 0 );
     699                 :          2 :     QgsPoint startPoint = mCurves.at( curveIds.at( 0 ).first ) ->startPoint();
     700                 :          2 :     QgsPoint endPoint = mCurves.at( curveIds.at( 1 ).first ) ->endPoint();
     701                 :          2 :     if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::LineString &&
     702                 :          2 :          QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
     703                 :          0 :          mCurves.at( curveIds.at( 1 ).first )->numPoints() > 3 )
     704                 :            :     {
     705                 :          0 :       QgsPoint intermediatePoint;
     706                 :            :       QgsVertexId::VertexType type;
     707                 :          0 :       mCurves.at( curveIds.at( 1 ).first ) ->pointAt( 2, intermediatePoint, type );
     708                 :          0 :       mCurves.at( curveIds.at( 0 ).first )->moveVertex(
     709                 :          0 :         QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), intermediatePoint );
     710                 :          0 :     }
     711                 :          2 :     else if ( !mCurves.at( curveIds.at( 0 ).first )->deleteVertex( curveIds.at( 0 ).second ) )
     712                 :            :     {
     713                 :          0 :       clearCache(); //bbox may have changed
     714                 :          0 :       return false;
     715                 :            :     }
     716                 :          2 :     if ( QgsWkbTypes::flatType( mCurves.at( curveIds.at( 0 ).first )->wkbType() ) == QgsWkbTypes::CircularString &&
     717                 :          0 :          mCurves.at( curveIds.at( 0 ).first )->numPoints() > 0 &&
     718                 :          0 :          QgsWkbTypes::flatType( mCurves.at( curveIds.at( 1 ).first )->wkbType() ) == QgsWkbTypes::LineString )
     719                 :            :     {
     720                 :          0 :       QgsPoint intermediatePoint = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
     721                 :          0 :       mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), intermediatePoint );
     722                 :          0 :     }
     723                 :          2 :     else if ( !mCurves.at( curveIds.at( 1 ).first )->deleteVertex( curveIds.at( 1 ).second ) )
     724                 :            :     {
     725                 :          0 :       clearCache(); //bbox may have changed
     726                 :          0 :       return false;
     727                 :            :     }
     728                 :          3 :     if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
     729                 :          1 :          mCurves.at( curveIds.at( 1 ).first )->numPoints() != 0 )
     730                 :            :     {
     731                 :          1 :       mCurves.at( curveIds.at( 1 ).first )->moveVertex( QgsVertexId( 0, 0, 0 ), startPoint );
     732                 :          1 :       removeCurve( curveIds.at( 0 ).first );
     733                 :          1 :     }
     734                 :          2 :     else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() != 0 &&
     735                 :          1 :               mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
     736                 :            :     {
     737                 :          2 :       mCurves.at( curveIds.at( 0 ).first )->moveVertex(
     738                 :          1 :         QgsVertexId( 0, 0, mCurves.at( curveIds.at( 0 ).first )->numPoints() - 1 ), endPoint );
     739                 :          1 :       removeCurve( curveIds.at( 1 ).first );
     740                 :          1 :     }
     741                 :          0 :     else if ( mCurves.at( curveIds.at( 0 ).first )->numPoints() == 0 &&
     742                 :          0 :               mCurves.at( curveIds.at( 1 ).first )->numPoints() == 0 )
     743                 :            :     {
     744                 :          0 :       removeCurve( curveIds.at( 1 ).first );
     745                 :          0 :       removeCurve( curveIds.at( 0 ).first );
     746                 :          0 :       QgsLineString *line = new QgsLineString();
     747                 :          0 :       line->insertVertex( QgsVertexId( 0, 0, 0 ), startPoint );
     748                 :          0 :       line->insertVertex( QgsVertexId( 0, 0, 1 ), endPoint );
     749                 :          0 :       mCurves.insert( curveIds.at( 0 ).first, line );
     750                 :          0 :     }
     751                 :            :     else
     752                 :            :     {
     753                 :          0 :       QgsPoint endPointOfFirst = mCurves.at( curveIds.at( 0 ).first ) ->endPoint();
     754                 :          0 :       QgsPoint startPointOfSecond = mCurves.at( curveIds.at( 1 ).first ) ->startPoint();
     755                 :          0 :       if ( endPointOfFirst != startPointOfSecond )
     756                 :            :       {
     757                 :          0 :         QgsLineString *line = new QgsLineString();
     758                 :          0 :         line->insertVertex( QgsVertexId( 0, 0, 0 ), endPointOfFirst );
     759                 :          0 :         line->insertVertex( QgsVertexId( 0, 0, 1 ), startPointOfSecond );
     760                 :          0 :         mCurves.insert( curveIds.at( 1 ).first, line );
     761                 :          0 :       }
     762                 :          0 :     }
     763                 :          2 :   }
     764                 :            : 
     765                 :          9 :   bool success = !curveIds.isEmpty();
     766                 :          9 :   if ( success )
     767                 :            :   {
     768                 :          6 :     clearCache(); //bbox changed
     769                 :          6 :   }
     770                 :          9 :   return success;
     771                 :          9 : }
     772                 :            : 
     773                 :        121 : QVector< QPair<int, QgsVertexId> > QgsCompoundCurve::curveVertexId( QgsVertexId id ) const
     774                 :            : {
     775                 :        121 :   QVector< QPair<int, QgsVertexId> > curveIds;
     776                 :            : 
     777                 :        121 :   int currentVertexIndex = 0;
     778                 :        154 :   for ( int i = 0; i < mCurves.size(); ++i )
     779                 :            :   {
     780                 :        129 :     int increment = mCurves.at( i )->numPoints() - 1;
     781                 :        129 :     if ( id.vertex >= currentVertexIndex && id.vertex <= currentVertexIndex + increment )
     782                 :            :     {
     783                 :         94 :       int curveVertexId = id.vertex - currentVertexIndex;
     784                 :         94 :       QgsVertexId vid;
     785                 :         94 :       vid.part = 0;
     786                 :         94 :       vid.ring = 0;
     787                 :         94 :       vid.vertex = curveVertexId;
     788                 :         94 :       curveIds.append( qMakePair( i, vid ) );
     789                 :         94 :       if ( curveVertexId == increment && i < ( mCurves.size() - 1 ) ) //add first vertex of next curve
     790                 :            :       {
     791                 :          9 :         vid.vertex = 0;
     792                 :          9 :         curveIds.append( qMakePair( i + 1, vid ) );
     793                 :          9 :       }
     794                 :         94 :       break;
     795                 :            :     }
     796                 :         35 :     else if ( id.vertex >= currentVertexIndex && id.vertex == currentVertexIndex + increment + 1 && i == ( mCurves.size() - 1 ) )
     797                 :            :     {
     798                 :          2 :       int curveVertexId = id.vertex - currentVertexIndex;
     799                 :          2 :       QgsVertexId vid;
     800                 :          2 :       vid.part = 0;
     801                 :          2 :       vid.ring = 0;
     802                 :          2 :       vid.vertex = curveVertexId;
     803                 :          2 :       curveIds.append( qMakePair( i, vid ) );
     804                 :          2 :       break;
     805                 :            :     }
     806                 :         33 :     currentVertexIndex += increment;
     807                 :         33 :   }
     808                 :            : 
     809                 :        121 :   return curveIds;
     810                 :        121 : }
     811                 :            : 
     812                 :         23 : double QgsCompoundCurve::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt,  QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
     813                 :            : {
     814                 :         23 :   return QgsGeometryUtils::closestSegmentFromComponents( mCurves, QgsGeometryUtils::Vertex, pt, segmentPt, vertexAfter, leftOf, epsilon );
     815                 :            : }
     816                 :            : 
     817                 :        177 : bool QgsCompoundCurve::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
     818                 :            : {
     819                 :        177 :   int currentVertexId = 0;
     820                 :        205 :   for ( int j = 0; j < mCurves.size(); ++j )
     821                 :            :   {
     822                 :        193 :     int nCurvePoints = mCurves.at( j )->numPoints();
     823                 :        193 :     if ( ( node - currentVertexId ) < nCurvePoints )
     824                 :            :     {
     825                 :        165 :       return ( mCurves.at( j )->pointAt( node - currentVertexId, point, type ) );
     826                 :            :     }
     827                 :         28 :     currentVertexId += ( nCurvePoints - 1 );
     828                 :         28 :   }
     829                 :         12 :   return false;
     830                 :        177 : }
     831                 :            : 
     832                 :         22 : double QgsCompoundCurve::xAt( int index ) const
     833                 :            : {
     834                 :         22 :   int currentVertexId = 0;
     835                 :         30 :   for ( int j = 0; j < mCurves.size(); ++j )
     836                 :            :   {
     837                 :         25 :     int nCurvePoints = mCurves.at( j )->numPoints();
     838                 :         25 :     if ( ( index - currentVertexId ) < nCurvePoints )
     839                 :            :     {
     840                 :         17 :       return mCurves.at( j )->xAt( index - currentVertexId );
     841                 :            :     }
     842                 :          8 :     currentVertexId += ( nCurvePoints - 1 );
     843                 :          8 :   }
     844                 :          5 :   return 0.0;
     845                 :         22 : }
     846                 :            : 
     847                 :         22 : double QgsCompoundCurve::yAt( int index ) const
     848                 :            : {
     849                 :         22 :   int currentVertexId = 0;
     850                 :         30 :   for ( int j = 0; j < mCurves.size(); ++j )
     851                 :            :   {
     852                 :         25 :     int nCurvePoints = mCurves.at( j )->numPoints();
     853                 :         25 :     if ( ( index - currentVertexId ) < nCurvePoints )
     854                 :            :     {
     855                 :         17 :       return mCurves.at( j )->yAt( index - currentVertexId );
     856                 :            :     }
     857                 :          8 :     currentVertexId += ( nCurvePoints - 1 );
     858                 :          8 :   }
     859                 :          5 :   return 0.0;
     860                 :         22 : }
     861                 :            : 
     862                 :          4 : bool QgsCompoundCurve::transform( QgsAbstractGeometryTransformer *transformer, QgsFeedback *feedback )
     863                 :            : {
     864                 :          4 :   bool res = true;
     865                 :          7 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
     866                 :            :   {
     867                 :          4 :     if ( !curve->transform( transformer ) )
     868                 :            :     {
     869                 :          1 :       res = false;
     870                 :          1 :       break;
     871                 :            :     }
     872                 :            : 
     873                 :          3 :     if ( feedback && feedback->isCanceled() )
     874                 :            :     {
     875                 :          0 :       res = false;
     876                 :          0 :       break;
     877                 :            :     }
     878                 :            :   }
     879                 :          4 :   clearCache();
     880                 :          4 :   return res;
     881                 :            : }
     882                 :            : 
     883                 :          3 : void QgsCompoundCurve::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
     884                 :            : {
     885                 :          6 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
     886                 :            :   {
     887                 :          3 :     curve->filterVertices( filter );
     888                 :            :   }
     889                 :          3 :   clearCache();
     890                 :          3 : }
     891                 :            : 
     892                 :          3 : void QgsCompoundCurve::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
     893                 :            : {
     894                 :          6 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
     895                 :            :   {
     896                 :          3 :     curve->transformVertices( transform );
     897                 :            :   }
     898                 :          3 :   clearCache();
     899                 :          3 : }
     900                 :            : 
     901                 :         10 : void QgsCompoundCurve::sumUpArea( double &sum ) const
     902                 :            : {
     903                 :         24 :   for ( const QgsCurve *curve : mCurves )
     904                 :            :   {
     905                 :         14 :     curve->sumUpArea( sum );
     906                 :            :   }
     907                 :         10 : }
     908                 :            : 
     909                 :          3 : void QgsCompoundCurve::close()
     910                 :            : {
     911                 :          3 :   if ( numPoints() < 1 || isClosed() )
     912                 :            :   {
     913                 :          2 :     return;
     914                 :            :   }
     915                 :          1 :   addVertex( startPoint() );
     916                 :          3 : }
     917                 :            : 
     918                 :          4 : bool QgsCompoundCurve::hasCurvedSegments() const
     919                 :            : {
     920                 :          5 :   for ( const QgsCurve *curve : mCurves )
     921                 :            :   {
     922                 :          3 :     if ( curve->hasCurvedSegments() )
     923                 :            :     {
     924                 :          2 :       return true;
     925                 :            :     }
     926                 :            :   }
     927                 :          2 :   return false;
     928                 :          4 : }
     929                 :            : 
     930                 :         48 : double QgsCompoundCurve::vertexAngle( QgsVertexId vertex ) const
     931                 :            : {
     932                 :         48 :   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( vertex );
     933                 :         48 :   if ( curveIds.size() == 1 )
     934                 :            :   {
     935                 :         43 :     QgsCurve *curve = mCurves[curveIds.at( 0 ).first];
     936                 :         43 :     return curve->vertexAngle( curveIds.at( 0 ).second );
     937                 :            :   }
     938                 :          5 :   else if ( curveIds.size() > 1 )
     939                 :            :   {
     940                 :          2 :     QgsCurve *curve1 = mCurves[curveIds.at( 0 ).first];
     941                 :          2 :     QgsCurve *curve2 = mCurves[curveIds.at( 1 ).first];
     942                 :          2 :     double angle1 = curve1->vertexAngle( curveIds.at( 0 ).second );
     943                 :          2 :     double angle2 = curve2->vertexAngle( curveIds.at( 1 ).second );
     944                 :          2 :     return QgsGeometryUtils::averageAngle( angle1, angle2 );
     945                 :            :   }
     946                 :            :   else
     947                 :            :   {
     948                 :          3 :     return 0.0;
     949                 :            :   }
     950                 :         48 : }
     951                 :            : 
     952                 :         34 : double QgsCompoundCurve::segmentLength( QgsVertexId startVertex ) const
     953                 :            : {
     954                 :         34 :   QVector< QPair<int, QgsVertexId> > curveIds = curveVertexId( startVertex );
     955                 :         34 :   double length = 0.0;
     956                 :         63 :   for ( auto it = curveIds.constBegin(); it != curveIds.constEnd(); ++it )
     957                 :            :   {
     958                 :         29 :     length += mCurves.at( it->first )->segmentLength( it->second );
     959                 :         29 :   }
     960                 :         34 :   return length;
     961                 :         34 : }
     962                 :            : 
     963                 :          2 : QgsCompoundCurve *QgsCompoundCurve::reversed() const
     964                 :            : {
     965                 :          2 :   QgsCompoundCurve *clone = new QgsCompoundCurve();
     966                 :          4 :   for ( int i = mCurves.count() - 1; i >= 0; --i )
     967                 :            :   {
     968                 :          2 :     QgsCurve *reversedCurve = mCurves.at( i )->reversed();
     969                 :          2 :     clone->addCurve( reversedCurve );
     970                 :          2 :   }
     971                 :          2 :   return clone;
     972                 :          0 : }
     973                 :            : 
     974                 :         11 : QgsPoint *QgsCompoundCurve::interpolatePoint( const double distance ) const
     975                 :            : {
     976                 :         11 :   if ( distance < 0 )
     977                 :          1 :     return nullptr;
     978                 :            : 
     979                 :         10 :   double distanceTraversed = 0;
     980                 :         14 :   for ( const QgsCurve *curve : mCurves )
     981                 :            :   {
     982                 :         12 :     const double thisCurveLength = curve->length();
     983                 :         12 :     if ( distanceTraversed + thisCurveLength > distance || qgsDoubleNear( distanceTraversed + thisCurveLength, distance ) )
     984                 :            :     {
     985                 :            :       // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
     986                 :          8 :       const double distanceToPoint = std::min( distance - distanceTraversed, thisCurveLength );
     987                 :            : 
     988                 :            :       // point falls on this curve
     989                 :          8 :       return curve->interpolatePoint( distanceToPoint );
     990                 :            :     }
     991                 :            : 
     992                 :          4 :     distanceTraversed += thisCurveLength;
     993                 :            :   }
     994                 :            : 
     995                 :          2 :   return nullptr;
     996                 :         11 : }
     997                 :            : 
     998                 :         13 : QgsCompoundCurve *QgsCompoundCurve::curveSubstring( double startDistance, double endDistance ) const
     999                 :            : {
    1000                 :         13 :   if ( startDistance < 0 && endDistance < 0 )
    1001                 :          1 :     return createEmptyWithSameType();
    1002                 :            : 
    1003                 :         12 :   endDistance = std::max( startDistance, endDistance );
    1004                 :         12 :   std::unique_ptr< QgsCompoundCurve > substring = std::make_unique< QgsCompoundCurve >();
    1005                 :            : 
    1006                 :         12 :   double distanceTraversed = 0;
    1007                 :         22 :   for ( const QgsCurve *curve : mCurves )
    1008                 :            :   {
    1009                 :         18 :     const double thisCurveLength = curve->length();
    1010                 :         18 :     if ( distanceTraversed + thisCurveLength < startDistance )
    1011                 :            :     {
    1012                 :            :       // keep going - haven't found start yet, so no need to include this curve at all
    1013                 :          2 :     }
    1014                 :            :     else
    1015                 :            :     {
    1016                 :         16 :       std::unique_ptr< QgsCurve > part( curve->curveSubstring( startDistance - distanceTraversed, endDistance - distanceTraversed ) );
    1017                 :         16 :       if ( part )
    1018                 :         16 :         substring->addCurve( part.release() );
    1019                 :         16 :     }
    1020                 :            : 
    1021                 :         18 :     distanceTraversed += thisCurveLength;
    1022                 :         18 :     if ( distanceTraversed > endDistance )
    1023                 :          8 :       break;
    1024                 :            :   }
    1025                 :            : 
    1026                 :         12 :   return substring.release();
    1027                 :         13 : }
    1028                 :            : 
    1029                 :         12 : bool QgsCompoundCurve::addZValue( double zValue )
    1030                 :            : {
    1031                 :         12 :   if ( QgsWkbTypes::hasZ( mWkbType ) )
    1032                 :          7 :     return false;
    1033                 :            : 
    1034                 :          5 :   mWkbType = QgsWkbTypes::addZ( mWkbType );
    1035                 :            : 
    1036                 :         12 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
    1037                 :            :   {
    1038                 :          7 :     curve->addZValue( zValue );
    1039                 :            :   }
    1040                 :          5 :   clearCache();
    1041                 :          5 :   return true;
    1042                 :         12 : }
    1043                 :            : 
    1044                 :         10 : bool QgsCompoundCurve::addMValue( double mValue )
    1045                 :            : {
    1046                 :         10 :   if ( QgsWkbTypes::hasM( mWkbType ) )
    1047                 :          6 :     return false;
    1048                 :            : 
    1049                 :          4 :   mWkbType = QgsWkbTypes::addM( mWkbType );
    1050                 :            : 
    1051                 :         11 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
    1052                 :            :   {
    1053                 :          7 :     curve->addMValue( mValue );
    1054                 :            :   }
    1055                 :          4 :   clearCache();
    1056                 :          4 :   return true;
    1057                 :         10 : }
    1058                 :            : 
    1059                 :          7 : bool QgsCompoundCurve::dropZValue()
    1060                 :            : {
    1061                 :          7 :   if ( !QgsWkbTypes::hasZ( mWkbType ) )
    1062                 :          4 :     return false;
    1063                 :            : 
    1064                 :          3 :   mWkbType = QgsWkbTypes::dropZ( mWkbType );
    1065                 :          8 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
    1066                 :            :   {
    1067                 :          5 :     curve->dropZValue();
    1068                 :            :   }
    1069                 :          3 :   clearCache();
    1070                 :          3 :   return true;
    1071                 :          7 : }
    1072                 :            : 
    1073                 :          8 : bool QgsCompoundCurve::dropMValue()
    1074                 :            : {
    1075                 :          8 :   if ( !QgsWkbTypes::hasM( mWkbType ) )
    1076                 :          5 :     return false;
    1077                 :            : 
    1078                 :          3 :   mWkbType = QgsWkbTypes::dropM( mWkbType );
    1079                 :          8 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
    1080                 :            :   {
    1081                 :          5 :     curve->dropMValue();
    1082                 :            :   }
    1083                 :          3 :   clearCache();
    1084                 :          3 :   return true;
    1085                 :          8 : }
    1086                 :            : 
    1087                 :          3 : void QgsCompoundCurve::swapXy()
    1088                 :            : {
    1089                 :          6 :   for ( QgsCurve *curve : std::as_const( mCurves ) )
    1090                 :            :   {
    1091                 :          3 :     curve->swapXy();
    1092                 :            :   }
    1093                 :          3 :   clearCache();
    1094                 :          3 : }

Generated by: LCOV version 1.14