Port hub lines algorithm to c++

Also:
- optimise a bit
- retain z/m values
- use point on surface for non-point layers, instead of
center of geometry bounding box
This commit is contained in:
Nyall Dawson 2017-10-11 18:34:14 +10:00
parent cac171a2e0
commit cc19d0a70d
10 changed files with 422 additions and 147 deletions

View File

@ -236,11 +236,6 @@ qgis:generatepointspixelcentroidsalongline:
qgis:generatepointspixelcentroidsinsidepolygons:
qgis:hublines:
This algorithm creates hub and spoke diagrams with lines drawn from points on the Spoke Point layer to matching points in the Hub Point layer.
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.
qgis:hypsometriccurves: >
This algorithm computes hypsometric curves for an input Digital Elevation Model. Curves are produced as table files in an output folder specified by the user.

View File

@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
Gridify.py
---------------------
Date : May 2010
Copyright : (C) 2010 by Michael Minn
Email : pyqgis at michaelminn dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from builtins import str
__author__ = 'Michael Minn'
__date__ = 'May 2010'
__copyright__ = '(C) 2010, Michael Minn'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.core import (QgsFeature,
QgsFeatureSink,
QgsGeometry,
QgsPointXY,
QgsWkbTypes,
QgsFeatureRequest,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSink,
QgsProcessingException,
QgsExpression,
QgsProcessingUtils)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.tools import vector
class HubLines(QgisAlgorithm):
HUBS = 'HUBS'
HUB_FIELD = 'HUB_FIELD'
SPOKES = 'SPOKES'
SPOKE_FIELD = 'SPOKE_FIELD'
OUTPUT = 'OUTPUT'
def group(self):
return self.tr('Vector analysis')
def __init__(self):
super().__init__()
def tags(self):
return self.tr('join,points,lines,connect,hub,spoke').split(',')
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.HUBS,
self.tr('Hub layer')))
self.addParameter(QgsProcessingParameterField(self.HUB_FIELD,
self.tr('Hub ID field'), parentLayerParameterName=self.HUBS))
self.addParameter(QgsProcessingParameterFeatureSource(self.SPOKES,
self.tr('Spoke layer')))
self.addParameter(QgsProcessingParameterField(self.SPOKE_FIELD,
self.tr('Spoke ID field'), parentLayerParameterName=self.SPOKES))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Hub lines'), QgsProcessing.TypeVectorLine))
def name(self):
return 'hublines'
def displayName(self):
return self.tr('Hub lines')
def processAlgorithm(self, parameters, context, feedback):
if parameters[self.SPOKES] == parameters[self.HUBS]:
raise QgsProcessingException(
self.tr('Same layer given for both hubs and spokes'))
hub_source = self.parameterAsSource(parameters, self.HUBS, context)
spoke_source = self.parameterAsSource(parameters, self.SPOKES, context)
field_hub = self.parameterAsString(parameters, self.HUB_FIELD, context)
field_hub_index = hub_source.fields().lookupField(field_hub)
field_spoke = self.parameterAsString(parameters, self.SPOKE_FIELD, context)
field_spoke_index = hub_source.fields().lookupField(field_spoke)
fields = QgsProcessingUtils.combineFields(hub_source.fields(), spoke_source.fields())
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.LineString, hub_source.sourceCrs())
hubs = hub_source.getFeatures()
total = 100.0 / hub_source.featureCount() if hub_source.featureCount() else 0
matching_field_types = hub_source.fields().at(field_hub_index).type() == spoke_source.fields().at(field_spoke_index).type()
for current, hub_point in enumerate(hubs):
if feedback.isCanceled():
break
if not hub_point.hasGeometry():
continue
p = hub_point.geometry().boundingBox().center()
hub_x = p.x()
hub_y = p.y()
hub_id = str(hub_point[field_hub])
hub_attributes = hub_point.attributes()
request = QgsFeatureRequest().setDestinationCrs(hub_source.sourceCrs())
if matching_field_types:
request.setFilterExpression(QgsExpression.createFieldEqualityExpression(field_spoke, hub_attributes[field_hub_index]))
spokes = spoke_source.getFeatures()
for spoke_point in spokes:
if feedback.isCanceled():
break
spoke_id = str(spoke_point[field_spoke])
if hub_id == spoke_id:
p = spoke_point.geometry().boundingBox().center()
spoke_x = p.x()
spoke_y = p.y()
f = QgsFeature()
f.setAttributes(hub_attributes + spoke_point.attributes())
f.setGeometry(QgsGeometry.fromPolyline(
[QgsPointXY(hub_x, hub_y), QgsPointXY(spoke_x, spoke_y)]))
sink.addFeature(f, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return {self.OUTPUT: dest_id}

View File

@ -84,7 +84,6 @@ from .Heatmap import Heatmap
from .Hillshade import Hillshade
from .HubDistanceLines import HubDistanceLines
from .HubDistancePoints import HubDistancePoints
from .HubLines import HubLines
from .HypsometricCurves import HypsometricCurves
from .IdwInterpolation import IdwInterpolation
from .ImportIntoPostGIS import ImportIntoPostGIS
@ -216,7 +215,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
Hillshade(),
HubDistanceLines(),
HubDistancePoints(),
HubLines(),
HypsometricCurves(),
IdwInterpolation(),
ImportIntoPostGIS(),

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ hub_lines_multi_to_z.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y><gml:Z>0</gml:Z></gml:coord>
<gml:coord><gml:X>7</gml:X><gml:Y>2</gml:Y><gml:Z>4</gml:Z></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:hub_lines_multi_to_z fid="points.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5,2,0 2,2,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:d>2</ogr:d>
<ogr:fid_2>points.2</ogr:fid_2>
<ogr:elev>2</ogr:elev>
</ogr:hub_lines_multi_to_z>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_multi_to_z fid="points.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>0,-5,0 1,1,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:d>3</ogr:d>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:elev>3</ogr:elev>
</ogr:hub_lines_multi_to_z>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_multi_to_z fid="points.7">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-1,0 0,-1,4</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:d>4</ogr:d>
<ogr:fid_2>points.8</ogr:fid_2>
<ogr:elev>4</ogr:elev>
</ogr:hub_lines_multi_to_z>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="hub_lines_multi_to_z" type="ogr:hub_lines_multi_to_z_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="hub_lines_multi_to_z_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="d" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="fid_2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="elev" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ hub_lines_to_poly.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0.5</gml:X><gml:Y>0</gml:Y></gml:coord>
<gml:coord><gml:X>4.672552783109405</gml:X><gml:Y>5.588675623800385</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.0">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>0.5,0.5 1,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>aa</ogr:name>
<ogr:intval>1</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2.5,5.5 1,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>bb</ogr:name>
<ogr:intval>1</ogr:intval>
<ogr:floatval>0.123</ogr:floatval>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.4">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>4,0 1,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>aa</ogr:name>
<ogr:intval>1</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2.94337811900192,4.92360844529751 1,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>bb</ogr:name>
<ogr:intval>1</ogr:intval>
<ogr:floatval>0.123</ogr:floatval>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.6">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>4.67255278310941,5.32264875239923 1,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>bb</ogr:name>
<ogr:intval>1</ogr:intval>
<ogr:floatval>0.123</ogr:floatval>
<ogr:fid_2>points.0</ogr:fid_2>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines_to_poly fid="polys.8">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3.12072936660269,5.58867562380038 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:name>bb</ogr:name>
<ogr:intval>2</ogr:intval>
<ogr:floatval>0.123</ogr:floatval>
<ogr:fid_2>points.1</ogr:fid_2>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
</ogr:hub_lines_to_poly>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="hub_lines_to_poly" type="ogr:hub_lines_to_poly_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="hub_lines_to_poly_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="name" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="2"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="intval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="floatval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="fid_2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="id" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="id2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -2444,7 +2444,7 @@ tests:
name: expected/hub_distance_lines.gml
type: vector
- algorithm: qgis:hublines
- algorithm: native:hublines
name: Hub lines
params:
HUBS:
@ -2460,6 +2460,46 @@ tests:
name: expected/hub_lines.gml
type: vector
- algorithm: native:hublines
name: Hub lines (with polygons)
params:
HUBS:
name: dissolve_polys.gml
type: vector
HUB_FIELD: intval
SPOKES:
name: points.gml
type: vector
SPOKE_FIELD: id
results:
OUTPUT:
name: expected/hub_lines_to_poly.gml
type: vector
compare:
fields:
fid: skip
fid_2: skip
- algorithm: native:hublines
name: Hub lines ( with z )
params:
HUBS:
name: multipoints.gml
type: vector
HUB_FIELD: d
SPOKES:
name: pointsz.gml
type: vector
SPOKE_FIELD: elev
results:
OUTPUT:
name: expected/hub_lines_multi_to_z.gml
type: vector
compare:
fields:
fid: skip
fid_2: skip
- algorithm: qgis:pointstopath
name: Points to path (non grouped)
params:

View File

@ -91,6 +91,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsMeanCoordinatesAlgorithm() );
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
addAlgorithm( new QgsJoinByAttributeAlgorithm() );
addAlgorithm( new QgsJoinWithLinesAlgorithm() );
}
void QgsSaveSelectedFeatures::initAlgorithm( const QVariantMap & )
@ -2878,4 +2879,134 @@ QVariantMap QgsJoinByAttributeAlgorithm::processAlgorithm( const QVariantMap &pa
}
void QgsJoinWithLinesAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "HUBS" ),
QObject::tr( "Hub layer" ) ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "HUB_FIELD" ),
QObject::tr( "Hub ID field" ), QVariant(), QStringLiteral( "HUBS" ) ) );
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "SPOKES" ),
QObject::tr( "Spoke layer" ) ) );
addParameter( new QgsProcessingParameterField( QStringLiteral( "SPOKE_FIELD" ),
QObject::tr( "Spoke ID field" ), QVariant(), QStringLiteral( "SPOKES" ) ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Hub lines" ), QgsProcessing::TypeVectorLine ) );
}
QString QgsJoinWithLinesAlgorithm::shortHelpString() const
{
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"
"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"
"If input layers are not point layers, a point on the surface of the geometries will be taken as the connecting location." );
}
QgsJoinWithLinesAlgorithm *QgsJoinWithLinesAlgorithm::createInstance() const
{
return new QgsJoinWithLinesAlgorithm();
}
QVariantMap QgsJoinWithLinesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
if ( parameters.value( QStringLiteral( "SPOKES" ) ) == parameters.value( QStringLiteral( "HUBS" ) ) )
throw QgsProcessingException( QObject::tr( "Same layer given for both hubs and spokes" ) );
std::unique_ptr< QgsFeatureSource > hubSource( parameterAsSource( parameters, QStringLiteral( "HUBS" ), context ) );
std::unique_ptr< QgsFeatureSource > spokeSource( parameterAsSource( parameters, QStringLiteral( "SPOKES" ), context ) );
if ( !hubSource || !spokeSource )
return QVariantMap();
QString fieldHubName = parameterAsString( parameters, QStringLiteral( "HUB_FIELD" ), context );
int fieldHubIndex = hubSource->fields().lookupField( fieldHubName );
QString fieldSpokeName = parameterAsString( parameters, QStringLiteral( "SPOKE_FIELD" ), context );
int fieldSpokeIndex = spokeSource->fields().lookupField( fieldSpokeName );
if ( fieldHubIndex < 0 || fieldSpokeIndex < 0 )
throw QgsProcessingException( QObject::tr( "Invalid ID field" ) );
QgsFields fields = QgsProcessingUtils::combineFields( hubSource->fields(), spokeSource->fields() );
QgsWkbTypes::Type outType = QgsWkbTypes::LineString;
bool hasZ = false;
if ( QgsWkbTypes::hasZ( hubSource->wkbType() ) || QgsWkbTypes::hasZ( spokeSource->wkbType() ) )
{
outType = QgsWkbTypes::addZ( outType );
hasZ = true;
}
bool hasM = false;
if ( QgsWkbTypes::hasM( hubSource->wkbType() ) || QgsWkbTypes::hasM( spokeSource->wkbType() ) )
{
outType = QgsWkbTypes::addM( outType );
hasM = true;
}
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields,
outType, hubSource->sourceCrs() ) );
if ( !sink )
return QVariantMap();
auto getPointFromFeature = [hasZ, hasM]( const QgsFeature & feature )->QgsPoint
{
QgsPoint p;
if ( feature.geometry().type() == QgsWkbTypes::PointGeometry && !feature.geometry().isMultipart() )
p = *static_cast< QgsPoint *>( feature.geometry().geometry() );
else
p = *static_cast< QgsPoint *>( feature.geometry().pointOnSurface().geometry() );
if ( hasZ && !p.is3D() )
p.addZValue( 0 );
if ( hasM && !p.isMeasure() )
p.addMValue( 0 );
return p;
};
QgsFeatureIterator hubFeatures = hubSource->getFeatures();
double step = hubSource->featureCount() > 0 ? 100.0 / hubSource->featureCount() : 1;
int i = 0;
QgsFeature hubFeature;
while ( hubFeatures.nextFeature( hubFeature ) )
{
i++;
if ( feedback->isCanceled() )
{
break;
}
feedback->setProgress( i * step );
if ( !hubFeature.hasGeometry() )
continue;
QgsPoint hubPoint = getPointFromFeature( hubFeature );
QgsAttributes hubAttributes = hubFeature.attributes();
QgsFeatureRequest spokeRequest = QgsFeatureRequest().setDestinationCrs( hubSource->sourceCrs() );
spokeRequest.setFilterExpression( QgsExpression::createFieldEqualityExpression( fieldSpokeName, hubFeature.attribute( fieldHubIndex ) ) );
QgsFeatureIterator spokeFeatures = spokeSource->getFeatures( spokeRequest );
QgsFeature spokeFeature;
while ( spokeFeatures.nextFeature( spokeFeature ) )
{
if ( feedback->isCanceled() )
{
break;
}
QgsPoint spokePoint = getPointFromFeature( spokeFeature );
QgsGeometry line( new QgsLineString( QVector< QgsPoint >() << hubPoint << spokePoint ) );
QgsFeature outFeature;
QgsAttributes outAttributes = hubAttributes;
outAttributes.append( spokeFeature.attributes() );
outFeature.setAttributes( outAttributes );
outFeature.setGeometry( line );
sink->addFeature( outFeature, QgsFeatureSink::FastInsert );
}
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}
///@endcond

View File

@ -871,6 +871,30 @@ class QgsJoinByAttributeAlgorithm : public QgsProcessingAlgorithm
};
/**
* Native join by lines ("hub lines") algorithm.
*/
class QgsJoinWithLinesAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsJoinWithLinesAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "hublines" ); }
QString displayName() const override { return QObject::tr( "Join by lines (hub lines)" ); }
virtual QStringList tags() const override { return QObject::tr( "join,connect,lines,points,hub,spoke" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector analysis" ); }
QString shortHelpString() const override;
QgsJoinWithLinesAlgorithm *createInstance() const override SIP_FACTORY;
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSNATIVEALGORITHMS_H