mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Port Hub Distance (points) to new API
Improvements: - handle different CRS between points and hubs - add unit test
This commit is contained in:
parent
0930e18bf9
commit
fc1746e770
@ -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}
|
||||
|
@ -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(),
|
||||
|
22
python/plugins/processing/tests/testdata/custom/hub_points.gfs
vendored
Normal file
22
python/plugins/processing/tests/testdata/custom/hub_points.gfs
vendored
Normal 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>
|
32
python/plugins/processing/tests/testdata/custom/hub_points.gml
vendored
Normal file
32
python/plugins/processing/tests/testdata/custom/hub_points.gml
vendored
Normal 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>
|
37
python/plugins/processing/tests/testdata/expected/hub_distance_points.gfs
vendored
Normal file
37
python/plugins/processing/tests/testdata/expected/hub_distance_points.gfs
vendored
Normal 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>
|
95
python/plugins/processing/tests/testdata/expected/hub_distance_points.gml
vendored
Normal file
95
python/plugins/processing/tests/testdata/expected/hub_distance_points.gml
vendored
Normal 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>
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user