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 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u
"INPUT"_s, context ) );
118 const bool closePaths = parameterAsBool( parameters, u
"CLOSE_PATH"_s, context );
120 QString orderExpressionString = parameterAsString( parameters, u
"ORDER_EXPRESSION"_s, context );
121 const QString orderFieldString = parameterAsString( parameters, u
"ORDER_FIELD"_s, context );
122 if ( !orderFieldString.isEmpty() )
127 QString dateFormat = parameterAsString( parameters, u
"DATE_FORMAT"_s, context );
128 if ( !dateFormat.isEmpty() )
130 QVector<QPair<QString, QString>> codeMap;
132 << QPair<QString, QString>(
"%%",
"%" )
133 << QPair<QString, QString>(
"%a",
"ddd" )
134 << QPair<QString, QString>(
"%A",
"dddd" )
135 << QPair<QString, QString>(
"%w",
"" )
136 << QPair<QString, QString>(
"%d",
"dd" )
137 << QPair<QString, QString>(
"%b",
"MMM" )
138 << QPair<QString, QString>(
"%B",
"MMMM" )
139 << QPair<QString, QString>(
"%m",
"MM" )
140 << QPair<QString, QString>(
"%y",
"yy" )
141 << QPair<QString, QString>(
"%Y",
"yyyy" )
142 << QPair<QString, QString>(
"%H",
"hh" )
143 << QPair<QString, QString>(
"%I",
"hh" )
144 << QPair<QString, QString>(
"%p",
"AP" )
145 << QPair<QString, QString>(
"%M",
"mm" )
146 << QPair<QString, QString>(
"%S",
"ss" )
147 << QPair<QString, QString>(
"%f",
"zzz" )
148 << QPair<QString, QString>(
"%z",
"" )
149 << QPair<QString, QString>(
"%Z",
"" )
150 << QPair<QString, QString>(
"%j",
"" )
151 << QPair<QString, QString>(
"%U",
"" )
152 << QPair<QString, QString>(
"%W",
"" )
153 << QPair<QString, QString>(
"%c",
"" )
154 << QPair<QString, QString>(
"%x",
"" )
155 << QPair<QString, QString>(
"%X",
"" )
156 << QPair<QString, QString>(
"%G",
"yyyy" )
157 << QPair<QString, QString>(
"%u",
"" )
158 << QPair<QString, QString>(
"%V",
"" );
159 for (
const auto &pair : std::as_const( codeMap ) )
161 dateFormat.replace( pair.first, pair.second );
163 orderExpressionString = QString(
"to_datetime(%1, '%2')" ).arg( orderExpressionString, dateFormat );
166 else if ( orderExpressionString.isEmpty() )
169 orderExpressionString = QString(
"$id" );
171 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
176 QStringList requiredFields = QStringList( orderExpression.
referencedColumns().values() );
177 orderExpression.
prepare( &expressionContext );
179 QMetaType::Type orderFieldType = QMetaType::Type::QString;
180 if ( orderExpression.
isField() )
182 const QString orderField = qgis::down_cast<const QgsExpressionNodeColumnRef *>( orderExpression.
rootNode() )->name();
183 const int orderFieldIndex = source->fields().lookupField( orderField );
184 if ( orderFieldIndex < 0 )
186 throw QgsProcessingException( QObject::tr(
"Order by field %1 does not exist in input layer." ).arg( orderField ) );
188 orderFieldType = source->fields().field( orderFieldIndex ).type();
191 QString groupExpressionString = parameterAsString( parameters, u
"GROUP_EXPRESSION"_s, context );
193 const QString groupFieldString = parameterAsString( parameters, u
"GROUP_FIELD"_s, context );
194 if ( !groupFieldString.isEmpty() )
202 if ( !groupExpressionString.isEmpty() )
204 requiredFields.append( groupExpression.referencedColumns().values() );
205 if ( groupExpression.isField() )
207 const QString groupField = qgis::down_cast<const QgsExpressionNodeColumnRef *>( groupExpression.rootNode() )->name();
208 const int groupFieldIndex = source->fields().lookupField( groupField );
209 if ( groupFieldIndex < 0 )
211 throw QgsProcessingException( QObject::tr(
"Group field %1 does not exist in input layer." ).arg( groupField ) );
213 outputFields.append( source->fields().field( groupFieldIndex ) );
217 outputFields.append( QgsField( u
"group"_s, QMetaType::QString ) );
221 outputFields.append(
QgsField(
"end", orderFieldType ) );
223 const bool naturalSort = parameterAsBool( parameters, u
"NATURAL_SORT"_s, context );
225 collator.setNumericMode(
true );
234 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u
"OUTPUT"_s, context, dest, outputFields, wkbType, source->sourceCrs() ) );
238 const QString textDir = parameterAsString( parameters, u
"OUTPUT_TEXT_DIR"_s, context );
239 if ( !textDir.isEmpty() && !QDir().mkpath( textDir ) )
249 QHash<QVariant, QVector<QPair<QVariant, QgsPoint>>> allPoints;
254 const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
255 long currentPoint = 0;
263 feedback->
setProgress( 0.5 * currentPoint * totalPoints );
268 const QVariant orderValue = orderExpression.
evaluate( &expressionContext );
269 const QVariant groupValue = groupExpressionString.isEmpty() ? QVariant() : groupExpression.evaluate( &expressionContext );
271 if ( !allPoints.contains( groupValue ) )
272 allPoints[groupValue] = QVector<QPair<QVariant, QgsPoint>>();
276 const QgsMultiPoint mp( *qgsgeometry_cast<const QgsMultiPoint *>( geom ) );
277 for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
279 if ( const QgsPoint *point = qgsgeometry_cast<const QgsPoint *>( *pit ) )
281 allPoints[groupValue] << qMakePair( orderValue, *point );
289 allPoints[groupValue] << qMakePair( orderValue, *point );
298 QHashIterator<QVariant, QVector<QPair<QVariant, QgsPoint>>> hit( allPoints );
300 while ( hit.hasNext() )
307 QVector<QPair<QVariant, QgsPoint>> pairs = hit.value();
311 std::stable_sort( pairs.begin(), pairs.end(), [&collator](
const QPair<QVariant, QgsPoint> &pair1,
const QPair<QVariant, QgsPoint> &pair2 ) {
312 return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
317 std::stable_sort( pairs.begin(), pairs.end(), [](
const QPair<QVariant, QgsPoint> &pair1,
const QPair<QVariant, QgsPoint> &pair2 ) { return qgsVariantLessThan( pair1.first, pair2.first ); } );
321 QVector<QgsPoint> pathPoints;
322 for (
auto pit = pairs.constBegin(); pit != pairs.constEnd(); ++pit )
328 feedback->
setProgress( 50 + 0.5 * currentPoint * totalPoints );
329 pathPoints.append( pit->second );
332 if ( pathPoints.size() < 2 )
334 feedback->
pushInfo( QObject::tr(
"Skipping path with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
337 if ( closePaths && pathPoints.size() > 2 && pathPoints.constFirst() != pathPoints.constLast() )
338 pathPoints.append( pathPoints.constFirst() );
342 if ( !groupExpressionString.isEmpty() )
343 attrs.append( hit.key() );
344 attrs.append( pairs.first().first );
345 attrs.append( pairs.last().first );
351 if ( !textDir.isEmpty() )
353 const QString filename = QDir( textDir ).filePath( hit.key().toString() + QString(
".txt" ) );
354 QFile textFile( filename );
355 if ( !textFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
358 QTextStream out( &textFile );
361 "heading=Coordinate_System\n"
362 "dist_units=Default\n"
367 .arg( pathPoints.at( 0 ).x() )
368 .arg( pathPoints.at( 0 ).y() );
370 for (
int i = 1; i < pathPoints.size(); ++i )
372 const double angle = pathPoints.at( i - 1 ).azimuth( pathPoints.at( i ) );
376 distance = da.
measureLine( pathPoints.at( i - 1 ), pathPoints.at( i ) );
382 out << QString(
"%1;%2;90\n" ).arg( angle ).arg( distance );
392 outputs.insert( u
"OUTPUT"_s, dest );
393 outputs.insert( u
"NUM_PATHS"_s, pathCount );
394 if ( !textDir.isEmpty() )
396 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.
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.
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)