QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
22 
23 QString QgsJoinWithLinesAlgorithm::name() const
24 {
25  return QStringLiteral( "hublines" );
26 }
27 
28 QString QgsJoinWithLinesAlgorithm::displayName() const
29 {
30  return QObject::tr( "Join by lines (hub lines)" );
31 }
32 
33 QStringList QgsJoinWithLinesAlgorithm::tags() const
34 {
35  return QObject::tr( "join,connect,lines,points,hub,spoke" ).split( ',' );
36 }
37 
38 QString QgsJoinWithLinesAlgorithm::group() const
39 {
40  return QObject::tr( "Vector analysis" );
41 }
42 
43 QString QgsJoinWithLinesAlgorithm::groupId() const
44 {
45  return QStringLiteral( "vectoranalysis" );
46 }
47 
48 void QgsJoinWithLinesAlgorithm::initAlgorithm( const QVariantMap & )
49 {
50  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "HUBS" ),
51  QObject::tr( "Hub layer" ) ) );
52  addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELD" ),
53  QObject::tr( "Hub ID field" ), QVariant(), QStringLiteral( "HUBS" ) ) );
54 
55  addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELDS" ),
56  QObject::tr( "Hub layer fields to copy (leave empty to copy all fields)" ),
57  QVariant(), QStringLiteral( "HUBS" ), QgsProcessingParameterField::Any,
58  true, true ) );
59 
60  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "SPOKES" ),
61  QObject::tr( "Spoke layer" ) ) );
62  addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELD" ),
63  QObject::tr( "Spoke ID field" ), QVariant(), QStringLiteral( "SPOKES" ) ) );
64 
65  addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELDS" ),
66  QObject::tr( "Spoke layer fields to copy (leave empty to copy all fields)" ),
67  QVariant(), QStringLiteral( "SPOKES" ), QgsProcessingParameterField::Any,
68  true, true ) );
69 
70  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Hub lines" ), QgsProcessing::TypeVectorLine ) );
71 }
72 
73 QString QgsJoinWithLinesAlgorithm::shortHelpString() const
74 {
75  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"
76  "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"
77  "If input layers are not point layers, a point on the surface of the geometries will be taken as the connecting location." );
78 }
79 
80 QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance() const
81 {
82  return new QgsJoinWithLinesAlgorithm();
83 }
84 
85 QVariantMap QgsJoinWithLinesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
86 {
87  if ( parameters.value( QStringLiteral( "SPOKES" ) ) == parameters.value( QStringLiteral( "HUBS" ) ) )
88  throw QgsProcessingException( QObject::tr( "Same layer given for both hubs and spokes" ) );
89 
90  std::unique_ptr< QgsProcessingFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) );
91  if ( !hubSource )
92  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "HUBS" ) ) );
93 
94  std::unique_ptr< QgsProcessingFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) );
95  if ( !hubSource || !spokeSource )
96  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "SPOKES" ) ) );
97 
98  QString fieldHubName = parameterAsString( parameters, QStringLiteral( "HUB_FIELD" ), context );
99  int fieldHubIndex = hubSource->fields().lookupField( fieldHubName );
100  const QStringList hubFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "HUB_FIELDS" ), context );
101 
102  QString fieldSpokeName = parameterAsString( parameters, QStringLiteral( "SPOKE_FIELD" ), context );
103  int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName );
104  const QStringList spokeFieldsToCopy = parameterAsFields( parameters, QStringLiteral( "SPOKE_FIELDS" ), context );
105 
106  if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
107  throw QgsProcessingException( QObject::tr( "Invalid ID field" ) );
108 
109  QgsFields hubOutFields;
110  QgsAttributeList hubFieldIndices;
111  if ( hubFieldsToCopy.empty() )
112  {
113  hubOutFields = hubSource->fields();
114  hubFieldIndices.reserve( hubOutFields.count() );
115  for ( int i = 0; i < hubOutFields.count(); ++i )
116  {
117  hubFieldIndices << i;
118  }
119  }
120  else
121  {
122  hubFieldIndices.reserve( hubOutFields.count() );
123  for ( const QString &field : hubFieldsToCopy )
124  {
125  int index = hubSource->fields().lookupField( field );
126  if ( index >= 0 )
127  {
128  hubFieldIndices << index;
129  hubOutFields.append( hubSource->fields().at( index ) );
130  }
131  }
132  }
133 
134  QgsAttributeList hubFields2Fetch = hubFieldIndices;
135  hubFields2Fetch << fieldHubIndex;
136 
137  QgsFields spokeOutFields;
138  QgsAttributeList spokeFieldIndices;
139  if ( spokeFieldsToCopy.empty() )
140  {
141  spokeOutFields = spokeSource->fields();
142  for ( int i = 0; i < spokeOutFields.count(); ++i )
143  {
144  spokeFieldIndices << i;
145  }
146  }
147  else
148  {
149  for ( const QString &field : spokeFieldsToCopy )
150  {
151  int index = spokeSource->fields().lookupField( field );
152  if ( index >= 0 )
153  {
154  spokeFieldIndices << index;
155  spokeOutFields.append( spokeSource->fields().at( index ) );
156  }
157  }
158  }
159 
160  QgsAttributeList spokeFields2Fetch = spokeFieldIndices;
161  spokeFields2Fetch << fieldSpokeIndex;
162 
163 
164  QgsFields fields = QgsProcessingUtils::combineFields( hubOutFields, spokeOutFields );
165 
167  bool hasZ = false;
168  if ( QgsWkbTypes::hasZ( hubSource->wkbType() ) || QgsWkbTypes::hasZ( spokeSource->wkbType() ) )
169  {
170  outType = QgsWkbTypes::addZ( outType );
171  hasZ = true;
172  }
173  bool hasM = false;
174  if ( QgsWkbTypes::hasM( hubSource->wkbType() ) || QgsWkbTypes::hasM( spokeSource->wkbType() ) )
175  {
176  outType = QgsWkbTypes::addM( outType );
177  hasM = true;
178  }
179 
180  QString dest;
181  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
182  outType, hubSource->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
183  if ( !sink )
184  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
185 
186  auto getPointFromFeature = [hasZ, hasM]( const QgsFeature & feature )->QgsPoint
187  {
188  QgsPoint p;
189  if ( feature.geometry().type() == QgsWkbTypes::PointGeometry && !feature.geometry().isMultipart() )
190  p = *static_cast< const QgsPoint *>( feature.geometry().constGet() );
191  else
192  p = *static_cast< const QgsPoint *>( feature.geometry().pointOnSurface().constGet() );
193  if ( hasZ && !p.is3D() )
194  p.addZValue( 0 );
195  if ( hasM && !p.isMeasure() )
196  p.addMValue( 0 );
197  return p;
198  };
199 
200  QgsFeatureIterator hubFeatures = hubSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( hubFields2Fetch ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
201  double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
202  int i = 0;
203  QgsFeature hubFeature;
204  while ( hubFeatures.nextFeature( hubFeature ) )
205  {
206  i++;
207  if ( feedback->isCanceled() )
208  {
209  break;
210  }
211 
212  feedback->setProgress( i * step );
213 
214  if ( !hubFeature.hasGeometry() )
215  continue;
216 
217  QgsPoint hubPoint = getPointFromFeature( hubFeature );
218 
219  // only keep selected attributes
220  QgsAttributes hubAttributes;
221  for ( int j = 0; j < hubFeature.attributes().count(); ++j )
222  {
223  if ( !hubFieldIndices.contains( j ) )
224  continue;
225  hubAttributes << hubFeature.attribute( j );
226  }
227 
228  QgsFeatureRequest spokeRequest = QgsFeatureRequest().setDestinationCrs( hubSource->sourceCrs(), context.transformContext() );
229  spokeRequest.setSubsetOfAttributes( spokeFields2Fetch );
230  spokeRequest.setFilterExpression( QgsExpression::createFieldEqualityExpression( fieldSpokeName, hubFeature.attribute( fieldHubIndex ) ) );
231 
232  QgsFeatureIterator spokeFeatures = spokeSource->getFeatures( spokeRequest, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
233  QgsFeature spokeFeature;
234  while ( spokeFeatures.nextFeature( spokeFeature ) )
235  {
236  if ( feedback->isCanceled() )
237  {
238  break;
239  }
240  if ( !spokeFeature.hasGeometry() )
241  continue;
242 
243  QgsPoint spokePoint = getPointFromFeature( spokeFeature );
244  QgsGeometry line( new QgsLineString( QVector< QgsPoint >() << hubPoint << spokePoint ) );
245 
246  QgsFeature outFeature;
247  QgsAttributes outAttributes = hubAttributes;
248 
249  // only keep selected attributes
250  QgsAttributes spokeAttributes;
251  for ( int j = 0; j < spokeFeature.attributes().count(); ++j )
252  {
253  if ( !spokeFieldIndices.contains( j ) )
254  continue;
255  spokeAttributes << spokeFeature.attribute( j );
256  }
257 
258  outAttributes.append( spokeAttributes );
259  outFeature.setAttributes( outAttributes );
260  outFeature.setGeometry( line );
261  sink->addFeature( outFeature, QgsFeatureSink::FastInsert );
262  }
263  }
264 
265  QVariantMap outputs;
266  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
267  return outputs;
268 }
269 
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature&#39;s geometries.
Wrapper for iterator of features from vector data provider or vector layer.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for providing feedback from a processing algorithm.
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
A vector layer or feature source field parameter for processing algorithms.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:469
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
Container of fields for a vector layer.
Definition: qgsfields.h:42
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:906
A feature sink output for processing algorithms.
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:68
bool isMeasure() const
Returns true if the geometry contains m values.
static Type addM(Type type)
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1027
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB)
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
static Type addZ(Type type)
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1002
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
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&#39;s index from the field name.
Definition: qgsfields.cpp:320
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value)
Create an expression allowing to evaluate if a field is equal to a value.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
An input feature source (such as vector layers) parameter for processing algorithms.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
Vector line layers.
Definition: qgsprocessing.h:49
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:480
static bool hasM(Type type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:956
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
A vector of attributes.
Definition: qgsattributes.h:57
Contains information about the context in which a processing algorithm is executed.
QgsAttributes attributes
Definition: qgsfeature.h:65