24 QString QgsJoinWithLinesAlgorithm::name()
const
26 return QStringLiteral(
"hublines" );
29 QString QgsJoinWithLinesAlgorithm::displayName()
const
31 return QObject::tr(
"Join by lines (hub lines)" );
34 QStringList QgsJoinWithLinesAlgorithm::tags()
const
36 return QObject::tr(
"join,connect,lines,points,hub,spoke,geodesic,great,circle" ).split(
',' );
39 QString QgsJoinWithLinesAlgorithm::group()
const
41 return QObject::tr(
"Vector analysis" );
44 QString QgsJoinWithLinesAlgorithm::groupId()
const
46 return QStringLiteral(
"vectoranalysis" );
49 void QgsJoinWithLinesAlgorithm::initAlgorithm(
const QVariantMap & )
52 QObject::tr(
"Hub layer" ) ) );
54 QObject::tr(
"Hub ID field" ), QVariant(), QStringLiteral(
"HUBS" ) ) );
57 QObject::tr(
"Hub layer fields to copy (leave empty to copy all fields)" ),
62 QObject::tr(
"Spoke layer" ) ) );
64 QObject::tr(
"Spoke ID field" ), QVariant(), QStringLiteral(
"SPOKES" ) ) );
67 QObject::tr(
"Spoke layer fields to copy (leave empty to copy all fields)" ),
73 auto distanceParam = qgis::make_unique< QgsProcessingParameterDistance >( QStringLiteral(
"GEODESIC_DISTANCE" ), QObject::tr(
"Distance between vertices (geodesic lines only)" ), 1000 );
76 distanceParam->setIsDynamic(
true );
78 distanceParam->setDynamicLayerParameterName( QStringLiteral(
"HUBS" ) );
79 addParameter( distanceParam.release() );
81 auto breakParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral(
"ANTIMERIDIAN_SPLIT" ), QObject::tr(
"Split lines at antimeridian (±180 degrees longitude)" ),
false );
83 addParameter( breakParam.release() );
88 QString QgsJoinWithLinesAlgorithm::shortHelpString()
const
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 "
99 QString QgsJoinWithLinesAlgorithm::shortDescription()
const
101 return QObject::tr(
"Creates lines joining two point layers, based on a common attribute value." );
104 QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance()
const
106 return new QgsJoinWithLinesAlgorithm();
111 if ( parameters.value( QStringLiteral(
"SPOKES" ) ) == parameters.value( QStringLiteral(
"HUBS" ) ) )
114 std::unique_ptr< QgsProcessingFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral(
"HUBS" ), context ) );
118 std::unique_ptr< QgsProcessingFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral(
"SPOKES" ), context ) );
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 );
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 );
130 if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
133 const bool geodesic = parameterAsBoolean( parameters, QStringLiteral(
"GEODESIC" ), context );
134 const double geodesicDistance = parameterAsDouble( parameters, QStringLiteral(
"GEODESIC_DISTANCE" ), context ) * 1000;
136 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, hubSource.get() );
138 if ( dynamicGeodesicDistance )
140 geodesicDistanceProperty = parameters.
value( QStringLiteral(
"GEODESIC_DISTANCE" ) ).value<
QgsProperty >();
143 const bool splitAntimeridian = parameterAsBoolean( parameters, QStringLiteral(
"ANTIMERIDIAN_SPLIT" ), context );
150 if ( hubFieldsToCopy.empty() )
152 hubOutFields = hubSource->fields();
153 hubFieldIndices.reserve( hubOutFields.
count() );
154 for (
int i = 0; i < hubOutFields.
count(); ++i )
156 hubFieldIndices << i;
161 hubFieldIndices.reserve( hubOutFields.
count() );
162 for (
const QString &
field : hubFieldsToCopy )
167 hubFieldIndices << index;
168 hubOutFields.
append( hubSource->fields().at( index ) );
174 hubFields2Fetch << fieldHubIndex;
178 if ( spokeFieldsToCopy.empty() )
180 spokeOutFields = spokeSource->fields();
181 spokeFieldIndices.reserve( spokeOutFields.
count() );
182 for (
int i = 0; i < spokeOutFields.
count(); ++i )
184 spokeFieldIndices << i;
189 for (
const QString &
field : spokeFieldsToCopy )
194 spokeFieldIndices << index;
195 spokeOutFields.
append( spokeSource->fields().at( index ) );
201 spokeFields2Fetch << fieldSpokeIndex;
221 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, dest, fields,
230 p = *
static_cast< const QgsPoint *
>( feature.geometry().constGet() );
232 p = *
static_cast< const QgsPoint *
>( feature.geometry().pointOnSurface().constGet() );
233 if ( hasZ && !p.
is3D() )
241 double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
257 QgsPoint hubPoint = getPointFromFeature( hubFeature );
261 for (
int j = 0; j < hubFeature.
attributes().count(); ++j )
263 if ( !hubFieldIndices.contains( j ) )
265 hubAttributes << hubFeature.
attribute( j );
274 while ( spokeFeatures.
nextFeature( spokeFeature ) )
283 QgsPoint spokePoint = getPointFromFeature( spokeFeature );
288 if ( splitAntimeridian )
293 double distance = geodesicDistance;
294 if ( dynamicGeodesicDistance )
297 distance = geodesicDistanceProperty.
valueAsDouble( expressionContext, distance );
300 std::unique_ptr< QgsMultiLineString > ml = qgis::make_unique< QgsMultiLineString >();
301 std::unique_ptr< QgsLineString > l = qgis::make_unique< QgsLineString >( QVector< QgsPoint >() << hubPoint );
303 QVector< QgsPointXY > points1 = points.at( 0 );
305 if ( points.count() == 1 )
309 l->append( &geodesicPoints );
310 if ( points.count() == 1 )
311 l->addVertex( spokePoint );
313 ml->addGeometry( l.release() );
314 if ( points.count() > 1 )
316 QVector< QgsPointXY > points2 = points.at( 1 );
318 l = qgis::make_unique< QgsLineString >( points2 );
320 l->addZValue( std::numeric_limits<double>::quiet_NaN() );
322 l->addMValue( std::numeric_limits<double>::quiet_NaN() );
324 l->addVertex( spokePoint );
325 ml->addGeometry( l.release() );
335 for (
int j = 0; j < spokeFeature.
attributes().count(); ++j )
337 if ( !spokeFieldIndices.contains( j ) )
339 spokeAttributes << spokeFeature.
attribute( j );
342 outAttributes.append( spokeAttributes );
350 outputs.insert( QStringLiteral(
"OUTPUT" ), dest );