Port Hub Distance (points) to new API

Improvements:
- handle different CRS between points and hubs
- add unit test
This commit is contained in:
Nyall Dawson 2017-08-03 01:34:28 +10:00
parent 0930e18bf9
commit fc1746e770
7 changed files with 261 additions and 59 deletions

View File

@ -33,34 +33,32 @@ from qgis.core import (QgsField,
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):
@ -76,16 +74,16 @@ class HubDistancePoints(QgisAlgorithm):
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'
@ -94,61 +92,62 @@ class HubDistancePoints(QgisAlgorithm):
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)
units = self.UNITS[self.getParameterValue(self.UNIT)]
if layerPoints.source() == layerHubs.source():
raise GeoAlgorithmExecutionException(
if parameters[self.INPUT] == parameters[self.HUBS]:
raise QgsProcessingException(
self.tr('Same layer given for both hubs and spokes'))
fields = layerPoints.fields()
point_source = self.parameterAsSource(parameters, self.INPUT, context)
hub_source = self.parameterAsSource(parameters, self.HUBS, context)
fieldName = self.parameterAsString(parameters, self.FIELD, context)
units = self.UNITS[self.parameterAsEnum(parameters, self.UNIT, context)]
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}

View File

@ -75,6 +75,7 @@ from .GridLine import GridLine
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
@ -141,7 +142,7 @@ from .ZonalStatistics import ZonalStatistics
# 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
@ -188,7 +189,6 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# HubDistancePoints(),
# HubDistanceLines(), HubLines(),
# GeometryConvert(), FieldsCalculator(),
# JoinAttributes(),
@ -245,6 +245,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
GridPolygon(),
Heatmap(),
Hillshade(),
HubDistancePoints(),
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),

View File

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

View File

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

View File

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

View File

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

View File

@ -2184,6 +2184,22 @@ tests:
name: expected/gridify_lines.gml
type: vector
- algorithm: qgis:distancetonearesthubpoints
name: Hub distance points
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_points.gml
type: vector
# - algorithm: qgis:joinattributestable
# name: join the attribute table by common field
# params: