Port Line Intersection algorithm to new API

Improvements
- allow different CRS between layers
- instead of optionally allowing selection of a single field to keep from
both inputs, allow selection of multiple fields
This commit is contained in:
Nyall Dawson 2017-07-15 17:18:51 +10:00
parent eaad18c6ad
commit 02bf88c4b7
3 changed files with 112 additions and 94 deletions

View File

@ -31,13 +31,15 @@ from qgis.PyQt.QtGui import QIcon
from qgis.core import (QgsFeatureRequest, QgsFeature, QgsGeometry,
QgsFeatureSink,
QgsWkbTypes, QgsFields,
QgsProcessingUtils)
QgsWkbTypes,
QgsFields,
QgsSpatialIndex,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterTableField
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
@ -45,10 +47,10 @@ pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class LinesIntersection(QgisAlgorithm):
INPUT_A = 'INPUT_A'
INPUT_B = 'INPUT_B'
FIELD_A = 'FIELD_A'
FIELD_B = 'FIELD_B'
INPUT = 'INPUT'
INTERSECT = 'INTERSECT'
INPUT_FIELDS = 'INPUT_FIELDS'
INTERSECT_FIELDS = 'INTERSECT_FIELDS'
OUTPUT = 'OUTPUT'
@ -62,22 +64,23 @@ class LinesIntersection(QgisAlgorithm):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.INPUT_A,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_LINE]))
self.addParameter(ParameterVector(self.INPUT_B,
self.tr('Intersect layer'), [dataobjects.TYPE_VECTOR_LINE]))
self.addParameter(ParameterTableField(
self.FIELD_A,
self.tr('Input field to keep (leave as [not set] to keep all fields)'),
self.INPUT_A,
optional=True))
self.addParameter(ParameterTableField(
self.FIELD_B,
self.tr('Intersect field to keep (leave as [not set] to keep all fields)'),
self.INPUT_B,
optional=True))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'), [QgsProcessing.TypeVectorLine]))
self.addParameter(QgsProcessingParameterFeatureSource(self.INTERSECT,
self.tr('Intersect layer'), [QgsProcessing.TypeVectorLine]))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Intersections'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(QgsProcessingParameterField(
self.INPUT_FIELDS,
self.tr('Input fields to keep (leave empty to keep all fields)'),
parentLayerParameterName=self.INPUT,
optional=True, allowMultiple=True))
self.addParameter(QgsProcessingParameterField(
self.INTERSECT_FIELDS,
self.tr('Intersect fields to keep (leave empty to keep all fields)'),
parentLayerParameterName=self.INTERSECT,
optional=True, allowMultiple=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Intersections'), QgsProcessing.TypeVectorPoint))
def name(self):
return 'lineintersections'
@ -86,67 +89,82 @@ class LinesIntersection(QgisAlgorithm):
return self.tr('Line intersections')
def processAlgorithm(self, parameters, context, feedback):
layerA = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_A), context)
layerB = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_B), context)
fieldA = self.getParameterValue(self.FIELD_A)
fieldB = self.getParameterValue(self.FIELD_B)
sourceA = self.parameterAsSource(parameters, self.INPUT, context)
sourceB = self.parameterAsSource(parameters, self.INTERSECT, context)
idxA = layerA.fields().lookupField(fieldA)
idxB = layerB.fields().lookupField(fieldB)
fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context)
fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS, context)
if idxA != -1:
fieldListA = QgsFields()
fieldListA.append(layerA.fields()[idxA])
fieldListA = QgsFields()
field_indices_a = []
if len(fieldsA) > 0:
for f in fieldsA:
idxA = sourceA.fields().lookupField(f)
if idxA >= 0:
field_indices_a.append(idxA)
fieldListA.append(sourceA.fields()[idxA])
else:
fieldListA = layerA.fields()
fieldListA = sourceA.fields()
field_indices_a = [i for i in range(0, fieldListA.count())]
if idxB != -1:
fieldListB = QgsFields()
fieldListB.append(layerB.fields()[idxB])
fieldListB = QgsFields()
field_indices_b = []
if len(fieldsB) > 0:
for f in fieldsB:
idxB = sourceB.fields().lookupField(f)
if idxB >= 0:
field_indices_b.append(idxB)
fieldListB.append(sourceB.fields()[idxB])
else:
fieldListB = layerB.fields()
fieldListB = sourceB.fields()
field_indices_b = [i for i in range(0, fieldListB.count())]
fieldListB = vector.testForUniqueness(fieldListA, fieldListB)
for b in fieldListB:
fieldListA.append(b)
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldListA, QgsWkbTypes.Point, layerA.crs(),
context)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fieldListA, QgsWkbTypes.Point, sourceA.sourceCrs())
spatialIndex = QgsProcessingUtils.createSpatialIndex(layerB, context)
spatialIndex = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)
outFeat = QgsFeature()
features = QgsProcessingUtils.getFeatures(layerA, context)
total = 100.0 / layerA.featureCount() if layerA.featureCount() else 0
hasIntersections = False
features = sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a))
total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0
for current, inFeatA in enumerate(features):
if feedback.isCanceled():
break
if not inFeatA.hasGeometry():
continue
inGeom = inFeatA.geometry()
hasIntersections = False
has_intersections = False
lines = spatialIndex.intersects(inGeom.boundingBox())
engine = None
if len(lines) > 0:
hasIntersections = True
has_intersections = True
# use prepared geometries for faster intersection tests
engine = QgsGeometry.createGeometryEngine(inGeom.geometry())
engine.prepareGeometry()
if hasIntersections:
if has_intersections:
request = QgsFeatureRequest().setFilterFids(lines)
for inFeatB in layerB.getFeatures(request):
request.setDestinationCrs(sourceA.sourceCrs())
request.setSubsetOfAttributes(field_indices_b)
for inFeatB in sourceB.getFeatures(request):
if feedback.isCanceled():
break
tmpGeom = inFeatB.geometry()
points = []
attrsA = inFeatA.attributes()
if idxA != -1:
attrsA = [attrsA[idxA]]
attrsB = inFeatB.attributes()
if idxB != -1:
attrsB = [attrsB[idxB]]
if engine.intersects(tmpGeom.geometry()):
tempGeom = inGeom.intersection(tmpGeom)
out_attributes = [inFeatA.attributes()[i] for i in field_indices_a]
out_attributes.extend([inFeatB.attributes()[i] for i in field_indices_b])
if tempGeom.type() == QgsWkbTypes.PointGeometry:
if tempGeom.isMultipart():
points = tempGeom.asMultiPoint()
@ -155,10 +173,9 @@ class LinesIntersection(QgisAlgorithm):
for j in points:
outFeat.setGeometry(tempGeom.fromPoint(j))
attrsA.extend(attrsB)
outFeat.setAttributes(attrsA)
writer.addFeature(outFeat, QgsFeatureSink.FastInsert)
outFeat.setAttributes(out_attributes)
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
del writer
return {self.OUTPUT: dest_id}

