QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsalgorithmjoinwithlines.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmjoinwithlines.cpp
3 ---------------------
4 begin : April 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson 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
20#include "qgsdistancearea.h"
21#include "qgslinestring.h"
22#include "qgsmultilinestring.h"
23
25
26QString QgsJoinWithLinesAlgorithm::name() const
27{
28 return QStringLiteral( "hublines" );
29}
30
31QString QgsJoinWithLinesAlgorithm::displayName() const
32{
33 return QObject::tr( "Join by lines (hub lines)" );
34}
35
36QStringList QgsJoinWithLinesAlgorithm::tags() const
37{
38 return QObject::tr( "join,connect,lines,points,hub,spoke,geodesic,great,circle" ).split( ',' );
39}
40
41QString QgsJoinWithLinesAlgorithm::group() const
42{
43 return QObject::tr( "Vector analysis" );
44}
45
46QString QgsJoinWithLinesAlgorithm::groupId() const
47{
48 return QStringLiteral( "vectoranalysis" );
49}
50
51void QgsJoinWithLinesAlgorithm::initAlgorithm( const QVariantMap & )
52{
53 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "HUBS" ), QObject::tr( "Hub layer" ) ) );
54 addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELD" ), QObject::tr( "Hub ID field" ), QVariant(), QStringLiteral( "HUBS" ) ) );
55
56 addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELDS" ), QObject::tr( "Hub layer fields to copy (leave empty to copy all fields)" ), QVariant(), QStringLiteral( "HUBS" ), Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
57
58 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "SPOKES" ), QObject::tr( "Spoke layer" ) ) );
59 addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELD" ), QObject::tr( "Spoke ID field" ), QVariant(), QStringLiteral( "SPOKES" ) ) );
60
61 addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELDS" ), QObject::tr( "Spoke layer fields to copy (leave empty to copy all fields)" ), QVariant(), QStringLiteral( "SPOKES" ), Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
62
63 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "GEODESIC" ), QObject::tr( "Create geodesic lines" ), false ) );
64
65 auto distanceParam = std::make_unique<QgsProcessingParameterDistance>( QStringLiteral( "GEODESIC_DISTANCE" ), QObject::tr( "Distance between vertices (geodesic lines only)" ), 1000 );
66 distanceParam->setFlags( distanceParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
67 distanceParam->setDefaultUnit( Qgis::DistanceUnit::Kilometers );
68 distanceParam->setIsDynamic( true );
69 distanceParam->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Geodesic Distance" ), QObject::tr( "Distance between vertices" ), QgsPropertyDefinition::DoublePositive ) );
70 distanceParam->setDynamicLayerParameterName( QStringLiteral( "HUBS" ) );
71 addParameter( distanceParam.release() );
72
73 auto breakParam = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral( "ANTIMERIDIAN_SPLIT" ), QObject::tr( "Split lines at antimeridian (±180 degrees longitude)" ), false );
74 breakParam->setFlags( breakParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
75 addParameter( breakParam.release() );
76
77 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Hub lines" ), Qgis::ProcessingSourceType::VectorLine ) );
78}
79
80QString QgsJoinWithLinesAlgorithm::shortHelpString() const
81{
82 return QObject::tr( "This algorithm creates hub and spoke diagrams by connecting lines from points on the Spoke layer to matching points in the Hub layer.\n\n"
83 "Determination of which hub goes with each point is based on a match between the Hub ID field on the hub points and the Spoke ID field on the spoke points.\n\n"
84 "If input layers are not point layers, a point on the surface of the geometries will be taken as the connecting location.\n\n"
85 "Optionally, geodesic lines can be created, which represent the shortest path on the surface of an ellipsoid. When "
86 "geodesic mode is used, it is possible to split the created lines at the antimeridian (±180 degrees longitude), which can improve "
87 "rendering of the lines. Additionally, the distance between vertices can be specified. A smaller distance results in a denser, more "
88 "accurate line." );
89}
90
91QString QgsJoinWithLinesAlgorithm::shortDescription() const
92{
93 return QObject::tr( "Creates lines joining two point layers, based on a common attribute value." );
94}
95
96Qgis::ProcessingAlgorithmDocumentationFlags QgsJoinWithLinesAlgorithm::documentationFlags() const
97{
99}
100
101QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance() const
102{
103 return new QgsJoinWithLinesAlgorithm();
104}
105
106QVariantMap QgsJoinWithLinesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
107{
108 if ( parameters.value( QStringLiteral( "SPOKES" ) ) == parameters.value( QStringLiteral( "HUBS" ) ) )
109 throw QgsProcessingException( QObject::tr( "Same layer given for both hubs and spokes" ) );
110
111 std::unique_ptr<QgsProcessingFeatureSource> hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) );
112 if ( !hubSource )
113 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "HUBS" ) ) );
114
115 std::unique_ptr<QgsProcessingFeatureSource> spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) );
116 if ( !spokeSource )
117 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "SPOKES" ) ) );
118
119 const QString fieldHubName = parameterAsString( parameters, QStringLiteral( "HUB_FIELD" ), context );
120 const int fieldHubIndex = hubSource->fields().lookupField( fieldHubName );
121 const QStringList hubFieldsToCopy = parameterAsStrings( parameters, QStringLiteral( "HUB_FIELDS" ), context );
122
123 const QString fieldSpokeName = parameterAsString( parameters, QStringLiteral( "SPOKE_FIELD" ), context );
124 const int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName );
125 const QStringList spokeFieldsToCopy = parameterAsStrings( parameters, QStringLiteral( "SPOKE_FIELDS" ), context );
126
127 if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
128 throw QgsProcessingException( QObject::tr( "Invalid ID field" ) );
129
130 const bool geodesic = parameterAsBoolean( parameters, QStringLiteral( "GEODESIC" ), context );
131 const double geodesicDistance = parameterAsDouble( parameters, QStringLiteral( "GEODESIC_DISTANCE" ), context ) * 1000;
132 const bool dynamicGeodesicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "GEODESIC_DISTANCE" ) );
133 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, hubSource.get() );
134 QgsProperty geodesicDistanceProperty;
135 if ( dynamicGeodesicDistance )
136 {
137 geodesicDistanceProperty = parameters.value( QStringLiteral( "GEODESIC_DISTANCE" ) ).value<QgsProperty>();
138 }
139
140 const bool splitAntimeridian = parameterAsBoolean( parameters, QStringLiteral( "ANTIMERIDIAN_SPLIT" ), context );
142 da.setSourceCrs( hubSource->sourceCrs(), context.transformContext() );
143 da.setEllipsoid( context.ellipsoid() );
144
145 QgsFields hubOutFields;
146 QgsAttributeList hubFieldIndices;
147 if ( hubFieldsToCopy.empty() )
148 {
149 hubOutFields = hubSource->fields();
150 hubFieldIndices.reserve( hubOutFields.count() );
151 for ( int i = 0; i < hubOutFields.count(); ++i )
152 {
153 hubFieldIndices << i;
154 }
155 }
156 else
157 {
158 hubFieldIndices.reserve( hubOutFields.count() );
159 for ( const QString &field : hubFieldsToCopy )
160 {
161 const int index = hubSource->fields().lookupField( field );
162 if ( index >= 0 )
163 {
164 hubFieldIndices << index;
165 hubOutFields.append( hubSource->fields().at( index ) );
166 }
167 }
168 }
169
170 QgsAttributeList hubFields2Fetch = hubFieldIndices;
171 hubFields2Fetch << fieldHubIndex;
172
173 QgsFields spokeOutFields;
174 QgsAttributeList spokeFieldIndices;
175 if ( spokeFieldsToCopy.empty() )
176 {
177 spokeOutFields = spokeSource->fields();
178 spokeFieldIndices.reserve( spokeOutFields.count() );
179 for ( int i = 0; i < spokeOutFields.count(); ++i )
180 {
181 spokeFieldIndices << i;
182 }
183 }
184 else
185 {
186 for ( const QString &field : spokeFieldsToCopy )
187 {
188 const int index = spokeSource->fields().lookupField( field );
189 if ( index >= 0 )
190 {
191 spokeFieldIndices << index;
192 spokeOutFields.append( spokeSource->fields().at( index ) );
193 }
194 }
195 }
196
197 QgsAttributeList spokeFields2Fetch = spokeFieldIndices;
198 spokeFields2Fetch << fieldSpokeIndex;
199
200
201 const QgsFields fields = QgsProcessingUtils::combineFields( hubOutFields, spokeOutFields );
202
204 bool hasZ = false;
205 if ( !geodesic && ( QgsWkbTypes::hasZ( hubSource->wkbType() ) || QgsWkbTypes::hasZ( spokeSource->wkbType() ) ) )
206 {
207 outType = QgsWkbTypes::addZ( outType );
208 hasZ = true;
209 }
210 bool hasM = false;
211 if ( !geodesic && ( QgsWkbTypes::hasM( hubSource->wkbType() ) || QgsWkbTypes::hasM( spokeSource->wkbType() ) ) )
212 {
213 outType = QgsWkbTypes::addM( outType );
214 hasM = true;
215 }
216
217 QString dest;
218 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, outType, hubSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
219 if ( !sink )
220 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
221
222 auto getPointFromFeature = [hasZ, hasM]( const QgsFeature &feature ) -> QgsPoint {
223 QgsPoint p;
224 if ( feature.geometry().type() == Qgis::GeometryType::Point && !feature.geometry().isMultipart() )
225 p = *static_cast<const QgsPoint *>( feature.geometry().constGet() );
226 else
227 p = *static_cast<const QgsPoint *>( feature.geometry().pointOnSurface().constGet() );
228 if ( hasZ && !p.is3D() )
229 p.addZValue( 0 );
230 if ( hasM && !p.isMeasure() )
231 p.addMValue( 0 );
232 return p;
233 };
234
235 QgsFeatureIterator hubFeatures = hubSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( hubFields2Fetch ), Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks );
236 const double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
237 int i = 0;
238 QgsFeature hubFeature;
239 while ( hubFeatures.nextFeature( hubFeature ) )
240 {
241 i++;
242 if ( feedback->isCanceled() )
243 {
244 break;
245 }
246
247 feedback->setProgress( i * step );
248
249 if ( !hubFeature.hasGeometry() )
250 continue;
251
252 const QgsPoint hubPoint = getPointFromFeature( hubFeature );
253
254 // only keep selected attributes
255 QgsAttributes hubAttributes;
256 const int attributeCount = hubFeature.attributeCount();
257 for ( int j = 0; j < attributeCount; ++j )
258 {
259 if ( !hubFieldIndices.contains( j ) )
260 continue;
261 hubAttributes << hubFeature.attribute( j );
262 }
263
264 QgsFeatureRequest spokeRequest = QgsFeatureRequest().setDestinationCrs( hubSource->sourceCrs(), context.transformContext() );
265 spokeRequest.setSubsetOfAttributes( spokeFields2Fetch );
266 spokeRequest.setFilterExpression( QgsExpression::createFieldEqualityExpression( fieldSpokeName, hubFeature.attribute( fieldHubIndex ) ) );
267
268 QgsFeatureIterator spokeFeatures = spokeSource->getFeatures( spokeRequest, Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks );
269 QgsFeature spokeFeature;
270 while ( spokeFeatures.nextFeature( spokeFeature ) )
271 {
272 if ( feedback->isCanceled() )
273 {
274 break;
275 }
276 if ( !spokeFeature.hasGeometry() )
277 continue;
278
279 const QgsPoint spokePoint = getPointFromFeature( spokeFeature );
280 QgsGeometry line;
281 if ( !geodesic )
282 {
283 line = QgsGeometry( new QgsLineString( QVector<QgsPoint>() << hubPoint << spokePoint ) );
284 if ( splitAntimeridian )
285 line = da.splitGeometryAtAntimeridian( line );
286 }
287 else
288 {
289 double distance = geodesicDistance;
290 if ( dynamicGeodesicDistance )
291 {
292 expressionContext.setFeature( hubFeature );
293 distance = geodesicDistanceProperty.valueAsDouble( expressionContext, distance );
294 }
295
296 auto ml = std::make_unique<QgsMultiLineString>();
297 auto l = std::make_unique<QgsLineString>( QVector<QgsPoint>() << hubPoint );
298 const QVector<QVector<QgsPointXY>> points = da.geodesicLine( QgsPointXY( hubPoint ), QgsPointXY( spokePoint ), distance, splitAntimeridian );
299 QVector<QgsPointXY> points1 = points.at( 0 );
300 points1.pop_front();
301 if ( points.count() == 1 )
302 points1.pop_back();
303
304 const QgsLineString geodesicPoints( points1 );
305 l->append( &geodesicPoints );
306 if ( points.count() == 1 )
307 l->addVertex( spokePoint );
308
309 ml->addGeometry( l.release() );
310 if ( points.count() > 1 )
311 {
312 QVector<QgsPointXY> points2 = points.at( 1 );
313 points2.pop_back();
314 l = std::make_unique<QgsLineString>( points2 );
315 if ( hasZ )
316 l->addZValue( std::numeric_limits<double>::quiet_NaN() );
317 if ( hasM )
318 l->addMValue( std::numeric_limits<double>::quiet_NaN() );
319
320 l->addVertex( spokePoint );
321 ml->addGeometry( l.release() );
322 }
323 line = QgsGeometry( std::move( ml ) );
324 }
325
326 QgsFeature outFeature;
327 QgsAttributes outAttributes = hubAttributes;
328
329 // only keep selected attributes
330 QgsAttributes spokeAttributes;
331 const int attributeCount = spokeFeature.attributeCount();
332 for ( int j = 0; j < attributeCount; ++j )
333 {
334 if ( !spokeFieldIndices.contains( j ) )
335 continue;
336 spokeAttributes << spokeFeature.attribute( j );
337 }
338
339 outAttributes.append( spokeAttributes );
340 outFeature.setAttributes( outAttributes );
341 outFeature.setGeometry( line );
342 if ( !sink->addFeature( outFeature, QgsFeatureSink::FastInsert ) )
343 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
344 }
345 }
346 sink->finalize();
347
348 QVariantMap outputs;
349 outputs.insert( QStringLiteral( "OUTPUT" ), dest );
350 return outputs;
351}
352
@ VectorLine
Vector line layers.
Definition qgis.h:3535
@ Kilometers
Kilometers.
Definition qgis.h:5015
@ Point
Points.
Definition qgis.h:359
@ RegeneratesPrimaryKey
Algorithm always drops any existing primary keys or FID values and regenerates them in outputs.
Definition qgis.h:3619
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3630
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3711
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:277
@ LineString
LineString.
Definition qgis.h:280
@ MultiLineString
MultiLineString.
Definition qgis.h:284
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3763
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
A vector of attributes.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
QVector< QVector< QgsPointXY > > geodesicLine(const QgsPointXY &p1, const QgsPointXY &p2, double interval, bool breakLine=false) const
Calculates the geodesic line between p1 and p2, which represents the shortest path on the ellipsoid b...
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
QgsGeometry splitGeometryAtAntimeridian(const QgsGeometry &geometry) const
Splits a (Multi)LineString geometry at the antimeridian (longitude +/- 180 degrees).
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.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QMetaType::Type fieldType=QMetaType::Type::UnknownType)
Create an expression allowing to evaluate if a field is equal to a value.
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.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
int attributeCount() const
Returns the number of attributes attached to the feature.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:73
int count
Definition qgsfields.h:50
A geometry is the spatial representation of a feature.
Line string geometry type, with support for z-dimension and m-values.
Represents a 2D point.
Definition qgspointxy.h:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition qgspoint.cpp:572
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition qgspoint.cpp:561
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.
A boolean 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.
static bool isDynamic(const QVariantMap &parameters, const QString &name)
Returns true if the parameter with matching name is a dynamic parameter, and must be evaluated once f...
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix=QString())
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
Definition for a property.
Definition qgsproperty.h:45
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:56
A store for object properties.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
double valueAsDouble(const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property and interprets it as a double.
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.
QList< int > QgsAttributeList
Definition qgsfield.h:28