LCOV - code coverage report
Current view: top level - analysis/processing - qgsalgorithmpointstopaths.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 229 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgsalgorithmdpointstopaths.cpp
       3                 :            :                          ---------------------
       4                 :            :     begin                : November 2020
       5                 :            :     copyright            : (C) 2020 by Stefanos Natsis
       6                 :            :     email                : uclaros at gmail dot com
       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 "qgsalgorithmpointstopaths.h"
      19                 :            : #include "qgsvectorlayer.h"
      20                 :            : #include "qgsmultipoint.h"
      21                 :            : 
      22                 :            : #include <QCollator>
      23                 :            : #include <QTextStream>
      24                 :            : 
      25                 :            : ///@cond PRIVATE
      26                 :            : 
      27                 :          0 : QString QgsPointsToPathsAlgorithm::name() const
      28                 :            : {
      29                 :          0 :   return QStringLiteral( "pointstopath" );
      30                 :            : }
      31                 :            : 
      32                 :          0 : QString QgsPointsToPathsAlgorithm::displayName() const
      33                 :            : {
      34                 :          0 :   return QObject::tr( "Points to path" );
      35                 :            : }
      36                 :            : 
      37                 :          0 : QString QgsPointsToPathsAlgorithm::shortHelpString() const
      38                 :            : {
      39                 :          0 :   return QObject::tr( "This algorithm takes a point layer and connects its features creating a new line layer.\n\n"
      40                 :            :                       "An attribute or expression may be specified to define the order the points should be connected. "
      41                 :            :                       "If no order expression is specified, the feature ID is used.\n\n"
      42                 :            :                       "A natural sort can be used when sorting by a string attribute "
      43                 :            :                       "or expression (ie. place 'a9' before 'a10').\n\n"
      44                 :            :                       "An attribute or expression can be selected to group points having the same value into the same resulting line." );
      45                 :            : }
      46                 :            : 
      47                 :          0 : QStringList QgsPointsToPathsAlgorithm::tags() const
      48                 :            : {
      49                 :          0 :   return QObject::tr( "create,lines,points,connect,convert,join" ).split( ',' );
      50                 :          0 : }
      51                 :            : 
      52                 :          0 : QString QgsPointsToPathsAlgorithm::group() const
      53                 :            : {
      54                 :          0 :   return QObject::tr( "Vector creation" );
      55                 :            : }
      56                 :            : 
      57                 :          0 : QString QgsPointsToPathsAlgorithm::groupId() const
      58                 :            : {
      59                 :          0 :   return QStringLiteral( "vectorcreation" );
      60                 :            : }
      61                 :            : 
      62                 :          0 : void QgsPointsToPathsAlgorithm::initAlgorithm( const QVariantMap & )
      63                 :            : {
      64                 :          0 :   addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
      65                 :          0 :                 QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorPoint ) );
      66                 :          0 :   addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CLOSE_PATH" ),
      67                 :          0 :                 QObject::tr( "Create closed paths" ), false, true ) );
      68                 :          0 :   addParameter( new QgsProcessingParameterExpression( QStringLiteral( "ORDER_EXPRESSION" ),
      69                 :          0 :                 QObject::tr( "Order expression" ), QVariant(), QStringLiteral( "INPUT" ), true ) );
      70                 :          0 :   addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "NATURAL_SORT" ),
      71                 :          0 :                 QObject::tr( "Sort text containing numbers naturally" ), false, true ) );
      72                 :          0 :   addParameter( new QgsProcessingParameterExpression( QStringLiteral( "GROUP_EXPRESSION" ),
      73                 :          0 :                 QObject::tr( "Path group expression" ), QVariant(), QStringLiteral( "INPUT" ), true ) );
      74                 :          0 :   addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ),
      75                 :          0 :                 QObject::tr( "Paths" ), QgsProcessing::TypeVectorLine ) );
      76                 :            :   // TODO QGIS 4: remove parameter. move logic to separate algorithm if needed.
      77                 :          0 :   addParameter( new QgsProcessingParameterFolderDestination( QStringLiteral( "OUTPUT_TEXT_DIR" ),
      78                 :          0 :                 QObject::tr( "Directory for text output" ), QVariant(), true, false ) );
      79                 :          0 :   addOutput( new QgsProcessingOutputNumber( QStringLiteral( "NUM_PATHS" ), QObject::tr( "Number of paths" ) ) );
      80                 :            : 
      81                 :            :   // backwards compatibility parameters
      82                 :            :   // TODO QGIS 4: remove compatibility parameters and their logic
      83                 :          0 :   QgsProcessingParameterField *orderField = new QgsProcessingParameterField( QStringLiteral( "ORDER_FIELD" ),
      84                 :          0 :       QObject::tr( "Order field" ), QVariant(), QString(), QgsProcessingParameterField::Any, false, true );
      85                 :          0 :   orderField->setFlags( orderField->flags() | QgsProcessingParameterDefinition::FlagHidden );
      86                 :          0 :   addParameter( orderField );
      87                 :          0 :   QgsProcessingParameterField *groupField = new QgsProcessingParameterField( QStringLiteral( "GROUP_FIELD" ),
      88                 :          0 :       QObject::tr( "Group field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true );
      89                 :          0 :   groupField->setFlags( orderField->flags() | QgsProcessingParameterDefinition::FlagHidden );
      90                 :          0 :   addParameter( groupField );
      91                 :          0 :   QgsProcessingParameterString *dateFormat = new QgsProcessingParameterString( QStringLiteral( "DATE_FORMAT" ),
      92                 :          0 :       QObject::tr( "Date format (if order field is DateTime)" ), QVariant(), false, true );
      93                 :          0 :   dateFormat->setFlags( orderField->flags() | QgsProcessingParameterDefinition::FlagHidden );
      94                 :          0 :   addParameter( dateFormat );
      95                 :          0 : }
      96                 :            : 
      97                 :          0 : QgsPointsToPathsAlgorithm *QgsPointsToPathsAlgorithm::createInstance() const
      98                 :            : {
      99                 :          0 :   return new QgsPointsToPathsAlgorithm();
     100                 :            : }
     101                 :            : 
     102                 :          0 : QVariantMap QgsPointsToPathsAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
     103                 :            : {
     104                 :          0 :   std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
     105                 :          0 :   if ( !source )
     106                 :          0 :     throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
     107                 :            : 
     108                 :          0 :   const bool closePaths = parameterAsBool( parameters, QStringLiteral( "CLOSE_PATH" ), context );
     109                 :            : 
     110                 :          0 :   QString orderExpressionString = parameterAsString( parameters, QStringLiteral( "ORDER_EXPRESSION" ), context );
     111                 :          0 :   const QString orderFieldString = parameterAsString( parameters, QStringLiteral( "ORDER_FIELD" ), context );
     112                 :          0 :   if ( ! orderFieldString.isEmpty() )
     113                 :            :   {
     114                 :            :     // this is a backwards compatibility parameter
     115                 :          0 :     orderExpressionString = QgsExpression::quotedColumnRef( orderFieldString );
     116                 :            : 
     117                 :          0 :     QString dateFormat = parameterAsString( parameters, QStringLiteral( "DATE_FORMAT" ), context );
     118                 :          0 :     if ( ! dateFormat.isEmpty() )
     119                 :            :     {
     120                 :          0 :       QVector< QPair< QString, QString > > codeMap;
     121                 :          0 :       codeMap << QPair< QString, QString >( "%%", "%" )
     122                 :          0 :               << QPair< QString, QString >( "%a", "ddd" )
     123                 :          0 :               << QPair< QString, QString >( "%A", "dddd" )
     124                 :          0 :               << QPair< QString, QString >( "%w", "" ) //day of the week 0-6
     125                 :          0 :               << QPair< QString, QString >( "%d", "dd" )
     126                 :          0 :               << QPair< QString, QString >( "%b", "MMM" )
     127                 :          0 :               << QPair< QString, QString >( "%B", "MMMM" )
     128                 :          0 :               << QPair< QString, QString >( "%m", "MM" )
     129                 :          0 :               << QPair< QString, QString >( "%y", "yy" )
     130                 :          0 :               << QPair< QString, QString >( "%Y", "yyyy" )
     131                 :          0 :               << QPair< QString, QString >( "%H", "hh" )
     132                 :          0 :               << QPair< QString, QString >( "%I", "hh" ) // 12 hour
     133                 :          0 :               << QPair< QString, QString >( "%p", "AP" )
     134                 :          0 :               << QPair< QString, QString >( "%M", "mm" )
     135                 :          0 :               << QPair< QString, QString >( "%S", "ss" )
     136                 :          0 :               << QPair< QString, QString >( "%f", "zzz" ) // milliseconds instead of microseconds
     137                 :          0 :               << QPair< QString, QString >( "%z", "" ) // utc offset
     138                 :          0 :               << QPair< QString, QString >( "%Z", "" ) // timezone name
     139                 :          0 :               << QPair< QString, QString >( "%j", "" ) // day of the year
     140                 :          0 :               << QPair< QString, QString >( "%U", "" ) // week number of the year sunday based
     141                 :          0 :               << QPair< QString, QString >( "%W", "" ) // week number of the year monday based
     142                 :          0 :               << QPair< QString, QString >( "%c", "" ) // full datetime
     143                 :          0 :               << QPair< QString, QString >( "%x", "" ) // full date
     144                 :          0 :               << QPair< QString, QString >( "%X", "" ) // full time
     145                 :          0 :               << QPair< QString, QString >( "%G", "yyyy" )
     146                 :          0 :               << QPair< QString, QString >( "%u", "" ) // day of the week 1-7
     147                 :          0 :               << QPair< QString, QString >( "%V", "" ); // week number
     148                 :          0 :       for ( const auto &pair : codeMap )
     149                 :            :       {
     150                 :          0 :         dateFormat.replace( pair.first, pair.second );
     151                 :            :       }
     152                 :          0 :       orderExpressionString = QString( "to_datetime(%1, '%2')" ).arg( orderExpressionString ).arg( dateFormat );
     153                 :          0 :     }
     154                 :          0 :   }
     155                 :          0 :   else if ( orderExpressionString.isEmpty() )
     156                 :            :   {
     157                 :            :     // If no order expression is given, default to the fid
     158                 :          0 :     orderExpressionString = QString( "$id" );
     159                 :          0 :   }
     160                 :          0 :   QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
     161                 :          0 :   QgsExpression orderExpression = QgsExpression( orderExpressionString );
     162                 :          0 :   if ( orderExpression.hasParserError() )
     163                 :          0 :     throw QgsProcessingException( orderExpression.parserErrorString() );
     164                 :            : 
     165                 :          0 :   QStringList requiredFields = QStringList( orderExpression.referencedColumns().values() );
     166                 :          0 :   orderExpression.prepare( &expressionContext );
     167                 :            : 
     168                 :          0 :   QVariant::Type orderFieldType = QVariant::String;
     169                 :          0 :   if ( orderExpression.isField() )
     170                 :            :   {
     171                 :          0 :     const int orderFieldIndex = source->fields().indexFromName( orderExpression.referencedColumns().values().first() );
     172                 :          0 :     orderFieldType = source->fields().field( orderFieldIndex ).type();
     173                 :          0 :   }
     174                 :            : 
     175                 :            : 
     176                 :          0 :   QString groupExpressionString = parameterAsString( parameters, QStringLiteral( "GROUP_EXPRESSION" ), context );
     177                 :            :   // handle backwards compatibility parameter GROUP_FIELD
     178                 :          0 :   const QString groupFieldString = parameterAsString( parameters, QStringLiteral( "GROUP_FIELD" ), context );
     179                 :          0 :   if ( ! groupFieldString.isEmpty() )
     180                 :          0 :     groupExpressionString = QgsExpression::quotedColumnRef( groupFieldString );
     181                 :            : 
     182                 :          0 :   QgsExpression groupExpression = groupExpressionString.isEmpty() ? QgsExpression( QString( "true" ) ) : QgsExpression( groupExpressionString );
     183                 :          0 :   if ( groupExpression.hasParserError() )
     184                 :          0 :     throw QgsProcessingException( groupExpression.parserErrorString() );
     185                 :            : 
     186                 :          0 :   QgsFields outputFields = QgsFields();
     187                 :          0 :   if ( ! groupExpressionString.isEmpty() )
     188                 :            :   {
     189                 :          0 :     requiredFields.append( groupExpression.referencedColumns().values() );
     190                 :          0 :     const QgsField field = groupExpression.isField() ? source->fields().field( requiredFields.last() ) : QStringLiteral( "group" );
     191                 :          0 :     outputFields.append( field );
     192                 :          0 :   }
     193                 :          0 :   outputFields.append( QgsField( "begin", orderFieldType ) );
     194                 :          0 :   outputFields.append( QgsField( "end", orderFieldType ) );
     195                 :            : 
     196                 :          0 :   const bool naturalSort = parameterAsBool( parameters, QStringLiteral( "NATURAL_SORT" ), context );
     197                 :          0 :   QCollator collator;
     198                 :          0 :   collator.setNumericMode( true );
     199                 :            : 
     200                 :          0 :   QgsWkbTypes::Type wkbType = QgsWkbTypes::LineString;
     201                 :          0 :   if ( QgsWkbTypes::hasM( source->wkbType() ) )
     202                 :          0 :     wkbType = QgsWkbTypes::addM( wkbType );
     203                 :          0 :   if ( QgsWkbTypes::hasZ( source->wkbType() ) )
     204                 :          0 :     wkbType = QgsWkbTypes::addZ( wkbType );
     205                 :            : 
     206                 :          0 :   QString dest;
     207                 :          0 :   std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outputFields, wkbType, source->sourceCrs() ) );
     208                 :          0 :   if ( !sink )
     209                 :          0 :     throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
     210                 :            : 
     211                 :          0 :   const QString textDir = parameterAsString( parameters, QStringLiteral( "OUTPUT_TEXT_DIR" ), context );
     212                 :          0 :   if ( ! textDir.isEmpty() &&
     213                 :          0 :        ! QDir( textDir ).exists() )
     214                 :          0 :     throw QgsProcessingException( QObject::tr( "The text output directory does not exist" ) );
     215                 :            : 
     216                 :          0 :   QgsDistanceArea da = QgsDistanceArea();
     217                 :          0 :   da.setSourceCrs( source->sourceCrs(), context.transformContext() );
     218                 :          0 :   da.setEllipsoid( context.ellipsoid() );
     219                 :            : 
     220                 :            :   // Store the points in a hash with the group identifier as the key
     221                 :          0 :   QHash< QVariant, QVector< QPair< QVariant, QgsPoint > > > allPoints;
     222                 :            : 
     223                 :          0 :   QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( requiredFields, source->fields() );
     224                 :          0 :   QgsFeatureIterator fit = source->getFeatures( request, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
     225                 :          0 :   QgsFeature f;
     226                 :          0 :   const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
     227                 :          0 :   long currentPoint = 0;
     228                 :          0 :   feedback->setProgressText( QObject::tr( "Loading points…" ) );
     229                 :          0 :   while ( fit.nextFeature( f ) )
     230                 :            :   {
     231                 :          0 :     if ( feedback->isCanceled() )
     232                 :            :     {
     233                 :          0 :       break;
     234                 :            :     }
     235                 :          0 :     feedback->setProgress( 0.5 * currentPoint * totalPoints );
     236                 :            : 
     237                 :          0 :     if ( f.hasGeometry() )
     238                 :            :     {
     239                 :          0 :       expressionContext.setFeature( f );
     240                 :          0 :       const QVariant orderValue = orderExpression.evaluate( &expressionContext );
     241                 :          0 :       const QVariant groupValue = groupExpressionString.isEmpty() ? QVariant() : groupExpression.evaluate( &expressionContext );
     242                 :            : 
     243                 :          0 :       if ( ! allPoints.contains( groupValue ) )
     244                 :          0 :         allPoints[ groupValue ] = QVector< QPair< QVariant, QgsPoint > >();
     245                 :          0 :       const QgsAbstractGeometry *geom = f.geometry().constGet();
     246                 :          0 :       if ( QgsWkbTypes::isMultiType( geom->wkbType() ) )
     247                 :            :       {
     248                 :          0 :         QgsMultiPoint mp( *qgsgeometry_cast< const QgsMultiPoint * >( geom ) );
     249                 :          0 :         for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
     250                 :            :         {
     251                 :          0 :           QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( *pit ) );
     252                 :          0 :           allPoints[ groupValue ] << qMakePair( orderValue, point );
     253                 :          0 :         }
     254                 :          0 :       }
     255                 :            :       else
     256                 :            :       {
     257                 :          0 :         QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( geom ) );
     258                 :          0 :         allPoints[ groupValue ] << qMakePair( orderValue, point );
     259                 :          0 :       }
     260                 :          0 :     }
     261                 :          0 :     ++currentPoint;
     262                 :            :   }
     263                 :            : 
     264                 :          0 :   int pathCount = 0;
     265                 :          0 :   currentPoint = 0;
     266                 :          0 :   QHashIterator< QVariant, QVector< QPair< QVariant, QgsPoint > > > hit( allPoints );
     267                 :          0 :   feedback->setProgressText( QObject::tr( "Creating paths…" ) );
     268                 :          0 :   while ( hit.hasNext() )
     269                 :            :   {
     270                 :          0 :     hit.next();
     271                 :          0 :     if ( feedback->isCanceled() )
     272                 :            :     {
     273                 :          0 :       break;
     274                 :            :     }
     275                 :          0 :     auto pairs = hit.value();
     276                 :            : 
     277                 :          0 :     if ( naturalSort )
     278                 :            :     {
     279                 :          0 :       std::stable_sort( pairs.begin(),
     280                 :          0 :                         pairs.end(),
     281                 :          0 :                         [&collator]( const QPair< const QVariant, QgsPoint > &pair1,
     282                 :            :                                      const QPair< const QVariant, QgsPoint > &pair2 )
     283                 :            :       {
     284                 :          0 :         return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
     285                 :          0 :       } );
     286                 :          0 :     }
     287                 :            :     else
     288                 :            :     {
     289                 :          0 :       std::stable_sort( pairs.begin(),
     290                 :          0 :                         pairs.end(),
     291                 :          0 :                         []( const QPair< const QVariant, QgsPoint > &pair1,
     292                 :            :                             const QPair< const QVariant, QgsPoint > &pair2 )
     293                 :            :       {
     294                 :          0 :         return qgsVariantLessThan( pair1.first, pair2.first );
     295                 :            :       } );
     296                 :            :     }
     297                 :            : 
     298                 :            : 
     299                 :          0 :     QVector<QgsPoint> pathPoints;
     300                 :          0 :     for ( auto pit = pairs.constBegin(); pit != pairs.constEnd(); ++pit )
     301                 :            :     {
     302                 :          0 :       if ( feedback->isCanceled() )
     303                 :            :       {
     304                 :          0 :         break;
     305                 :            :       }
     306                 :          0 :       feedback->setProgress( 50 + 0.5 * currentPoint * totalPoints );
     307                 :          0 :       pathPoints.append( pit->second );
     308                 :          0 :       ++currentPoint;
     309                 :          0 :     }
     310                 :          0 :     if ( pathPoints.size() < 2 )
     311                 :            :     {
     312                 :          0 :       feedback->pushInfo( QObject::tr( "Skipping path with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
     313                 :          0 :       continue;
     314                 :            :     }
     315                 :          0 :     if ( closePaths && pathPoints.size() > 2 && pathPoints.constFirst() != pathPoints.constLast() )
     316                 :          0 :       pathPoints.append( pathPoints.constFirst() );
     317                 :            : 
     318                 :          0 :     QgsFeature outputFeature;
     319                 :          0 :     QgsAttributes attrs;
     320                 :          0 :     if ( ! groupExpressionString.isEmpty() )
     321                 :          0 :       attrs.append( hit.key() );
     322                 :          0 :     attrs.append( hit.value().first().first );
     323                 :          0 :     attrs.append( hit.value().last().first );
     324                 :          0 :     outputFeature.setGeometry( QgsGeometry::fromPolyline( pathPoints ) );
     325                 :          0 :     outputFeature.setAttributes( attrs );
     326                 :          0 :     sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
     327                 :            : 
     328                 :          0 :     if ( ! textDir.isEmpty() )
     329                 :            :     {
     330                 :          0 :       const QString filename = QDir( textDir ).filePath( hit.key().toString() + QString( ".txt" ) );
     331                 :          0 :       QFile textFile( filename );
     332                 :          0 :       if ( !textFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
     333                 :          0 :         throw QgsProcessingException( QObject::tr( "Cannot open file for writing " ) + filename );
     334                 :            : 
     335                 :          0 :       QTextStream out( &textFile );
     336                 :          0 :       out << QString( "angle=Azimuth\n"
     337                 :            :                       "heading=Coordinate_System\n"
     338                 :            :                       "dist_units=Default\n"
     339                 :            :                       "startAt=%1;%2;90\n"
     340                 :            :                       "survey=Polygonal\n"
     341                 :          0 :                       "[data]\n" ).arg( pathPoints.at( 0 ).x() ).arg( pathPoints.at( 0 ).y() );
     342                 :            : 
     343                 :          0 :       for ( int i = 1; i < pathPoints.size(); ++i )
     344                 :            :       {
     345                 :          0 :         const double angle = pathPoints.at( i - 1 ).azimuth( pathPoints.at( i ) );
     346                 :          0 :         const double distance = da.measureLine( pathPoints.at( i - 1 ), pathPoints.at( i ) );
     347                 :          0 :         out << QString( "%1;%2;90\n" ).arg( angle ).arg( distance );
     348                 :          0 :       }
     349                 :          0 :     }
     350                 :            : 
     351                 :          0 :     ++pathCount;
     352                 :          0 :   }
     353                 :            : 
     354                 :            : 
     355                 :          0 :   QVariantMap outputs;
     356                 :          0 :   outputs.insert( QStringLiteral( "OUTPUT" ), dest );
     357                 :          0 :   outputs.insert( QStringLiteral( "NUM_PATHS" ), pathCount );
     358                 :          0 :   return outputs;
     359                 :          0 : }
     360                 :            : 
     361                 :            : ///@endcond

Generated by: LCOV version 1.14