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)