23 #include <QTextStream> 
   27 QString QgsPointsToPathsAlgorithm::name()
 const 
   29   return QStringLiteral( 
"pointstopath" );
 
   32 QString QgsPointsToPathsAlgorithm::displayName()
 const 
   34   return QObject::tr( 
"Points to path" );
 
   37 QString QgsPointsToPathsAlgorithm::shortHelpString()
 const 
   39   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." );
 
   47 QStringList QgsPointsToPathsAlgorithm::tags()
 const 
   49   return QObject::tr( 
"create,lines,points,connect,convert,join,path" ).split( 
',' );
 
   52 QString QgsPointsToPathsAlgorithm::group()
 const 
   54   return QObject::tr( 
"Vector creation" );
 
   57 QString QgsPointsToPathsAlgorithm::groupId()
 const 
   59   return QStringLiteral( 
"vectorcreation" );
 
   62 void QgsPointsToPathsAlgorithm::initAlgorithm( 
const QVariantMap & )
 
   67                 QObject::tr( 
"Create closed paths" ), 
false, 
true ) );
 
   69                 QObject::tr( 
"Order expression" ), QVariant(), QStringLiteral( 
"INPUT" ), 
true ) );
 
   71                 QObject::tr( 
"Sort text containing numbers naturally" ), 
false, 
true ) );
 
   73                 QObject::tr( 
"Path group expression" ), QVariant(), QStringLiteral( 
"INPUT" ), 
true ) );
 
   78                 QObject::tr( 
"Directory for text output" ), QVariant(), 
true, 
false ) );
 
   86   addParameter( orderField );
 
   90   addParameter( groupField );
 
   92       QObject::tr( 
"Date format (if order field is DateTime)" ), QVariant(), 
false, 
true );
 
   94   addParameter( dateFormat );
 
   97 QgsPointsToPathsAlgorithm *QgsPointsToPathsAlgorithm::createInstance()
 const 
   99   return new QgsPointsToPathsAlgorithm();
 
  104   std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( 
"INPUT" ), context ) );
 
  108   const bool closePaths = parameterAsBool( parameters, QStringLiteral( 
"CLOSE_PATH" ), context );
 
  110   QString orderExpressionString = parameterAsString( parameters, QStringLiteral( 
"ORDER_EXPRESSION" ), context );
 
  111   const QString orderFieldString = parameterAsString( parameters, QStringLiteral( 
"ORDER_FIELD" ), context );
 
  112   if ( ! orderFieldString.isEmpty() )
 
  117     QString dateFormat = parameterAsString( parameters, QStringLiteral( 
"DATE_FORMAT" ), context );
 
  118     if ( ! dateFormat.isEmpty() )
 
  120       QVector< QPair< QString, QString > > codeMap;
 
  121       codeMap << QPair< QString, QString >( 
"%%", 
"%" )
 
  122               << QPair< QString, QString >( 
"%a", 
"ddd" )
 
  123               << QPair< QString, QString >( 
"%A", 
"dddd" )
 
  124               << QPair< QString, QString >( 
"%w", 
"" ) 
 
  125               << QPair< QString, QString >( 
"%d", 
"dd" )
 
  126               << QPair< QString, QString >( 
"%b", 
"MMM" )
 
  127               << QPair< QString, QString >( 
"%B", 
"MMMM" )
 
  128               << QPair< QString, QString >( 
"%m", 
"MM" )
 
  129               << QPair< QString, QString >( 
"%y", 
"yy" )
 
  130               << QPair< QString, QString >( 
"%Y", 
"yyyy" )
 
  131               << QPair< QString, QString >( 
"%H", 
"hh" )
 
  132               << QPair< QString, QString >( 
"%I", 
"hh" ) 
 
  133               << QPair< QString, QString >( 
"%p", 
"AP" )
 
  134               << QPair< QString, QString >( 
"%M", 
"mm" )
 
  135               << QPair< QString, QString >( 
"%S", 
"ss" )
 
  136               << QPair< QString, QString >( 
"%f", 
"zzz" ) 
 
  137               << QPair< QString, QString >( 
"%z", 
"" ) 
 
  138               << QPair< QString, QString >( 
"%Z", 
"" ) 
 
  139               << QPair< QString, QString >( 
"%j", 
"" ) 
 
  140               << QPair< QString, QString >( 
"%U", 
"" ) 
 
  141               << QPair< QString, QString >( 
"%W", 
"" ) 
 
  142               << QPair< QString, QString >( 
"%c", 
"" ) 
 
  143               << QPair< QString, QString >( 
"%x", 
"" ) 
 
  144               << QPair< QString, QString >( 
"%X", 
"" ) 
 
  145               << QPair< QString, QString >( 
"%G", 
"yyyy" )
 
  146               << QPair< QString, QString >( 
"%u", 
"" ) 
 
  147               << QPair< QString, QString >( 
"%V", 
"" ); 
 
  148       for ( 
const auto &pair : codeMap )
 
  150         dateFormat.replace( pair.first, pair.second );
 
  152       orderExpressionString = QString( 
"to_datetime(%1, '%2')" ).arg( orderExpressionString ).arg( dateFormat );
 
  155   else if ( orderExpressionString.isEmpty() )
 
  158     orderExpressionString = QString( 
"$id" );
 
  160   QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
 
  165   QStringList requiredFields = QStringList( orderExpression.
referencedColumns().values() );
 
  166   orderExpression.
