Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port Hub Distance (points) to new API
Improvements:
- handle different CRS between points and hubs
- add unit test
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent 0930e18 commit fc1746e
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 58 deletions.
111 changes: 55 additions & 56 deletions python/plugins/processing/algs/qgis/HubDistancePoints.py
Expand Up @@ -33,34 +33,32 @@
QgsDistanceArea,
QgsFeature,
QgsFeatureRequest,
QgsSpatialIndex,
QgsWkbTypes,
QgsApplication,
QgsProject,
QgsProcessingUtils)
QgsUnitTypes,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink,
QgsProcessingException)
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.parameters import ParameterSelection
from processing.core.outputs import OutputVector

from processing.tools import dataobjects

from math import sqrt


class HubDistancePoints(QgisAlgorithm):
POINTS = 'POINTS'
INPUT = 'INPUT'
HUBS = 'HUBS'
FIELD = 'FIELD'
UNIT = 'UNIT'
OUTPUT = 'OUTPUT'
LAYER_UNITS = 'LAYER_UNITS'

UNITS = ['Meters',
'Feet',
'Miles',
'Kilometers',
'Layer units'
UNITS = [QgsUnitTypes.DistanceMeters,
QgsUnitTypes.DistanceFeet,
QgsUnitTypes.DistanceMiles,
QgsUnitTypes.DistanceKilometers,
LAYER_UNITS
]

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

self.addParameter(ParameterVector(self.POINTS,
self.tr('Source points layer')))
self.addParameter(ParameterVector(self.HUBS,
self.tr('Destination hubs layer')))
self.addParameter(ParameterTableField(self.FIELD,
self.tr('Hub layer name attribute'), self.HUBS))
self.addParameter(ParameterSelection(self.UNIT,
self.tr('Measurement unit'), self.units))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Source points layer')))
self.addParameter(QgsProcessingParameterFeatureSource(self.HUBS,
self.tr('Destination hubs layer')))
self.addParameter(QgsProcessingParameterField(self.FIELD,
self.tr('Hub layer name attribute'), parentLayerParameterName=self.HUBS))
self.addParameter(QgsProcessingParameterEnum(self.UNIT,
self.tr('Measurement unit'), self.units))

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

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

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

units = self.UNITS[self.getParameterValue(self.UNIT)]
point_source = self.parameterAsSource(parameters, self.INPUT, context)
hub_source = self.parameterAsSource(parameters, self.HUBS, context)
fieldName = self.parameterAsString(parameters, self.FIELD, context)

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

fields = layerPoints.fields()
fields = point_source.fields()
fields.append(QgsField('HubName', QVariant.String))
fields.append(QgsField('HubDist', QVariant.Double))

writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layerPoints.crs(),
context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.Point, point_source.sourceCrs())

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

distance = QgsDistanceArea()
distance.setSourceCrs(layerPoints.crs())
distance.setSourceCrs(point_source.sourceCrs())
distance.setEllipsoid(context.project().ellipsoid())

# Scan source points, find nearest hub, and write to output file
features = QgsProcessingUtils.getFeatures(layerPoints, context)
total = 100.0 / layerPoints.featureCount() if layerPoints.featureCount() else 0
features = point_source.getFeatures()
total = 100.0 / point_source.featureCount() if point_source.featureCount() else 0
for current, f in enumerate(features):
if feedback.isCanceled():
break

if not f.hasGeometry():
sink.addFeature(f, QgsFeatureSink.FastInsert)
continue

src = f.geometry().boundingBox().center()

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

if units != self.LAYER_UNITS:
hub_dist_in_desired_units = distance.convertLengthMeasurement(hubDist, units)
else:
hub_dist_in_desired_units = hubDist

attributes = f.attributes()
attributes.append(ft[fieldName])
if units == 'Feet':
attributes.append(hubDist * 3.2808399)
elif units == 'Miles':
attributes.append(hubDist * 0.000621371192)
elif units == 'Kilometers':
attributes.append(hubDist / 1000.0)
elif units != 'Meters':
attributes.append(sqrt(
pow(src.x() - closest.x(), 2.0) +
pow(src.y() - closest.y(), 2.0)))
else:
attributes.append(hubDist)
attributes.append(hub_dist_in_desired_units)

