Skip to content
Permalink
Browse files

Port Hub Distance (lines) to new API

Improvements:
 - handle different CRS between points and hubs
 - add unit test
  • Loading branch information
nyalldawson committed Aug 3, 2017
1 parent fc1746e commit e0354456e3240d7b2498547727c5ffc27c7610cd
@@ -34,33 +34,33 @@
QgsFeatureSink,
QgsFeatureRequest,
QgsWkbTypes,
QgsApplication,
QgsProject,
QgsProcessingUtils)
QgsUnitTypes,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink,
QgsProcessingException,
QgsSpatialIndex)
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 HubDistanceLines(QgisAlgorithm):
POINTS = 'POINTS'
INPUT = 'INPUT'
HUBS = 'HUBS'
FIELD = 'FIELD'
UNIT = 'UNIT'
OUTPUT = 'OUTPUT'

UNITS = ['Meters',
'Feet',
'Miles',
'Kilometers',
'Layer units'
LAYER_UNITS = 'LAYER_UNITS'

UNITS = [QgsUnitTypes.DistanceMeters,
QgsUnitTypes.DistanceFeet,
QgsUnitTypes.DistanceMiles,
QgsUnitTypes.DistanceKilometers,
LAYER_UNITS
]

def group(self):
@@ -76,16 +76,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_LINE]))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Hub distance'), QgsProcessing.TypeVectorLine))

def name(self):
return 'distancetonearesthublinetohub'
@@ -94,61 +94,61 @@ def displayName(self):
return self.tr('Distance to nearest hub (line to hub)')

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.LineString, layerPoints.crs(),
context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.LineString, 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.fromPolyline([src, closest]))

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

del writer
return {self.OUTPUT: dest_id}
@@ -37,7 +37,6 @@
QgsWkbTypes,
QgsUnitTypes,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
@@ -75,6 +75,7 @@
from .GridPolygon import GridPolygon
from .Heatmap import Heatmap
from .Hillshade import Hillshade
from .HubDistanceLines import HubDistanceLines
from .HubDistancePoints import HubDistancePoints
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
@@ -142,8 +143,6 @@
# from .ExtractByLocation import ExtractByLocation
# from .SelectByLocation import SelectByLocation
# from .SpatialJoin import SpatialJoin

# from .HubDistanceLines import HubDistanceLines
# from .HubLines import HubLines
# from .GeometryConvert import GeometryConvert
# from .StatisticsByCategories import StatisticsByCategories
@@ -189,7 +188,7 @@ def getAlgs(self):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubDistanceLines(), HubLines(),
# HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
# FieldsPyculator(),
@@ -245,6 +244,7 @@ def getAlgs(self):
GridPolygon(),
Heatmap(),
Hillshade(),
HubDistanceLines(),
HubDistancePoints(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
@@ -0,0 +1,37 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>hub_distance_lines</Name>
<ElementPath>hub_distance_lines</ElementPath>
<!--LINESTRING-->
<GeometryType>2</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_lines fid="points.0">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>1,1 1.34480766269365,-1.25946718408328</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,3 3.12290985247467,2.27221164803351</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,2 3.12290985247467,2.27221164803351</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>5,2 3.12290985247467,2.27221164803351</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.4">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>4,1 3.12290985247467,2.27221164803351</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>0,-5 1.34480766269365,-1.25946718408328</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.6">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>8,-1 6.29896824663525,0.138489020296281</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.7">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-1 6.29896824663525,0.138489020296281</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:hub_distance_lines fid="points.8">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>0,-1 1.34480766269365,-1.25946718408328</gml:coordinates></gml:LineString></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_lines>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -2200,6 +2200,22 @@ tests:
name: expected/hub_distance_points.gml
type: vector

- algorithm: qgis:distancetonearesthublinetohub
name: Hub distance lines
params:
INPUT:
name: points.gml
type: vector
HUBS:
name: custom/hub_points.gml
type: vector
FIELD: name
UNIT: 0
results:
OUTPUT:
name: expected/hub_distance_lines.gml
type: vector

# - algorithm: qgis:joinattributestable
# name: join the attribute table by common field
# params:

0 comments on commit e035445

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