prepare( &expressionContext );
 
  168   QVariant::Type orderFieldType = QVariant::String;
 
  169   if ( orderExpression.
isField() )
 
  171     const int orderFieldIndex = source->fields().indexFromName( orderExpression.
referencedColumns().values().first() );
 
  172     orderFieldType = source->fields().field( orderFieldIndex ).type();
 
  176   QString groupExpressionString = parameterAsString( parameters, QStringLiteral( 
"GROUP_EXPRESSION" ), context );
 
  178   const QString groupFieldString = parameterAsString( parameters, QStringLiteral( 
"GROUP_FIELD" ), context );
 
  179   if ( ! groupFieldString.isEmpty() )
 
  187   if ( ! groupExpressionString.isEmpty() )
 
  190     const QgsField field = groupExpression.
isField() ? source->fields().field( requiredFields.last() ) : QStringLiteral( 
"group" );
 
  196   const bool naturalSort = parameterAsBool( parameters, QStringLiteral( 
"NATURAL_SORT" ), context );
 
  198   collator.setNumericMode( 
true );
 
  207   std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( 
"OUTPUT" ), context, dest, outputFields, wkbType, source->sourceCrs() ) );
 
  211   const QString textDir = parameterAsString( parameters, QStringLiteral( 
"OUTPUT_TEXT_DIR" ), context );
 
  212   if ( ! textDir.isEmpty() &&
 
  213        ! QDir( textDir ).exists() )
 
  221   QHash< QVariant, QVector< QPair< QVariant, QgsPoint > > > allPoints;
 
  226   const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
 
  227   long currentPoint = 0;
 
  235     feedback->
setProgress( 0.5 * currentPoint * totalPoints );
 
  240       const QVariant orderValue = orderExpression.
