QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 #include "qgslinestring.h"
20 #include "qgsmultilinestring.h"
21 
23 
24 QString QgsJoinWithLinesAlgorithm::name() const
25 {
26  return QStringLiteral( "hublines" );
27 }
28 
29 QString QgsJoinWithLinesAlgorithm::displayName() const
30 {
31  return QObject::tr( "Join by lines (hub lines)" );
32 }
33 
34 QStringList QgsJoinWithLinesAlgorithm::tags() const
35 {
36  return QObject::tr( "join,connect,lines,points,hub,spoke,geodesic,great,circle" ).split( ',' );
37 }
38 
39 QString QgsJoinWithLinesAlgorithm::group() const
40 {
41  return QObject::tr( "Vector analysis" );
42 }
43 
44 QString QgsJoinWithLinesAlgorithm::groupId() const
45 {
46  return QStringLiteral( "vectoranalysis" );
47 }
48 
49 void QgsJoinWithLinesAlgorithm::initAlgorithm( const QVariantMap & )
50 {
51  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "HUBS" ),
52  QObject::tr( "Hub layer" ) ) );
53  addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELD" ),
54  QObject::tr( "Hub ID field" ), QVariant(), QStringLiteral( "HUBS" ) ) );
55 
56  addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELDS" ),
57  QObject::tr( "Hub layer fields to copy (leave empty to copy all fields)" ),
58  QVariant(), QStringLiteral( "HUBS" ), QgsProcessingParameterField::Any,
59  true, true ) );
60 
61  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "SPOKES" ),
62  QObject::tr( "Spoke layer" ) ) );
63  addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELD" ),
64  QObject::tr( "Spoke ID field" ), QVariant(), QStringLiteral( "SPOKES" ) ) );
65 
66  addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELDS" ),
67  QObject::tr( "Spoke layer fields to copy (leave empty to copy all fields)" ),
68  QVariant(), QStringLiteral( "SPOKES" ), QgsProcessingParameterField::Any,
69  true, true ) );
70 
71  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "GEODESIC" ), QObject::tr( "Create geodesic lines" ), false ) );
72 
73  auto distanceParam = std::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "GEODESIC_DISTANCE" ), QObject::tr( "Distance between vertices (geodesic lines only)" ), 1000 );
74  distanceParam->setFlags( distanceParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
75  distanceParam->setDefaultUnit( QgsUnitTypes::DistanceKilometers );
76  distanceParam->setIsDynamic( true );
77  distanceParam->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Geodesic Distance" ), QObject::tr( "Distance between vertices" ), QgsPropertyDefinition::DoublePositive ) );
78  distanceParam->setDynamicLayerParameterName( QStringLiteral( "HUBS" ) );
79  addParameter( distanceParam.release() );
80 
81  auto breakParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "ANTIMERIDIAN_SPLIT" ), QObject::tr( "Split lines at antimeridian (±180 degrees longitude)" ), false );
82  breakParam->setFlags( breakParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
83  addParameter( breakParam.release() );
84 
85  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Hub lines" ), QgsProcessing::TypeVectorLine ) );
86 }
87 
88 QString QgsJoinWithLinesAlgorithm::shortHelpString() const
89 {
90  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"
91  "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"
92  "If input layers are not point layers, a point on the surface of the geometries will be taken as the connecting location.\n\n"
93  "Optionally, geodesic lines can be created, which represent the shortest path on the surface of an ellipsoid. When "
94  "geodesic mode is used, it is possible to split the created lines at the antimeridian (±180 degrees longitude), which can improve "
95  "rendering of the lines. Additionally, the distance between vertices can be specified. A smaller distance results in a denser, more "
96  "accurate line." );
97 }
98 
99 QString QgsJoinWithLinesAlgorithm::shortDescription() const
100 {
101  return QObject::tr( "Creates lines joining two point layers, based on a common attribute value." );
102 }
103 
104 QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance() const
105 {
106  return new QgsJoinWithLinesAlgorithm();
107 }
108 
109 QVariantMap QgsJoinWithLinesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
110 {
111  if ( parameters.value( QStringLiteral( "SPOKES" ) ) == parameters.value( QStringLiteral( "HUBS" ) ) )
112  throw QgsProcessingException( QObject::tr( "Same layer given for both hubs and spokes" ) );
113 
114  std::unique_ptr< QgsProcessingFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) );
115  if ( !hubSource )
116  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "HUBS" ) ) );
117 
118  std::unique_ptr< QgsProcessingFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) );
119  if ( !spokeSource )
120  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "SPOKES" ) ) );
121 
122  QString fieldHubName = parameterAsString( parameters, QStringLiteral( "HUB_FIELD" ), context );
123  int fieldHubIndex = hubSource->fields().lookupField( fieldHubName );
124  const QStringList hubFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "HUB_FIELDS" ), context );
125 
126  QString fieldSpokeName = parameterAsString( parameters, QStringLiteral( "SPOKE_FIELD" ), context );
127  int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName );
128  const QStringList spokeFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "SPOKE_FIELDS" ), context );
129 
130  if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
131  throw QgsProcessingException( QObject::tr( "Invalid ID field" ) );
132 
133  const bool geodesic = parameterAsBoolean( parameters, QStringLiteral( "GEODESIC" ), context );
134  const double geodesicDistance = parameterAsDouble( parameters, QStringLiteral( "GEODESIC_DISTANCE" ), context ) * 1000;
135  bool dynamicGeodesicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "GEODESIC_DISTANCE" ) );
136  QgsExpressionContext expressionContext = createExpressionContext( parameters, context, hubSource.get() );
137  QgsProperty geodesicDistanceProperty;
138  if ( dynamicGeodesicDistance )
139  {
140  geodesicDistanceProperty = parameters.value( QStringLiteral( "GEODESIC_DISTANCE" ) ).value< QgsProperty >();
141  }
142 
143  const bool splitAntimeridian = parameterAsBoolean( parameters, QStringLiteral( "ANTIMERIDIAN_SPLIT" ), context );
144  QgsDistanceArea da;
145  da.setSourceCrs( hubSource->sourceCrs(), context.transformContext() );
146  da.setEllipsoid( context.ellipsoid() );
147 
148  QgsFields hubOutFields;
149  QgsAttributeList hubFieldIndices;
150  if ( hubFieldsToCopy.empty() )
151  {
152  hubOutFields = hubSource->fields();
153  hubFieldIndices.reserve( hubOutFields.count() );
154  for ( int i = 0; i < hubOutFields.count(); ++i )
155  {
156  hubFieldIndices << i;
157  }
158  }
159  else
160  {
161  hubFieldIndices.reserve( hubOutFields.count() );
162  for ( const QString &field : hubFieldsToCopy )
163  {
164  int index = hubSource->fields().lookupField( field );
165  if ( index >= 0 )
166  {
167  hubFieldIndices << index;
168  hubOutFields.append( hubSource->fields().at( index ) );
169  }
170  }
171  }
172 
173  QgsAttributeList hubFields2Fetch = hubFieldIndices;
174  hubFields2Fetch << fieldHubIndex;
175 
176  QgsFields spokeOutFields;
177  QgsAttributeList spokeFieldIndices;
178  if ( spokeFieldsToCopy.empty() )
179  {
180  spokeOutFields = spokeSource->fields();
181  spokeFieldIndices.reserve( spokeOutFields.count() );
182  for ( int i = 0; i < spokeOutFields.count(); ++i )
183  {
184  spokeFieldIndices << i;
185  }
186  }
187  else
188  {
189  for ( const QString &field : spokeFieldsToCopy )
190  {
191  int index = spokeSource->fields().lookupField( field );
192  if ( index >= 0 )
193  {
194  spokeFieldIndices << index;
195  spokeOutFields.append( spokeSource->fields().at( index ) );
196  }
197  }
198  }
199 
200  QgsAttributeList spokeFields2Fetch = spokeFieldIndices;
201  spokeFields2Fetch << fieldSpokeIndex;
202 
203 
204  QgsFields fields = QgsProcessingUtils::combineFields( hubOutFields, spokeOutFields );
205 
207  bool hasZ = false;
208  if ( QgsWkbTypes::hasZ( hubSource->wkbType() ) || QgsWkbTypes::hasZ( spokeSource->wkbType() ) )
209  {
210  outType = QgsWkbTypes::addZ( outType );
211  hasZ = true;
212  }
213  bool hasM = false;
214  if ( QgsWkbTypes::hasM( hubSource->wkbType() ) || QgsWkbTypes::hasM( spokeSource->wkbType() ) )
215  {
216  outType = QgsWkbTypes::addM( outType );
217  hasM = true;
218  }
219 
220  QString dest;
221  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
222  outType, hubSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
223  if ( !sink )
224  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
225 
226  auto getPointFromFeature = [hasZ, hasM]( const QgsFeature & feature )->QgsPoint
227  {
228  QgsPoint p;
229  if ( feature.geometry().type() == QgsWkbTypes::PointGeometry && !feature.geometry().isMultipart() )
230  p = *static_cast< const QgsPoint *>( feature.geometry().constGet() );
231  else
232  p = *static_cast< const QgsPoint *>( feature.geometry().pointOnSurface().constGet() );
233  if ( hasZ && !p.is3D() )
234  p.addZValue( 0 );
235  if ( hasM && !p.isMeasure() )
236  p.addMValue( 0 );
237  return p;
238  };
239 
241  double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
242  int i = 0;
243  QgsFeature hubFeature;
244  while ( hubFeatures.nextFeature( hubFeature ) )
245  {
246  i++;
247  if ( feedback->isCanceled() )
248  {
249  break;
250  }
251 
252  feedback->setProgress( i * step );
253 
254  if ( !hubFeature.hasGeometry() )
255  continue;
256 
257  QgsPoint hubPoint = getPointFromFeature( hubFeature );
258 
259  // only keep selected attributes
260  QgsAttributes hubAttributes;
261  for ( int j = 0; j < hubFeature.attributes().count(); ++j )
262  {
263  if ( !hubFieldIndices.contains( j ) )
264  continue;
265  hubAttributes << hubFeature.attribute( j );
266  }
267 
268  QgsFeatureRequest spokeRequest = QgsFeatureRequest().setDestinationCrs( hubSource->sourceCrs(), context.transformContext() );
269  spokeRequest.setSubsetOfAttributes( spokeFields2Fetch );
270  spokeRequest.setFilterExpression( QgsExpression::createFieldEqualityExpression( fieldSpokeName, hubFeature.attribute( fieldHubIndex ) ) );
271 
272  QgsFeatureIterator spokeFeatures = spokeSource->getFeatures( spokeRequest, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
273  QgsFeature spokeFeature;
274  while ( spokeFeatures.nextFeature( spokeFeature ) )
275  {
276  if ( feedback->isCanceled() )
277  {
278  break;
279  }
280  if ( !spokeFeature.hasGeometry() )
281  continue;
282 
283  QgsPoint spokePoint = getPointFromFeature( spokeFeature );
284  QgsGeometry line;
285  if ( !geodesic )
286  {
287  line = QgsGeometry( new QgsLineString( QVector< QgsPoint >() << hubPoint << spokePoint ) );
288  if ( splitAntimeridian )
289  line = da.splitGeometryAtAntimeridian( line );
290  }
291  else
292  {
293  double distance = geodesicDistance;
294  if ( dynamicGeodesicDistance )
295  {
296  expressionContext.setFeature( hubFeature );
297  distance = geodesicDistanceProperty.valueAsDouble( expressionContext, distance );
298  }
299 
300  std::unique_ptr< QgsMultiLineString > ml = std::make_unique< QgsMultiLineString >();
301  std::unique_ptr< QgsLineString > l = std::make_unique< QgsLineString >( QVector< QgsPoint >() << hubPoint );
302  QVector< QVector< QgsPointXY > > points = da.geodesicLine( QgsPointXY( hubPoint ), QgsPointXY( spokePoint ), distance, splitAntimeridian );
303  QVector< QgsPointXY > points1 = points.at( 0 );
304  points1.pop_front();
305  if ( points.count() == 1 )
306  points1.pop_back();
307 
308  QgsLineString geodesicPoints( points1 );
309  l->append( &geodesicPoints );
310  if ( points.count() == 1 )
311  l->addVertex( spokePoint );
312 
313  ml->addGeometry( l.release() );
314  if ( points.count() > 1 )
315  {
316  QVector< QgsPointXY > points2 = points.at( 1 );
317  points2.pop_back();
318  l = std::make_unique< QgsLineString >( points2 );
319  if ( hasZ )
320  l->addZValue( std::numeric_limits<double>::quiet_NaN() );
321  if ( hasM )
322  l->addMValue( std::numeric_limits<double>::quiet_NaN() );
323 
324  l->addVertex( spokePoint );
325  ml->addGeometry( l.release() );
326  }
327  line = QgsGeometry( std::move( ml ) );
328  }
329 
330  QgsFeature outFeature;
331  QgsAttributes outAttributes = hubAttributes;
332 
333  // only keep selected attributes
334  QgsAttributes spokeAttributes;
335  for ( int j = 0; j < spokeFeature.attributes().count(); ++j )
336  {
337  if ( !spokeFieldIndices.contains( j ) )
338  continue;
339  spokeAttributes << spokeFeature.attribute( j );
340  }
341 
342  outAttributes.append( spokeAttributes );
343  outFeature.setAttributes( outAttributes );
344  outFeature.setGeometry( line );
345  sink->addFeature( outFeature, QgsFeatureSink::FastInsert );
346  }
347  }
348 
349  QVariantMap outputs;
350  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
351  return outputs;
352 }
353 
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
A vector of attributes.
Definition: qgsattributes.h:58
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)
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)
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.
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:56
QgsAttributes attributes
Definition: qgsfeature.h:65
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:135
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:205
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:302
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:145
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
Container of fields for a vector layer.
Definition: qgsfields.h:45
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)
Definition: qgsfields.cpp:59
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
A class to represent a 2D point.
Definition: qgspointxy.h:59
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:562
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:551
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.
Definition: qgsexception.h:83
@ 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.
A boolean parameter for processing algorithms.
@ FlagAdvanced
Parameter is an advanced parameter which should be hidden from users by default.
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).
@ TypeVectorLine
Vector line layers.
Definition: qgsprocessing.h:50
Definition for a property.
Definition: qgsproperty.h:48
@ DoublePositive
Positive double value (including 0)
Definition: qgsproperty.h:59
A store for object properties.
Definition: qgsproperty.h:232
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.
@ DistanceKilometers
Kilometers.
Definition: qgsunittypes.h:70
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1100
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static Type addZ(Type type) SIP_HOLDGIL
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1146
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1050
static Type addM(Type type) SIP_HOLDGIL
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1171
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463