Skip to content

Commit fc1746e

Browse files
committedAug 5, 2017
Port Hub Distance (points) to new API
Improvements: - handle different CRS between points and hubs - add unit test
1 parent 0930e18 commit fc1746e

File tree

7 files changed

+260
-58
lines changed

7 files changed

+260
-58
lines changed
 

‎python/plugins/processing/algs/qgis/HubDistancePoints.py

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,32 @@
3333
QgsDistanceArea,
3434
QgsFeature,
3535
QgsFeatureRequest,
36+
QgsSpatialIndex,
3637
QgsWkbTypes,
37-
QgsApplication,
38-
QgsProject,
39-
QgsProcessingUtils)
38+
QgsUnitTypes,
39+
QgsProcessing,
40+
QgsProcessingUtils,
41+
QgsProcessingParameterFeatureSource,
42+
QgsProcessingParameterField,
43+
QgsProcessingParameterEnum,
44+
QgsProcessingParameterFeatureSink,
45+
QgsProcessingException)
4046
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
41-
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
42-
from processing.core.parameters import ParameterVector
43-
from processing.core.parameters import ParameterTableField
44-
from processing.core.parameters import ParameterSelection
45-
from processing.core.outputs import OutputVector
46-
47-
from processing.tools import dataobjects
48-
49-
from math import sqrt
5047

5148

5249
class HubDistancePoints(QgisAlgorithm):
53-
POINTS = 'POINTS'
50+
INPUT = 'INPUT'
5451
HUBS = 'HUBS'
5552
FIELD = 'FIELD'
5653
UNIT = 'UNIT'
5754
OUTPUT = 'OUTPUT'
55+
LAYER_UNITS = 'LAYER_UNITS'
5856

59-
UNITS = ['Meters',
60-
'Feet',
61-
'Miles',
62-
'Kilometers',
63-
'Layer units'
57+
UNITS = [QgsUnitTypes.DistanceMeters,
58+
QgsUnitTypes.DistanceFeet,
59+
QgsUnitTypes.DistanceMiles,
60+
QgsUnitTypes.DistanceKilometers,
61+
LAYER_UNITS
6462
]
6563

6664
def group(self):
@@ -76,16 +74,16 @@ def initAlgorithm(self, config=None):
7674
self.tr('Kilometers'),
7775
self.tr('Layer units')]
7876

79-
self.addParameter(ParameterVector(self.POINTS,
80-
self.tr('Source points layer')))
81-
self.addParameter(ParameterVector(self.HUBS,
82-
self.tr('Destination hubs layer')))
83-
self.addParameter(ParameterTableField(self.FIELD,
84-
self.tr('Hub layer name attribute'), self.HUBS))
85-
self.addParameter(ParameterSelection(self.UNIT,
86-
self.tr('Measurement unit'), self.units))
77+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
78+
self.tr('Source points layer')))
79+
self.addParameter(QgsProcessingParameterFeatureSource(self.HUBS,
80+
self.tr('Destination hubs layer')))
81+
self.addParameter(QgsProcessingParameterField(self.FIELD,
82+
self.tr('Hub layer name attribute'), parentLayerParameterName=self.HUBS))
83+
self.addParameter(QgsProcessingParameterEnum(self.UNIT,
84+
self.tr('Measurement unit'), self.units))
8785

88-
self.addOutput(OutputVector(self.OUTPUT, self.tr('Hub distance'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
86+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Hub distance'), QgsProcessing.TypeVectorPoint))
8987

9088
def name(self):
9189
return 'distancetonearesthubpoints'
@@ -94,61 +92,62 @@ def displayName(self):
9492
return self.tr('Distance to nearest hub (points)')
9593

9694
def processAlgorithm(self, parameters, context, feedback):
97-
layerPoints = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.POINTS), context)
98-
layerHubs = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.HUBS), context)
99-
fieldName = self.getParameterValue(self.FIELD)
95+
if parameters[self.INPUT] == parameters[self.HUBS]:
96+
raise QgsProcessingException(
97+
self.tr('Same layer given for both hubs and spokes'))
10098

