29using namespace Qt::StringLiterals;
33QString QgsPointsToPathsAlgorithm::name()
const
35 return u
"pointstopath"_s;
38QString QgsPointsToPathsAlgorithm::displayName()
const
40 return QObject::tr(
"Points to path" );
43QString QgsPointsToPathsAlgorithm::shortHelpString()
const
46 "This algorithm takes a point layer and connects its features to create a new line layer.\n\n"
47 "An attribute or expression may be specified to define the order the points should be connected. "
48 "If no order expression is specified, the feature ID is used.\n\n"
49 "A natural sort can be used when sorting by a string attribute "
50 "or expression (ie. place 'a9' before 'a10').\n\n"
51 "An attribute or expression can be selected to group points having the same value into the same resulting line."
55QString QgsPointsToPathsAlgorithm::shortDescription()
const
57 return QObject::tr(
"Takes a point layer and connects its features to create a new line layer." );
65QStringList QgsPointsToPathsAlgorithm::tags()
const
67 return QObject::tr(
"create,lines,points,connect,convert,join,path" ).split(
',' );
70QString QgsPointsToPathsAlgorithm::group()
const
72 return QObject::tr(
"Vector creation" );
75QString QgsPointsToPathsAlgorithm::groupId()
const
77 return u
"vectorcreation"_s;
80void QgsPointsToPathsAlgorithm::initAlgorithm(
const QVariantMap & )
83 addParameter( std::make_unique<QgsProcessingParameterBoolean>( u
"CLOSE_PATH"_s, QObject::tr(
"Create closed paths" ),
false ) );
84 addParameter( std::make_unique<QgsProcessingParameterExpression>( u
"ORDER_EXPRESSION"_s, QObject::tr(
"Order expression" ), QVariant(), u
"INPUT"_s,
true ) );
85 addParameter( std::make_unique<QgsProcessingParameterBoolean>( u
"NATURAL_SORT"_s, QObject::tr(
"Sort text containing numbers naturally" ),
false ) );
86 addParameter( std::make_unique<QgsProcessingParameterExpression>( u
"GROUP_EXPRESSION"_s, QObject::tr(
"Path group expression" ), QVariant(), u
"INPUT"_s,
true ) );
89 addParameter( std::make_unique<QgsProcessingParameterFolderDestination>( u
"OUTPUT_TEXT_DIR"_s, QObject::tr(
"Directory for text output" ), QVariant(),
true,
false ) );
90 addOutput( std::make_unique<QgsProcessingOutputNumber>( u
"NUM_PATHS"_s, QObject::tr(
"Number of paths" ) ) );
96 addParameter( std::move( orderField ) );
100 addParameter( std::move( groupField ) );
102 auto dateFormat = std::make_unique<QgsProcessingParameterString>( u
"DATE_FORMAT"_s, QObject::tr(
"Date format (if order field is DateTime)" ), QVariant(),
false,
true );
104 addParameter( std::move( dateFormat ) );
107QgsPointsToPathsAlgorithm *QgsPointsToPathsAlgorithm::createInstance()
const
109 return new QgsPointsToPathsAlgorithm();
114 QGS_MARK_ALGORITHM_SOURCE
116 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u
"INPUT"_s, context ) );
120 const bool closePaths = parameterAsBool( parameters, u
"CLOSE_PATH"_s, context );
122 QString orderExpressionString = parameterAsString( parameters, u
"ORDER_EXPRESSION"_s, context );
123 const QString orderFieldString = parameterAsString( parameters, u
"ORDER_FIELD"_s, context );
124 if ( !orderFieldString.isEmpty() )
129 QString dateFormat = parameterAsString( parameters, u
"DATE_FORMAT"_s, context );
130 if ( !dateFormat.isEmpty() )
132 QVector<QPair<QString, QString>> codeMap;
134 << QPair<QString, QString>(
"%%",
"%" )
135 << QPair<QString, QString>(
"%a",
"ddd" )
136 << QPair<QString, QString>(
"%A",
"dddd" )
137 << QPair<QString, QString>(
"%w",
"" )
138 << QPair<QString, QString>(
"%d",
"dd" )
139 << QPair<QString, QString>(
"%b",
"MMM" )
140 << QPair<QString, QString>(
"%B",
"MMMM" )
141 << QPair<QString, QString>(
"%m",
"MM" )
142 << QPair<QString, QString>(
"%y",
"yy" )
143 << QPair<QString, QString>(
"%Y",
"yyyy" )
144 << QPair<QString, QString>(
"%H",
"hh" )
145 << QPair<QString, QString>(
"%I",
"hh" )
146 << QPair<QString, QString>(
"%p",
"AP" )
147 << QPair<QString, QString>(
"%M",
"mm" )
148 << QPair<QString, QString>(
"%S",
"ss" )
149 << QPair<QString, QString>(
"%f",
"zzz" )
150 << QPair<QString, QString>(
"%z",
"" )
151 << QPair<QString, QString>(
"%Z",
"" )
152 << QPair<QString, QString>(
"%j",
"" )
153 << QPair<QString, QString>(
"%U",
"" )
154 << QPair<QString, QString>(
"%W",
"" )
155 << QPair<QString, QString>(
"%c",
"" )
156 << QPair<QString, QString>(
"%x",
"" )
157 << QPair<QString, QString>(
"%X",
"" )
158 << QPair<QString, QString>(
"%G",
"yyyy" )
159 << QPair<QString, QString>(
"%u",
"" )
160 << QPair<QString, QString>(
"%V",
"" );
161 for (
const auto &pair : std::as_const( codeMap ) )
163 dateFormat.replace( pair.first, pair.second );
165 orderExpressionString = QString(
"to_datetime(%1, '%2')" ).arg( orderExpressionString, dateFormat );
168 else if ( orderExpressionString.isEmpty() )
171 orderExpressionString = QString(
"$id" );
173 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
178 QStringList requiredFields = QStringList( orderExpression.
referencedColumns().values() );
179 orderExpression.
prepare( &expressionContext );
181 QMetaType::Type orderFieldType = QMetaType::Type::QString;
182 if ( orderExpression.
isField() )
184 const QString orderField = qgis::down_cast<const QgsExpressionNodeColumnRef *>( orderExpression.
rootNode() )->name();
185 const int orderFieldIndex = source->fields().lookupField( orderField );
186 if ( orderFieldIndex < 0 )
188 throw QgsProcessingException( QObject::tr(
"Order by field %1 does not exist in input layer." ).arg( orderField ) );
190 orderFieldType = source->fields().field( orderFieldIndex ).type();
193 QString groupExpressionString = parameterAsString( parameters, u
"GROUP_EXPRESSION"_s, context );
195 const QString groupFieldString = parameterAsString( parameters, u
"GROUP_FIELD"_s, context );
196 if ( !groupFieldString.isEmpty() )
204 if ( !groupExpressionString.isEmpty() )
206 requiredFields.append( groupExpression.referencedColumns().values() );
207 if ( groupExpression.isField() )
209 const QString groupField = qgis::down_cast<const QgsExpressionNodeColumnRef *>( groupExpression.rootNode() )->name();
210 const int groupFieldIndex = source->fields().lookupField( groupField );
211 if ( groupFieldIndex < 0 )
213 throw QgsProcessingException( QObject::tr(
"Group field %1 does not exist in input layer." ).arg( groupField ) );
215 outputFields.append( source->fields().field( groupFieldIndex ) );
219 outputFields.append( QgsField( u
"group"_s, QMetaType::QString ) );
223 outputFields.append(
QgsField(
"end", orderFieldType ) );
225 const bool naturalSort = parameterAsBool( parameters, u
"NATURAL_SORT"_s, context );
227 collator.setNumericMode(
true );
236 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, outputFields, wkbType, source->sourceCrs() ) );
240 const QString textDir = parameterAsString( parameters, u
"OUTPUT_TEXT_DIR"_s, context );
241 if ( !textDir.isEmpty() && !QDir().mkpath( textDir ) )
251 QHash<QVariant, QVector<QPair<QVariant, QgsPoint>>> allPoints;
256 const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
257 long currentPoint = 0;
265 feedback->
setProgress( 0.5 * currentPoint * totalPoints );
270 const QVariant orderValue = orderExpression.
evaluate( &expressionContext );
271 const QVariant groupValue = groupExpressionString.isEmpty() ? QVariant() : groupExpression.evaluate( &expressionContext );
273 if ( !allPoints.contains( groupValue ) )
274 allPoints[groupValue] = QVector<QPair<QVariant, QgsPoint>>();
278 const QgsMultiPoint mp( *qgsgeometry_cast<const QgsMultiPoint *>( geom ) );
279 for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
281 if ( const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( *pit ) )
283 allPoints[groupValue] << qMakePair( orderValue, *point );
291 allPoints[groupValue] << qMakePair( orderValue, *point );
300 QHashIterator<QVariant, QVector<QPair<QVariant, QgsPoint>>> hit( allPoints );
302 while ( hit.hasNext() )
309 QVector<QPair<QVariant, QgsPoint>> pairs = hit.value();
313 std::stable_sort( pairs.begin(), pairs.end(), [&collator](
const QPair<QVariant, QgsPoint> &pair1,
const QPair<QVariant, QgsPoint> &pair2 ) {
314 return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
319 std::stable_sort( pairs.begin(), pairs.end(), [](
const QPair<QVariant, QgsPoint> &pair1,
const QPair<QVariant, QgsPoint> &pair2 ) { return qgsVariantLessThan( pair1.first, pair2.first ); } );
323 QVector<QgsPoint> pathPoints;
324 for (
auto pit = pairs.constBegin(); pit != pairs.constEnd(); ++pit )
330 feedback->
setProgress( 50 + 0.5 * currentPoint * totalPoints );
331 pathPoints.append( pit->second );
334 if ( pathPoints.size() < 2 )
336 feedback->
pushInfo( QObject::tr(
"Skipping path with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
339 if ( closePaths && pathPoints.size() > 2 && pathPoints.constFirst() != pathPoints.constLast() )
340 pathPoints.append( pathPoints.constFirst() );
344 if ( !groupExpressionString.isEmpty() )
345 attrs.append( hit.key() );
346 attrs.append( pairs.first().first );
347 attrs.append( pairs.last().first );
355 if ( !textDir.isEmpty() )
357 const QString filename = QDir( textDir ).filePath( hit.key().toString() + QString(
".txt" ) );
358 QFile textFile( filename );
359 if ( !textFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
362 QTextStream out( &textFile );
365 "heading=Coordinate_System\n"
366 "dist_units=Default\n"
371 .arg( pathPoints.at( 0 ).x() )
372 .arg( pathPoints.at( 0 ).y() );
374 for (
int i = 1; i < pathPoints.size(); ++i )
376 const double angle = pathPoints.at( i - 1 ).azimuth( pathPoints.at( i ) );
380 distance = da.
measureLine( pathPoints.at( i - 1 ), pathPoints.at( i ) );
386 out << QString(
"%1;%2;90\n" ).arg( angle ).arg( distance );
397 outputs.insert( u
"OUTPUT"_s, dest );
398 outputs.insert( u
"NUM_PATHS"_s, pathCount );
399 if ( !textDir.isEmpty() )
401 outputs.insert( u
"OUTPUT_TEXT_DIR"_s, textDir );
@ VectorPoint
Vector point layers.
@ VectorLine
Vector line layers.
@ RespectsEllipsoid
Algorithm respects the context's ellipsoid settings, and uses ellipsoidal based measurements.
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
WkbType
The WKB type describes the number of dimensions a geometry has.
@ Hidden
Parameter is hidden and should not be shown to users.
Abstract base class for all geometries.
Custom exception class for Coordinate Reference System related exceptions.
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.
Handles 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).
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
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)
Fetch next feature and stores in f, returns true on success.
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
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, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
const QgsAbstractGeometry * constGet() const
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.
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.
Base class for providing feedback from a processing algorithm.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi 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).
T qgsgeometry_cast(QgsAbstractGeometry *geom)