Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port Hub Distance (lines) to new API
Improvements:
 - handle different CRS between points and hubs
 - add unit test
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent fc1746e commit e035445
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 58 deletions.
108 changes: 54 additions & 54 deletions python/plugins/processing/algs/qgis/HubDistanceLines.py
Expand Up @@ -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):
Expand All @@ -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'
Expand All @@ -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}
1 change: 0 additions & 1 deletion python/plugins/processing/algs/qgis/HubDistancePoints.py
Expand Up @@ -37,7 +37,6 @@
QgsWkbTypes,
QgsUnitTypes,
QgsProcessing,
QgsProcessingUtils,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
Expand Down
6 changes: 3 additions & 3 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 .HubDistanceLines import HubDistanceLines
from .HubDistancePoints import HubDistancePoints
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -189,7 +188,7 @@ def getAlgs(self):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubDistanceLines(), HubLines(),
# HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
# FieldsPyculator(),
Expand Down Expand Up @@ -245,6 +244,7 @@ def getAlgs(self):
GridPolygon(),
Heatmap(),
Hillshade(),
HubDistanceLines(),
HubDistancePoints(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Expand Down
@@ -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>
16 changes: 16 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -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:
Expand Down

0 comments on commit e035445

Please sign in to comment.