diff --git a/python/plugins/processing/algs/qgis/LinesIntersection.py b/python/plugins/processing/algs/qgis/LinesIntersection.py index 6ea981471b1..eefb5450f09 100644 --- a/python/plugins/processing/algs/qgis/LinesIntersection.py +++ b/python/plugins/processing/algs/qgis/LinesIntersection.py @@ -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} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 1dd5ef7e340..bc4604eb746 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -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(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index f8618bd2d4c..03dba4be65b 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -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