Merge pull request #4862 from nyalldawson/nn

Port Line Intersection algorithm to new API
This commit is contained in:
Nyall Dawson 2017-07-15 20:32:50 +10:00 committed by GitHub
commit 8333f6a59d
5 changed files with 167 additions and 103 deletions

View File

@ -31,6 +31,7 @@ from qgis.PyQt.QtGui import QIcon
from qgis.core import (QgsFeatureRequest,
QgsFeature,
QgsFields,
QgsFeatureSink,
QgsGeometry,
QgsWkbTypes,
@ -38,7 +39,7 @@ from qgis.core import (QgsFeatureRequest,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsSpatialIndex,
QgsProcessingUtils)
QgsProcessingParameterField)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.tools import vector
@ -60,6 +61,8 @@ class Intersection(QgisAlgorithm):
INPUT = 'INPUT'
OVERLAY = 'OVERLAY'
OUTPUT = 'OUTPUT'
INPUT_FIELDS = 'INPUT_FIELDS'
OVERLAY_FIELDS = 'OVERLAY_FIELDS'
def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'intersect.png'))
@ -76,6 +79,17 @@ class Intersection(QgisAlgorithm):
self.addParameter(QgsProcessingParameterFeatureSource(self.OVERLAY,
self.tr('Intersection layer')))
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.OVERLAY_FIELDS,
self.tr('Intersect fields to keep (leave empty to keep all fields)'),
parentLayerParameterName=self.OVERLAY,
optional=True, allowMultiple=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Intersection')))
def name(self):
@ -89,10 +103,40 @@ class Intersection(QgisAlgorithm):
sourceB = self.parameterAsSource(parameters, self.OVERLAY, context)
geomType = QgsWkbTypes.multiType(sourceA.wkbType())
fields = vector.combineFields(sourceA.fields(), sourceB.fields())
fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context)
fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context)
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 = sourceA.fields()
field_indices_a = [i for i in range(0, fieldListA.count())]
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 = 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)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, geomType, sourceA.sourceCrs())
fieldListA, geomType, sourceA.sourceCrs())
outFeat = QgsFeature()
indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback)
@ -100,16 +144,20 @@ class Intersection(QgisAlgorithm):
total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1
count = 0
for featA in sourceA.getFeatures():
for featA in sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)):
if feedback.isCanceled():
break
if not featA.hasGeometry():
continue
geom = featA.geometry()
atMapA = featA.attributes()
intersects = indexB.intersects(geom.boundingBox())
request = QgsFeatureRequest().setFilterFids(intersects)
request.setDestinationCrs(sourceA.sourceCrs())
request.setSubsetOfAttributes(field_indices_b)
engine = None
if len(intersects) > 0:
@ -123,7 +171,8 @@ class Intersection(QgisAlgorithm):
tmpGeom = featB.geometry()
if engine.intersects(tmpGeom.geometry()):
atMapB = featB.attributes()
out_attributes = [featA.attributes()[i] for i in field_indices_a]
out_attributes.extend([featB.attributes()[i] for i in field_indices_b])
int_geom = QgsGeometry(geom.intersection(tmpGeom))
if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection:
int_com = geom.combine(tmpGeom)
@ -139,10 +188,7 @@ class Intersection(QgisAlgorithm):
try:
if int_geom.wkbType() in wkbTypeGroups[wkbTypeGroups[int_geom.wkbType()]]:
outFeat.setGeometry(int_geom)
attrs = []
attrs.extend(atMapA)
attrs.extend(atMapB)
outFeat.setAttributes(attrs)
outFeat.setAttributes(out_attributes)
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
except:
raise QgsProcessingException(

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