evaluate( &expressionContext );
 
  241       const QVariant groupValue = groupExpressionString.isEmpty() ? QVariant() : groupExpression.evaluate( &expressionContext );
 
  243       if ( ! allPoints.contains( groupValue ) )
 
  244         allPoints[ groupValue ] = QVector< QPair< QVariant, QgsPoint > >();
 
  248         QgsMultiPoint mp( *qgsgeometry_cast< const QgsMultiPoint * >( geom ) );
 
  249         for ( 
auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
 
  251           QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( *pit ) );
 
  252           allPoints[ groupValue ] << qMakePair( orderValue, point );
 
  257         QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( geom ) );
 
  258         allPoints[ groupValue ] << qMakePair( orderValue, point );
 
  266   QHashIterator< QVariant, QVector< QPair< QVariant, QgsPoint > > > hit( allPoints );
 
  268   while ( hit.hasNext() )
 
  275     auto pairs = hit.value();
 
  279       std::stable_sort( pairs.begin(),
 
  281                         [&collator]( 
const QPair< const QVariant, QgsPoint > &pair1,
 
  282                                      const QPair< const QVariant, QgsPoint > &pair2 )
 
  284         return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
 
  289       std::stable_sort( pairs.begin(),
 
  291                         []( 
const QPair< const QVariant, QgsPoint > &pair1,
 
  292                             const QPair< const QVariant, QgsPoint > &pair2 )
 
  294         return qgsVariantLessThan( pair1.first, pair2.first );
 
  299     QVector<QgsPoint> pathPoints;
 
  300     for ( 
auto pit = pairs.constBegin(); pit != pairs.constEnd(); ++pit )
 
  306       feedback->
setProgress( 50 + 0.5 * currentPoint * totalPoints );
 
  307       pathPoints.append( pit->second );
 
  310     if ( pathPoints.size() < 2 )
 
  312       feedback->
pushInfo( QObject::tr( 
"Skipping path with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
 
  315     if ( closePaths && pathPoints.size() > 2 && pathPoints.constFirst() != pathPoints.constLast() )
 
  316       pathPoints.append( pathPoints.constFirst() );
 
  320     if ( ! groupExpressionString.isEmpty() )
 
  321       attrs.append( hit.key() );
 
  322     attrs.append( hit.value().first().first );
 
  323     attrs.append( hit.value().last().first );
 
  328     if ( ! textDir.isEmpty() )
 
  330       const QString filename = QDir( textDir ).filePath( hit.key().toString() + QString( 
".txt" ) );
 
  331       QFile textFile( filename );
 
  332       if ( !textFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
 
  335       QTextStream out( &textFile );
 
  336 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 
  337       out.setCodec( 
"UTF-8" );
 
  339       out << QString( 
"angle=Azimuth\n" 
  340                       "heading=Coordinate_System\n" 
  341                       "dist_units=Default\n" 
  344                       "[data]\n" ).arg( pathPoints.at( 0 ).x() ).arg( pathPoints.at( 0 ).y() );
 
  346       for ( 
int i = 1; i < pathPoints.size(); ++i )
 
  348         const double angle = pathPoints.at( i - 1 ).azimuth( pathPoints.at( i ) );
 
  349         const double distance = da.
measureLine( pathPoints.at( i - 1 ), pathPoints.at( i ) );
 
  350         out << QString( 
"%1;%2;90\n" ).arg( 
angle ).arg( distance );
 
  359   outputs.insert( QStringLiteral( 
"OUTPUT" ), dest );
 
  360   outputs.insert( QStringLiteral( 
"NUM_PATHS" ), pathCount );
 
Abstract base class for all geometries.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString parserErrorString() const
Returns parser error.
bool isField() const
Checks whether an expression consists only of a single field reference.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
Encapsulate a field in an attribute table or data source.
Container of fields for a vector layer.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromPolyline(const QgsPolyline &polyline)
Creates a new LineString geometry from a list of QgsPoint points.
Multi point geometry collection.
Point geometry type, with support for z-dimension and m-values.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
QString ellipsoid() const
Returns the ellipsoid to use for distance and area calculations.
Custom exception class for processing related exceptions.
@ FlagSkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
A numeric output for processing algorithms.
A boolean parameter for processing algorithms.
@ FlagHidden
Parameter is hidden and should not be shown to users.
Flags flags() const
Returns any flags associated with the parameter.
void setFlags(Flags flags)
Sets the flags associated with the parameter.
An expression parameter for processing algorithms.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
A folder destination parameter, for specifying the destination path for a folder created by the algor...
A string parameter for processing algorithms.
@ TypeVectorLine
Vector line layers.
@ TypeVectorPoint
Vector point layers.
static bool isMultiType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Type
The WKB type describes the number of dimensions a geometry has.
static Type addZ(Type type) SIP_HOLDGIL
Adds the z dimension to a WKB type and returns the new type.
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
static Type addM(Type type) SIP_HOLDGIL
Adds the m dimension to a WKB type and returns the new type.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)