View File

@ -64,6 +64,7 @@ from .Hillshade import Hillshade
from .ImportIntoPostGIS import ImportIntoPostGIS
from .ImportIntoSpatialite import ImportIntoSpatialite
from .Intersection import Intersection
from .LinesIntersection import LinesIntersection
from .LinesToPolygons import LinesToPolygons
from .Merge import Merge
from .NearestNeighbourAnalysis import NearestNeighbourAnalysis
@ -91,7 +92,6 @@ from .VoronoiPolygons import VoronoiPolygons
from .ZonalStatistics import ZonalStatistics
# from .ExtractByLocation import ExtractByLocation
# from .LinesIntersection import LinesIntersection
# from .MeanCoords import MeanCoords
# from .PointDistance import PointDistance
# from .UniqueValues import UniqueValues
@ -184,7 +184,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
def getAlgs(self):
# algs = [MeanCoords(),
# LinesIntersection(), UniqueValues(), PointDistance(),
# UniqueValues(), PointDistance(),
# ExportGeometryInfo(),
# SinglePartsToMultiparts(),
# ExtractNodes(),
@ -258,6 +258,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),
LinesIntersection(),
LinesToPolygons(),
Merge(),
NearestNeighbourAnalysis(),

View File

@ -1112,38 +1112,38 @@ tests:
# OUTPUT_LAYER:
# hash: 7fe0e0174185fd743e23760f33615adf10f771b4275f320db6f7f4f8
# type: rasterhash
#
# # Case 1: Keep all fields
# - algorithm: qgis:lineintersections
# name: Line Intersection Keep All Fields from Both
# params:
# INPUT_A:
# name: lines.gml
# type: vector
# INPUT_B:
# name: simplify_lines.gml
# type: vector
# results:
# OUTPUT:
# name: expected/line_intersection.gml
# type: vector
#
# # Case 2: Keep fid field from both layers
# - algorithm: qgis:lineintersections
# name: Line Intersection Keep fid from Both
# params:
# FIELD_A: fid
# FIELD_B: fid
# INPUT_A:
# name: lines.gml
# type: vector
# INPUT_B:
# name: simplify_lines.gml
# type: vector
# results:
# OUTPUT:
# name: expected/line_intersection.gml
# type: vector
# Case 1: Keep all fields
- algorithm: qgis:lineintersections
name: Line Intersection Keep All Fields from Both
params:
INPUT:
name: lines.gml
type: vector
INTERSECT:
name: simplify_lines.gml
type: vector
results:
OUTPUT:
name: expected/line_intersection.gml
type: vector
# Case 2: Keep fid field from both layers
- algorithm: qgis:lineintersections
name: Line Intersection Keep fid from Both
params:
INPUT_FIELDS: fid
INTERSECT_FIELDS: fid
INPUT:
name: lines.gml
type: vector
INTERSECT:
name: simplify_lines.gml
type: vector
results:
OUTPUT:
name: expected/line_intersection.gml
type: vector
- algorithm: qgis:sumlinelengths
name: Sum line lengths