25 QString QgsJoinWithLinesAlgorithm::name()
const
27 return QStringLiteral(
"hublines" );
30 QString QgsJoinWithLinesAlgorithm::displayName()
const
32 return QObject::tr(
"Join by lines (hub lines)" );
35 QStringList QgsJoinWithLinesAlgorithm::tags()
const
37 return QObject::tr(
"join,connect,lines,points,hub,spoke,geodesic,great,circle" ).split(
',' );
40 QString QgsJoinWithLinesAlgorithm::group()
const
42 return QObject::tr(
"Vector analysis" );
45 QString QgsJoinWithLinesAlgorithm::groupId()
const
47 return QStringLiteral(
"vectoranalysis" );
50 void QgsJoinWithLinesAlgorithm::initAlgorithm(
const QVariantMap & )
53 QObject::tr(
"Hub layer" ) ) );
55 QObject::tr(
"Hub ID field" ), QVariant(), QStringLiteral(
"HUBS" ) ) );
58 QObject::tr(
"Hub layer fields to copy (leave empty to copy all fields)" ),
63 QObject::tr(
"Spoke layer" ) ) );
65 QObject::tr(
"Spoke ID field" ), QVariant(), QStringLiteral(
"SPOKES" ) ) );
68 QObject::tr(
"Spoke layer fields to copy (leave empty to copy all fields)" ),
74 auto distanceParam = std::make_unique< QgsProcessingParameterDistance >( QStringLiteral(
"GEODESIC_DISTANCE" ), QObject::tr(
"Distance between vertices (geodesic lines only)" ), 1000 );
77 distanceParam->setIsDynamic(
true );
79 distanceParam->setDynamicLayerParameterName( QStringLiteral(
"HUBS" ) );
80 addParameter( distanceParam.release() );
82 auto breakParam = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral(
"ANTIMERIDIAN_SPLIT" ), QObject::tr(
"Split lines at antimeridian (±180 degrees longitude)" ),
false );
84 addParameter( breakParam.release() );
89 QString QgsJoinWithLinesAlgorithm::shortHelpString()
const
91 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"
92 "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"
93 "If input layers are not point layers, a point on the surface of the geometries will be taken as the connecting location.\n\n"
94 "Optionally, geodesic lines can be created, which represent the shortest path on the surface of an ellipsoid. When "
95 "geodesic mode is used, it is possible to split the created lines at the antimeridian (±180 degrees longitude), which can improve "
96 "rendering of the lines. Additionally, the distance between vertices can be specified. A smaller distance results in a denser, more "
100 QString QgsJoinWithLinesAlgorithm::shortDescription()
const
102 return QObject::tr(
"Creates lines joining two point layers, based on a common attribute value." );
105 QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance()
const
107 return new QgsJoinWithLinesAlgorithm();
112 if ( parameters.value( QStringLiteral(
"SPOKES" ) ) == parameters.value( QStringLiteral(
"HUBS" ) ) )
115 std::unique_ptr< QgsProcessingFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral(
"HUBS" ), context ) );
119 std::unique_ptr< QgsProcessingFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral(
"SPOKES" ), context ) );
123 const QString fieldHubName = parameterAsString( parameters, QStringLiteral(
"HUB_FIELD" ), context );
124 const int fieldHubIndex = hubSource->fields().lookupField( fieldHubName );
125 const QStringList hubFieldsToCopy = parameterAsFields( parameters, QStringLiteral(
"HUB_FIELDS" ), context );
127 const QString fieldSpokeName = parameterAsString( parameters, QStringLiteral(
"SPOKE_FIELD" ), context );
128 const int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName );
129 const QStringList spokeFieldsToCopy = parameterAsFields( parameters, QStringLiteral(
"SPOKE_FIELDS" ), context );
131 if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
134 const bool geodesic = parameterAsBoolean( parameters, QStringLiteral(
"GEODESIC" ), context );
135 const double geodesicDistance = parameterAsDouble( parameters, QStringLiteral(
"GEODESIC_DISTANCE" ), context ) * 1000;
137 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, hubSource.get() );
139 if ( dynamicGeodesicDistance )
141 geodesicDistanceProperty = parameters.
value( QStringLiteral(
"GEODESIC_DISTANCE" ) ).value<
QgsProperty >();
144 const bool splitAntimeridian = parameterAsBoolean( parameters, QStringLiteral(
"ANTIMERIDIAN_SPLIT" ), context );
151 if ( hubFieldsToCopy.empty() )
153 hubOutFields = hubSource->fields();
154 hubFieldIndices.reserve( hubOutFields.
count() );
155 for (
int i = 0; i < hubOutFields.
count(); ++i )
157 hubFieldIndices << i;
162 hubFieldIndices.reserve( hubOutFields.
count() );
163 for (
const QString &
field : hubFieldsToCopy )
168 hubFieldIndices << index;
169 hubOutFields.
append( hubSource->fields().at( index ) );
175 hubFields2Fetch << fieldHubIndex;
179 if ( spokeFieldsToCopy.empty() )
181 spokeOutFields = spokeSource->fields();
182 spokeFieldIndices.reserve( spokeOutFields.
count() );
183 for (
int i = 0; i < spokeOutFields.
count(); ++i )
185 spokeFieldIndices << i;
190 for (
const QString &
field : spokeFieldsToCopy )
195 spokeFieldIndices << index;
196 spokeOutFields.
append( spokeSource->fields().at( index ) );
202 spokeFields2Fetch << fieldSpokeIndex;
222 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, dest, fields,
231 p = *
static_cast< const QgsPoint *
>( feature.geometry().constGet() );
233 p = *
static_cast< const QgsPoint *
>( feature.geometry().pointOnSurface().constGet() );
234 if ( hasZ && !p.
is3D() )
242 const double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
258 const QgsPoint hubPoint = getPointFromFeature( hubFeature );
262 for (
int j = 0; j < hubFeature.
attributes().count(); ++j )
264 if ( !hubFieldIndices.contains( j ) )
266 hubAttributes << hubFeature.
attribute( j );
275 while ( spokeFeatures.
nextFeature( spokeFeature ) )
284 const QgsPoint spokePoint = getPointFromFeature( spokeFeature );
289 if ( splitAntimeridian )
294 double distance = geodesicDistance;
295 if ( dynamicGeodesicDistance )
298 distance = geodesicDistanceProperty.
valueAsDouble( expressionContext, distance );
301 std::unique_ptr< QgsMultiLineString > ml = std::make_unique< QgsMultiLineString >();
302 std::unique_ptr< QgsLineString > l = std::make_unique< QgsLineString >( QVector< QgsPoint >() << hubPoint );
304 QVector< QgsPointXY > points1 = points.at( 0 );
306 if ( points.count() == 1 )
310 l->append( &geodesicPoints );
311 if ( points.count() == 1 )
312 l->addVertex( spokePoint );
314 ml->addGeometry( l.release() );
315 if ( points.count() > 1 )
317 QVector< QgsPointXY > points2 = points.at( 1 );
319 l = std::make_unique< QgsLineString >( points2 );
321 l->addZValue( std::numeric_limits<double>::quiet_NaN() );
323 l->addMValue( std::numeric_limits<double>::quiet_NaN() );
325 l->addVertex( spokePoint );
326 ml->addGeometry( l.release() );
336 for (
int j = 0; j < spokeFeature.
attributes().count(); ++j )
338 if ( !spokeFieldIndices.contains( j ) )
340 spokeAttributes << spokeFeature.
attribute( j );
343 outAttributes.append( spokeAttributes );
352 outputs.insert( QStringLiteral(
"OUTPUT" ), dest );