101-
units = self.UNITS[self.getParameterValue(self.UNIT)]
99+
point_source = self.parameterAsSource(parameters, self.INPUT, context)
100+
hub_source = self.parameterAsSource(parameters, self.HUBS, context)
101+
fieldName = self.parameterAsString(parameters, self.FIELD, context)
102102

103-
if layerPoints.source() == layerHubs.source():
104-
raise GeoAlgorithmExecutionException(
105-
self.tr('Same layer given for both hubs and spokes'))
103+
units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)]
106104

107-
fields = layerPoints.fields()
105+
fields = point_source.fields()
108106
fields.append(QgsField('HubName', QVariant.String))
109107
fields.append(QgsField('HubDist', QVariant.Double))
110108

111-
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layerPoints.crs(),
112-
context)
109+
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
110+
fields, QgsWkbTypes.Point, point_source.sourceCrs())
113111

114-
index = QgsProcessingUtils.createSpatialIndex(layerHubs, context)
112+
index = QgsSpatialIndex(hub_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(point_source.sourceCrs())))
115113

116114
distance = QgsDistanceArea()
117-
distance.setSourceCrs(layerPoints.crs())
115+
distance.setSourceCrs(point_source.sourceCrs())
118116
distance.setEllipsoid(context.project().ellipsoid())
119117

120118
# Scan source points, find nearest hub, and write to output file
121-
features = QgsProcessingUtils.getFeatures(layerPoints, context)
122-
total = 100.0 / layerPoints.featureCount() if layerPoints.featureCount() else 0
119+
features = point_source.getFeatures()
120+
total = 100.0 / point_source.featureCount() if point_source.featureCount() else 0
123121
for current, f in enumerate(features):
122+
if feedback.isCanceled():
123+
break
124+
125+
if not f.hasGeometry():
126+
sink.addFeature(f, QgsFeatureSink.FastInsert)
127+
continue
128+
124129
src = f.geometry().boundingBox().center()
125130

126131
neighbors = index.nearestNeighbor(src, 1)
127-
ft = next(layerHubs.getFeatures(QgsFeatureRequest().setFilterFid(neighbors[0]).setSubsetOfAttributes([fieldName], layerHubs.fields())))
132+
ft = next(hub_source.getFeatures(QgsFeatureRequest().setFilterFid(neighbors[0]).setSubsetOfAttributes([fieldName], hub_source.fields()).setDestinationCrs(point_source.sourceCrs())))
128133
closest = ft.geometry().boundingBox().center()
129134
hubDist = distance.measureLine(src, closest)
130135

136+
if units != self.LAYER_UNITS:
137+
hub_dist_in_desired_units = distance.convertLengthMeasurement(hubDist, units)
138+
else:
139+
hub_dist_in_desired_units = hubDist
140+
131141
attributes = f.attributes()
132142
attributes.append(ft[fieldName])
133-
if units == 'Feet':
134-
attributes.append(hubDist * 3.2808399)
135-
elif units == 'Miles':
136-
attributes.append(hubDist * 0.000621371192)
137-
elif units == 'Kilometers':
138-
attributes.append(hubDist / 1000.0)
139-
elif units != 'Meters':
140-
attributes.append(sqrt(
141-
pow(src.x() - closest.x(), 2.0) +
142-
pow(src.y() - closest.y(), 2.0)))
143-
else:
144-
attributes.append(hubDist)
143+
attributes.append(hub_dist_in_desired_units)
145144

146145
feat = QgsFeature()
147146
feat.setAttributes(attributes)
148147

149148
feat.setGeometry(QgsGeometry.fromPoint(src))
150149

151-
writer.addFeature(feat, QgsFeatureSink.FastInsert)
150+
sink.addFeature(feat, QgsFeatureSink.FastInsert)
152151
feedback.setProgress(int(current * total))
153152

