24 #include <QTextStream>
28 QString QgsPointsToPathsAlgorithm::name()
const
30 return QStringLiteral(
"pointstopath" );
33 QString QgsPointsToPathsAlgorithm::displayName()
const
35 return QObject::tr(
"Points to path" );
38 QString QgsPointsToPathsAlgorithm::shortHelpString()
const
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." );
48 QStringList QgsPointsToPathsAlgorithm::tags()
const
50 return QObject::tr(
"create,lines,points,connect,convert,join,path" ).split(
',' );
53 QString QgsPointsToPathsAlgorithm::group()
const
55 return QObject::tr(
"Vector creation" );
58 QString QgsPointsToPathsAlgorithm::groupId()
const
60 return QStringLiteral(
"vectorcreation" );
63 void QgsPointsToPathsAlgorithm::initAlgorithm(
const QVariantMap & )
68 QObject::tr(
"Create closed paths" ),
false,
true ) );
70 QObject::tr(
"Order expression" ), QVariant(), QStringLiteral(
"INPUT" ),
true ) );
72 QObject::tr(
"Sort text containing numbers naturally" ),
false,
true ) );
74 QObject::tr(
"Path group expression" ), QVariant(), QStringLiteral(
"INPUT" ),
true ) );
79 QObject::tr(
"Directory for text output" ), QVariant(),
true,
false ) );
87 addParameter( orderField );
91 addParameter( groupField );
93 QObject::tr(
"Date format (if order field is DateTime)" ), QVariant(),
false,
true );
95 addParameter( dateFormat );
98 QgsPointsToPathsAlgorithm *QgsPointsToPathsAlgorithm::createInstance()
const
100 return new QgsPointsToPathsAlgorithm();
105 std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral(
"INPUT" ), context ) );
109 const bool closePaths = parameterAsBool( parameters, QStringLiteral(
"CLOSE_PATH" ), context );
111 QString orderExpressionString = parameterAsString( parameters, QStringLiteral(
"ORDER_EXPRESSION" ), context );
112 const QString orderFieldString = parameterAsString( parameters, QStringLiteral(
"ORDER_FIELD" ), context );
113 if ( ! orderFieldString.isEmpty() )
118 QString dateFormat = parameterAsString( parameters, QStringLiteral(
"DATE_FORMAT" ), context );
119 if ( ! dateFormat.isEmpty() )
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",
"" )
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" )
134 << QPair< QString, QString >(
"%p",
"AP" )
135 << QPair< QString, QString >(
"%M",
"mm" )
136 << QPair< QString, QString >(
"%S",
"ss" )
137 << QPair< QString, QString >(
"%f",
"zzz" )
138 << QPair< QString, QString >(
"%z",
"" )
139 << QPair< QString, QString >(
"%Z",
"" )
140 << QPair< QString, QString >(
"%j",
"" )
141 << QPair< QString, QString >(
"%U",
"" )
142 << QPair< QString, QString >(
"%W",
"" )
143 << QPair< QString, QString >(
"%c",
"" )
144 << QPair< QString, QString >(
"%x",
"" )
145 << QPair< QString, QString >(
"%X",
"" )
146 << QPair< QString, QString >(
"%G",
"yyyy" )
147 << QPair< QString, QString >(
"%u",
"" )
148 << QPair< QString, QString >(
"%V",
"" );
149 for (
const auto &pair : codeMap )
151 dateFormat.replace( pair.first, pair.second );
153 orderExpressionString = QString(
"to_datetime(%1, '%2')" ).arg( orderExpressionString ).arg( dateFormat );
156 else if ( orderExpressionString.isEmpty() )
159 orderExpressionString = QString(
"$id" );
161 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
166 QStringList requiredFields = QStringList( orderExpression.
referencedColumns().values() );
167 orderExpression.
prepare( &expressionContext );
169 QVariant::Type orderFieldType = QVariant::String;
170 if ( orderExpression.
isField() )
172 const int orderFieldIndex = source->fields().indexFromName( orderExpression.
referencedColumns().values().first() );
173 orderFieldType = source->fields().field( orderFieldIndex ).type();
177 QString groupExpressionString = parameterAsString( parameters, QStringLiteral(
"GROUP_EXPRESSION" ), context );
179 const QString groupFieldString = parameterAsString( parameters, QStringLiteral(
"GROUP_FIELD" ), context );
180 if ( ! groupFieldString.isEmpty() )
188 if ( ! groupExpressionString.isEmpty() )
191 const QgsField field = groupExpression.
isField() ? source->fields().field( requiredFields.last() ) : QStringLiteral(
"group" );
197 const bool naturalSort = parameterAsBool( parameters, QStringLiteral(
"NATURAL_SORT" ), context );
199 collator.setNumericMode(
true );
208 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, dest, outputFields, wkbType, source->sourceCrs() ) );
212 const QString textDir = parameterAsString( parameters, QStringLiteral(
"OUTPUT_TEXT_DIR" ), context );
213 if ( ! textDir.isEmpty() &&
214 ! QDir( textDir ).exists() )
222 QHash< QVariant, QVector< QPair< QVariant, QgsPoint > > > allPoints;
227 const double totalPoints = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
228 long currentPoint = 0;
236 feedback->
setProgress( 0.5 * currentPoint * totalPoints );
241 const QVariant orderValue = orderExpression.
evaluate( &expressionContext );
242 const QVariant groupValue = groupExpressionString.isEmpty() ? QVariant() : groupExpression.evaluate( &expressionContext );
244 if ( ! allPoints.contains( groupValue ) )
245 allPoints[ groupValue ] = QVector< QPair< QVariant, QgsPoint > >();
249 const QgsMultiPoint mp( *qgsgeometry_cast< const QgsMultiPoint * >( geom ) );
250 for (
auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit )
252 const QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( *pit ) );
253 allPoints[ groupValue ] << qMakePair( orderValue, point );
258 const QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( geom ) );
259 allPoints[ groupValue ] << qMakePair( orderValue, point );
267 QHashIterator< QVariant, QVector< QPair< QVariant, QgsPoint > > > hit( allPoints );
269 while ( hit.hasNext() )
276 auto pairs = hit.value();
280 std::stable_sort( pairs.begin(),
282 [&collator](
const QPair< const QVariant, QgsPoint > &pair1,
283 const QPair< const QVariant, QgsPoint > &pair2 )
285 return collator.compare( pair1.first.toString(), pair2.first.toString() ) < 0;
290 std::stable_sort( pairs.begin(),
292 [](
const QPair< const QVariant, QgsPoint > &pair1,
293 const QPair< const QVariant, QgsPoint > &pair2 )
295 return qgsVariantLessThan( pair1.first, pair2.first );
300 QVector<QgsPoint> pathPoints;
301 for (
auto pit = pairs.constBegin(); pit != pairs.constEnd(); ++pit )
307 feedback->
setProgress( 50 + 0.5 * currentPoint * totalPoints );
308 pathPoints.append( pit->second );
311 if ( pathPoints.size() < 2 )
313 feedback->
pushInfo( QObject::tr(
"Skipping path with group %1 : insufficient vertices" ).arg( hit.key().toString() ) );
316 if ( closePaths && pathPoints.size() > 2 && pathPoints.constFirst() != pathPoints.constLast() )
317 pathPoints.append( pathPoints.constFirst() );
321 if ( ! groupExpressionString.isEmpty() )
322 attrs.append( hit.key() );
323 attrs.append( hit.value().first().first );
324 attrs.append( hit.value().last().first );
330 if ( ! textDir.isEmpty() )
332 const QString filename = QDir( textDir ).filePath( hit.key().toString() + QString(
".txt" ) );
333 QFile textFile( filename );
334 if ( !textFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
337 QTextStream out( &textFile );
338 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
339 out.setCodec(
"UTF-8" );
341 out << QString(
"angle=Azimuth\n"
342 "heading=Coordinate_System\n"
343 "dist_units=Default\n"
346 "[data]\n" ).arg( pathPoints.at( 0 ).x() ).arg( pathPoints.at( 0 ).y() );
348 for (
int i = 1; i < pathPoints.size(); ++i )
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 );
361 outputs.insert( QStringLiteral(
"OUTPUT" ), dest );
362 outputs.insert( QStringLiteral(
"NUM_PATHS" ), pathCount );