feat = QgsFeature()
feat.setAttributes(attributes)

feat.setGeometry(QgsGeometry.fromPoint(src))

writer.addFeature(feat, QgsFeatureSink.FastInsert)
sink.addFeature(feat, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))

del writer
return {self.OUTPUT: dest_id}
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -75,6 +75,7 @@
from .GridPolygon import GridPolygon
from .Heatmap import Heatmap
from .Hillshade import Hillshade
from .HubDistancePoints import HubDistancePoints
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
from .Intersection import Intersection
Expand Down Expand Up @@ -141,7 +142,7 @@
# from .ExtractByLocation import ExtractByLocation
# from .SelectByLocation import SelectByLocation
# from .SpatialJoin import SpatialJoin
# from .HubDistancePoints import HubDistancePoints

# from .HubDistanceLines import HubDistanceLines
# from .HubLines import HubLines
# from .GeometryConvert import GeometryConvert
Expand Down Expand Up @@ -188,7 +189,6 @@ def getAlgs(self):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubDistancePoints(),
# HubDistanceLines(), HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
Expand Down Expand Up @@ -245,6 +245,7 @@ def getAlgs(self):
GridPolygon(),
Heatmap(),
Hillshade(),
HubDistancePoints(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),
Expand Down
22 changes: 22 additions & 0 deletions python/plugins/processing/tests/testdata/custom/hub_points.gfs
@@ -0,0 +1,22 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>hub_points</Name>
<ElementPath>hub_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>3</FeatureCount>
<ExtentXMin>1.34481</ExtentXMin>
<ExtentXMax>6.29897</ExtentXMax>
<ExtentYMin>-1.25947</ExtentYMin>
<ExtentYMax>2.27221</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>6</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
32 changes: 32 additions & 0 deletions python/plugins/processing/tests/testdata/custom/hub_points.gml
@@ -0,0 +1,32 @@
<?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_points.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>1.344807662693645</gml:X><gml:Y>-1.259467184083282</gml:Y></gml:coord>
<gml:coord><gml:X>6.298968246635251</gml:X><gml:Y>2.272211648033507</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:hub_points fid="hub_points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1.34480766269365,-1.25946718408328</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:name>point1</ogr:name>
</ogr:hub_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_points fid="hub_points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>6.29896824663525,0.138489020296281</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:name>point2</ogr:name>
</ogr:hub_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_points fid="hub_points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.12290985247467,2.27221164803351</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:name>point3</ogr:name>
</ogr:hub_points>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,37 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>hub_distance_points</Name>
<ElementPath>hub_distance_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>8.00000</ExtentXMax>
<ExtentYMin>-5.00000</ExtentYMin>
<ExtentYMax>3.00000</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>HubName</Name>
<ElementPath>HubName</ElementPath>
<Type>String</Type>
<Width>6</Width>
</PropertyDefn>
<PropertyDefn>
<Name>HubDist</Name>
<ElementPath>HubDist</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,95 @@
<?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>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:hub_distance_points fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:HubName>point1</ogr:HubName>
<ogr:HubDist>254434.675423572</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:HubName>point3</ogr:HubName>
<ogr:HubDist>82164.2455422206</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:HubName>point3</ogr:HubName>
<ogr:HubDist>128622.227687308</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:HubName>point3</ogr:HubName>
<ogr:HubDist>211142.486929284</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>5</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:HubName>point3</ogr:HubName>
<ogr:HubDist>172016.876891364</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>6</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:HubName>point1</ogr:HubName>
<ogr:HubDist>442487.532089586</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>7</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:HubName>point2</ogr:HubName>
<ogr:HubDist>227856.24000978</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:HubName>point2</ogr:HubName>
<ogr:HubDist>148835.564980152</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_points fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>9</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:HubName>point1</ogr:HubName>
<ogr:HubDist>152464.26003518</ogr:HubDist>
</ogr:hub_distance_points>
</gml:featureMember>
</ogr:FeatureCollection>

0 comments on commit fc1746e

Please sign in to comment.