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
This commit is contained in:
Nyall Dawson 2017-08-03 19:49:50 +10:00
parent e0354456e3
commit b4b39996d2
8 changed files with 306 additions and 50 deletions

View File

@ -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.

View File

@ -31,15 +31,15 @@ from qgis.core import (QgsFeature,
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 @@ class HubLines(QgisAlgorithm):
def __init__(self):
super().__init__()
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))
def tags(self):
return self.tr('join,points,lines,connect,hub,spoke').split(',')
self.addOutput(OutputVector(self.OUTPUT, self.tr('Hub lines'), datatype=[dataobjects.TYPE_VECTOR_LINE]))
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'
@ -74,44 +78,61 @@ class HubLines(QgisAlgorithm):
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)
fieldHub = self.getParameterValue(self.HUB_FIELD)
fieldSpoke = self.getParameterValue(self.SPOKE_FIELD)
if layerHub.source() == layerSpoke.source():
raise GeoAlgorithmExecutionException(
if parameters[self.SPOKES] == parameters[self.HUBS]:
raise QgsProcessingException(
self.tr('Same layer given for both hubs and spokes'))
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layerSpoke.fields(), QgsWkbTypes.LineString,
layerSpoke.crs(), context)
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)
spokes = QgsProcessingUtils.getFeatures(layerSpoke, context)
hubs = QgsProcessingUtils.getFeatures(layerHub, context)
total = 100.0 / layerSpoke.featureCount() if layerSpoke.featureCount() else 0
fields = vector.combineFields(hub_source.fields(), spoke_source.fields())
for current, spokepoint in enumerate(spokes):
p = spokepoint.geometry().boundingBox().center()
spokeX = p.x()
spokeY = p.y()
spokeId = str(spokepoint[fieldSpoke])
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.LineString, hub_source.sourceCrs())
for hubpoint in hubs:
hubId = str(hubpoint[fieldHub])
if hubId == spokeId:
p = hubpoint.geometry().boundingBox().center()
hubX = p.x()
hubY = p.y()
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(spokepoint.attributes())
f.setAttributes(hub_attributes + spoke_point.attributes())
f.setGeometry(QgsGeometry.fromPolyline(
[QgsPointXY(spokeX, spokeY), QgsPointXY(hubX, hubY)]))
writer.addFeature(f, QgsFeatureSink.FastInsert)
break
[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}

View File

@ -77,6 +77,7 @@ from .Heatmap import Heatmap
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 .ZonalStatistics import ZonalStatistics
# 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 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
# FieldsPyculator(),
@ -246,6 +245,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
Hillshade(),
HubDistanceLines(),
HubDistancePoints(),
HubLines(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>1</gml:X><gml:Y>-4.1675</gml:Y></gml:coord>
<gml:coord><gml:X>7</gml:X><gml:Y>3.8825</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:hub_lines fid="points.0">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>1,1 5.07625,-2.1725</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:fid_2>spoke_points.0</ogr:fid_2>
<ogr:id_2>1</ogr:id_2>
<ogr:name>point 1</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,3 5.82,3.8825</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:fid_2>spoke_points.1</ogr:fid_2>
<ogr:id_2>2</ogr:id_2>
<ogr:name>point 2</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,2 1.62,1.4675</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:fid_2>spoke_points.2</ogr:fid_2>
<ogr:id_2>3</ogr:id_2>
<ogr:name>point 3</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5,2 6.68625,1.23125</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:fid_2>spoke_points.3</ogr:fid_2>
<ogr:id_2>4</ogr:id_2>
<ogr:name>point 4</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5,2 1.27875,-3.66875</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:fid_2>spoke_points.4</ogr:fid_2>
<ogr:id_2>4</ogr:id_2>
<ogr:name>point 4a</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5,2 3.81625,-4.1675</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:fid_2>spoke_points.5</ogr:fid_2>
<ogr:id_2>4</ogr:id_2>
<ogr:name>point 4b</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_lines fid="points.7">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-1 6.82625,-2.79375</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:fid_2>spoke_points.6</ogr:fid_2>
<ogr:id_2>8</ogr:id_2>
<ogr:name>point 8</ogr:name>
</ogr:hub_lines>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -2216,6 +2216,22 @@ tests:
name: expected/hub_distance_lines.gml
type: vector
- algorithm: qgis:hublines
name: Hub lines
params:
HUBS:
name: points.gml
type: vector
SPOKES:
name: custom/spoke_points.gml
type: vector
HUB_FIELD: id
SPOKE_FIELD: id
results:
OUTPUT:
name: expected/hub_lines.gml
type: vector
# - algorithm: qgis:joinattributestable
# name: join the attribute table by common field
# params: