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