Skip to content
Permalink
Browse files

Port hub lines algorithm to new API

Improvements:
- transparent reprojection to match hub/spoke CRS
- keep all attributes from matched hub/spoke features
- don't break after matching one hub point to spoke - instead
join ALL hub/spoke points with matching id values
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent e035445 commit b4b39996d23cfeae09991f3234f0c221cc9af657
@@ -242,7 +242,9 @@ 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.
@@ -31,15 +31,15 @@
QgsGeometry,
QgsPointXY,
QgsWkbTypes,
QgsApplication,
QgsProcessingUtils)
QgsFeatureRequest,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSink,
QgsProcessingException,
QgsExpression)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterTableField
from processing.core.outputs import OutputVector

from processing.tools import dataobjects
from processing.tools import vector


class HubLines(QgisAlgorithm):
@@ -55,17 +55,21 @@ def group(self):
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(ParameterVector(self.HUBS,
self.tr('Hub layer')))
self.addParameter(ParameterTableField(self.HUB_FIELD,
self.tr('Hub ID field'), self.HUBS))
self.addParameter(ParameterVector(self.SPOKES,
self.tr('Spoke layer')))
self.addParameter(ParameterTableField(self.SPOKE_FIELD,
self.tr('Spoke ID field'), self.SPOKES))

self.addOutput(OutputVector(self.OUTPUT, self.tr('Hub lines'), datatype=[dataobjects.TYPE_VECTOR_LINE]))
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'
@@ -74,44 +78,61 @@ def displayName(self):
return self.tr('Hub lines')

def processAlgorithm(self, parameters, context, feedback):
layerHub = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.HUBS), context)
layerSpoke = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.SPOKES), context)
if parameters[self.SPOKES] == parameters[self.HUBS]:
raise QgsProcessingException(
self.tr('Same layer given for both hubs and spokes'))

fieldHub = self.getParameterValue(self.HUB_FIELD)
fieldSpoke = self.getParameterValue(self.SPOKE_FIELD)
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)

if layerHub.source() == layerSpoke.source():
raise GeoAlgorithmExecutionException(
self.tr('Same layer given for both hubs and spokes'))
fields = vector.combineFields(hub_source.fields(), spoke_source.fields())

writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layerSpoke.fields(), QgsWkbTypes.LineString,
layerSpoke.crs(), context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.LineString, hub_source.sourceCrs())

spokes = QgsProcessingUtils.getFeatures(layerSpoke, context)
hubs = QgsProcessingUtils.getFeatures(layerHub, context)
total = 100.0 / layerSpoke.featureCount() if layerSpoke.featureCount() else 0
hubs = hub_source.getFeatures()
total = 100.0 / hub_source.featureCount() if hub_source.featureCount() else 0

for current, spokepoint in enumerate(spokes):
p = spokepoint.geometry().boundingBox().center()
spokeX = p.x()
spokeY = p.y()
spokeId = str(spokepoint[fieldSpoke])
matching_field_types = hub_source.fields().at(field_hub_index).type() == spoke_source.fields().at(field_spoke_index).type()

for hubpoint in hubs:
hubId = str(hubpoint[fieldHub])
if hubId == spokeId:
p = hubpoint.geometry().boundingBox().center()
hubX = p.x()
hubY = p.y()
for current, hub_point in enumerate(hubs):
if feedback.isCanceled():
break

f = QgsFeature()
f.setAttributes(spokepoint.attributes())
f.setGeometry(QgsGeometry.fromPolyline(
[QgsPointXY(spokeX, spokeY), QgsPointXY(hubX, hubY)]))
writer.addFeature(f, QgsFeatureSink.FastInsert)
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))

del writer
return {self.OUTPUT: dest_id}
@@ -77,6 +77,7 @@
from .Hillshade import Hillshade
from .HubDistanceLines import HubDistanceLines
from .HubDistancePoints import HubDistancePoints
from .HubLines import HubLines
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
from .Intersection import Intersection
@@ -143,7 +144,6 @@
# from .ExtractByLocation import ExtractByLocation
# from .SelectByLocation import SelectByLocation
# from .SpatialJoin import SpatialJoin
# from .HubLines import HubLines
# from .GeometryConvert import GeometryConvert
# from .StatisticsByCategories import StatisticsByCategories
# from .FieldsCalculator import FieldsCalculator
@@ -188,7 +188,6 @@ def getAlgs(self):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
# FieldsPyculator(),
@@ -246,6 +245,7 @@ def getAlgs(self):
Hillshade(),
HubDistanceLines(),
HubDistancePoints(),
HubLines(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),
@@ -0,0 +1,27 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>spoke_points</Name>
<ElementPath>spoke_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>7</FeatureCount>
<ExtentXMin>1.27875</ExtentXMin>
<ExtentXMax>6.82625</ExtentXMax>
<ExtentYMin>-4.16750</ExtentYMin>
<ExtentYMax>3.88250</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>id</Name>
<ElementPath>id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>8</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ spoke_points.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>1.27875</gml:X><gml:Y>-4.1675</gml:Y></gml:coord>
<gml:coord><gml:X>6.826249999999999</gml:X><gml:Y>3.882499999999999</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:spoke_points fid="spoke_points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5.07625,-2.1725</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:name>point 1</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5.82,3.8825</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:name>point 2</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.62,1.4675</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:name>point 3</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.68625,1.23125</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:name>point 4</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.27875,-3.66875</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:name>point 4a</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.81625,-4.1675</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:name>point 4b</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
<gml:featureMember>
<ogr:spoke_points fid="spoke_points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.82625,-2.79375</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:name>point 8</ogr:name>
</ogr:spoke_points>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,43 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>hub_lines</Name>
<ElementPath>hub_lines</ElementPath>
<!--LINESTRING-->
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>7</FeatureCount>
<ExtentXMin>1.00000</ExtentXMin>
<ExtentXMax>7.00000</ExtentXMax>
<ExtentYMin>-4.16750</ExtentYMin>
<ExtentYMax>3.88250</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>id</Name>
<ElementPath>id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>id2</Name>
<ElementPath>id2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>fid_2</Name>
<ElementPath>fid_2</ElementPath>
<Type>String</Type>
<Width>14</Width>
</PropertyDefn>
<PropertyDefn>
<Name>id_2</Name>
<ElementPath>id_2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>8</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

0 comments on commit b4b3999

Please sign in to comment.
You can’t perform that action at this time.