154-
del writer
153+
return {self.OUTPUT: dest_id}

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
from .GridPolygon import GridPolygon
7676
from .Heatmap import Heatmap
7777
from .Hillshade import Hillshade
78+
from .HubDistancePoints import HubDistancePoints
7879
from .ImportIntoPostGIS import ImportIntoPostGIS
7980
from .ImportIntoSpatialite import ImportIntoSpatialite
8081
from .Intersection import Intersection
@@ -141,7 +142,7 @@
141142
# from .ExtractByLocation import ExtractByLocation
142143
# from .SelectByLocation import SelectByLocation
143144
# from .SpatialJoin import SpatialJoin
144-
# from .HubDistancePoints import HubDistancePoints
145+
145146
# from .HubDistanceLines import HubDistanceLines
146147
# from .HubLines import HubLines
147148
# from .GeometryConvert import GeometryConvert
@@ -188,7 +189,6 @@ def getAlgs(self):
188189
# SelectByLocation(),
189190
# ExtractByLocation(),
190191
# SpatialJoin(),
191-
# HubDistancePoints(),
192192
# HubDistanceLines(), HubLines(),
193193
# GeometryConvert(), FieldsCalculator(),
194194
# JoinAttributes(),
@@ -245,6 +245,7 @@ def getAlgs(self):
245245
GridPolygon(),
246246
Heatmap(),
247247
Hillshade(),
248+
HubDistancePoints(),
248249
ImportIntoPostGIS(),
249250
ImportIntoSpatialite(),
250251
Intersection(),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>hub_points</Name>
4+
<ElementPath>hub_points</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>3</FeatureCount>
10+
<ExtentXMin>1.34481</ExtentXMin>
11+
<ExtentXMax>6.29897</ExtentXMax>
12+
<ExtentYMin>-1.25947</ExtentYMin>
13+
<ExtentYMax>2.27221</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>name</Name>
17+
<ElementPath>name</ElementPath>
18+
<Type>String</Type>
19+
<Width>6</Width>
20+
</PropertyDefn>
21+
</GMLFeatureClass>
22+
</GMLFeatureClassList>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ hub_points.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>1.344807662693645</gml:X><gml:Y>-1.259467184083282</gml:Y></gml:coord>
10+
<gml:coord><gml:X>6.298968246635251</gml:X><gml:Y>2.272211648033507</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:hub_points fid="hub_points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.34480766269365,-1.25946718408328</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:name>point1</ogr:name>
18+
</ogr:hub_points>
19+
</gml:featureMember>
20+
<gml:featureMember>
21+
<ogr:hub_points fid="hub_points.1">
22+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.29896824663525,0.138489020296281</gml:coordinates></gml:Point></ogr:geometryProperty>
23+
<ogr:name>point2</ogr:name>
24+
</ogr:hub_points>
25+
</gml:featureMember>
26+
<gml:featureMember>
27+
<ogr:hub_points fid="hub_points.2">
28+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.12290985247467,2.27221164803351</gml:coordinates></gml:Point></ogr:geometryProperty>
29+
<ogr:name>point3</ogr:name>
30+
</ogr:hub_points>
31+
</gml:featureMember>
32+
</ogr:FeatureCollection>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>hub_distance_points</Name>
4+
<ElementPath>hub_distance_points</ElementPath>
5+
<!--POINT-->
6+
<GeometryType>1</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>9</FeatureCount>
10+
<ExtentXMin>0.00000</ExtentXMin>
11+
<ExtentXMax>8.00000</ExtentXMax>
12+
<ExtentYMin>-5.00000</ExtentYMin>
13+
<ExtentYMax>3.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>id</Name>
17+
<ElementPath>id</ElementPath>
18+
<Type>Integer</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>id2</Name>
22+
<ElementPath>id2</ElementPath>
23+
<Type>Integer</Type>
24+
</PropertyDefn>
25+
<PropertyDefn>
26+
<Name>HubName</Name>
27+
<ElementPath>HubName</ElementPath>
28+
<Type>String</Type>
29+
<Width>6</Width>
30+
</PropertyDefn>
31+
<PropertyDefn>
32+
<Name>HubDist</Name>
33+
<ElementPath>HubDist</ElementPath>
34+
<Type>Real</Type>
35+
</PropertyDefn>
36+
</GMLFeatureClass>
37+
</GMLFeatureClassList>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:hub_distance_points fid="points.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:id>1</ogr:id>
18+
<ogr:id2>2</ogr:id2>
19+
<ogr:HubName>point1</ogr:HubName>
20+
<ogr:HubDist>254434.675423572</ogr:HubDist>
21+
</ogr:hub_distance_points>
22+
</gml:featureMember>
23+
<gml:featureMember>
24+
<ogr:hub_distance_points fid="points.1">
25+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
26+
<ogr:id>2</ogr:id>
27+
<ogr:id2>1</ogr:id2>
28+
<ogr:HubName>point3</ogr:HubName>
29+
<ogr:HubDist>82164.2455422206</ogr:HubDist>
30+
</ogr:hub_distance_points>
31+
</gml:featureMember>
32+
<gml:featureMember>
33+
<ogr:hub_distance_points fid="points.2">
34+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
35+
<ogr:id>3</ogr:id>
36+
<ogr:id2>0</ogr:id2>
37+
<ogr:HubName>point3</ogr:HubName>
38+
<ogr:HubDist>128622.227687308</ogr:HubDist>
39+
</ogr:hub_distance_points>
40+
</gml:featureMember>
41+
<gml:featureMember>
42+
<ogr:hub_distance_points fid="points.3">
43+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
44+
<ogr:id>4</ogr:id>
45+
<ogr:id2>2</ogr:id2>
46+
<ogr:HubName>point3</ogr:HubName>
47+
<ogr:HubDist>211142.486929284</ogr:HubDist>
48+
</ogr:hub_distance_points>
49+
</gml:featureMember>
50+
<gml:featureMember>
51+
<ogr:hub_distance_points fid="points.4">
52+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
53+
<ogr:id>5</ogr:id>
54+
<ogr:id2>1</ogr:id2>
55+
<ogr:HubName>point3</ogr:HubName>
56+
<ogr:HubDist>172016.876891364</ogr:HubDist>
57+
</ogr:hub_distance_points>
58+
</gml:featureMember>
59+
<gml:featureMember>
60+
<ogr:hub_distance_points fid="points.5">
61+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
62+
<ogr:id>6</ogr:id>
63+
<ogr:id2>0</ogr:id2>
64+
<ogr:HubName>point1</ogr:HubName>
65+
<ogr:HubDist>442487.532089586</ogr:HubDist>
66+
</ogr:hub_distance_points>
67+
</gml:featureMember>
68+
<gml:featureMember>
69+
<ogr:hub_distance_points fid="points.6">
70+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
71+
<ogr:id>7</ogr:id>
72+
<ogr:id2>0</ogr:id2>
73+
<ogr:HubName>point2</ogr:HubName>
74+
<ogr:HubDist>227856.24000978</ogr:HubDist>
75+
</ogr:hub_distance_points>
76+
</gml:featureMember>
77+
<gml:featureMember>
78+
<ogr:hub_distance_points fid="points.7">
79+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
80+
<ogr:id>8</ogr:id>
81+
<ogr:id2>0</ogr:id2>
82+
<ogr:HubName>point2</ogr:HubName>
83+
<ogr:HubDist>148835.564980152</ogr:HubDist>
84+
</ogr:hub_distance_points>
85+
</gml:featureMember>
86+
<gml:featureMember>
87+
<ogr:hub_distance_points fid="points.8">
88+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
89+
<ogr:id>9</ogr:id>
90+
<ogr:id2>0</ogr:id2>
91+
<ogr:HubName>point1</ogr:HubName>
92+
<ogr:HubDist>152464.26003518</ogr:HubDist>
93+
</ogr:hub_distance_points>
94+
</gml:featureMember>
95+
</ogr:FeatureCollection>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,6 +2184,22 @@ tests:
21842184
name: expected/gridify_lines.gml
21852185
type: vector
21862186

2187+
- algorithm: qgis:distancetonearesthubpoints
2188+
name: Hub distance points
2189+
params:
2190+
INPUT:
2191+
name: points.gml
2192+
type: vector
2193+
HUBS:
2194+
name: custom/hub_points.gml
2195+
type: vector
2196+
FIELD: name
2197+
UNIT: 0
2198+
results:
2199+
OUTPUT:
2200+
name: expected/hub_distance_points.gml
2201+
type: vector
2202+
21872203
# - algorithm: qgis:joinattributestable
21882204
# name: join the attribute table by common field
21892205
# params:

0 commit comments

Comments
 (0)
Please sign in to comment.