Skip to content

Commit

Permalink
Merge pull request #4862 from nyalldawson/nn
Browse files Browse the repository at this point in the history
Port Line Intersection algorithm to new API
  • Loading branch information
nyalldawson authored Jul 15, 2017
2 parents eaad18c + a6736ce commit 8333f6a
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 107 deletions.
64 changes: 55 additions & 9 deletions python/plugins/processing/algs/qgis/Intersection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@

from qgis.core import (QgsFeatureRequest,
QgsFeature,
QgsFields,
QgsFeatureSink,
QgsGeometry,
QgsWkbTypes,
QgsProcessingException,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsSpatialIndex,
QgsProcessingUtils)
QgsProcessingParameterField)

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.tools import vector
Expand All @@ -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'))
Expand All @@ -76,6 +79,17 @@ def initAlgorithm(self, config=None):
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):
Expand All @@ -89,27 +103,61 @@ def processAlgorithm(self, parameters, context, feedback):
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)

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:
Expand All @@ -123,7 +171,8 @@ def processAlgorithm(self, parameters, context, feedback):

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)
Expand All @@ -139,10 +188,7 @@ def processAlgorithm(self, parameters, context, feedback):
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(
Expand Down
145 changes: 81 additions & 64 deletions python/plugins/processing/algs/qgis/LinesIntersection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,26 @@

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]


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'

Expand All @@ -62,22 +64,23 @@ def __init__(self):
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.addOutput(OutputVector(self.OUTPUT, self.tr('Intersections'), datatype=[dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'), [QgsProcessing.TypeVectorLine]))
self.addParameter(QgsProcessingParameterFeatureSource(self.INTERSECT,
self.tr('Intersect layer'), [QgsProcessing.TypeVectorLine]))

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'
Expand All @@ -86,67 +89,82 @@ def displayName(self):
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)

idxA = layerA.fields().lookupField(fieldA)
idxB = layerB.fields().lookupField(fieldB)

if idxA != -1:
fieldListA = QgsFields()
fieldListA.append(layerA.fields()[idxA])
sourceA = self.parameterAsSource(parameters, self.INPUT, context)
sourceB = self.parameterAsSource(parameters, self.INTERSECT, context)

fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context)
fieldsB = self.parameterAsFields(parameters, self.INTERSECT_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 = layerA.fields()

if idxB != -1:
fieldListB = QgsFields()
fieldListB.append(layerB.fields()[idxB])
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 = 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()
Expand All @@ -155,10 +173,9 @@ def processAlgorithm(self, parameters, context, feedback):

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}
5 changes: 3 additions & 2 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
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
Expand Down Expand Up @@ -91,7 +92,6 @@
from .ZonalStatistics import ZonalStatistics

# from .ExtractByLocation import ExtractByLocation
# from .LinesIntersection import LinesIntersection
# from .MeanCoords import MeanCoords
# from .PointDistance import PointDistance
# from .UniqueValues import UniqueValues
Expand Down Expand Up @@ -184,7 +184,7 @@ def __init__(self):

def getAlgs(self):
# algs = [MeanCoords(),
# LinesIntersection(), UniqueValues(), PointDistance(),
# UniqueValues(), PointDistance(),
# ExportGeometryInfo(),
# SinglePartsToMultiparts(),
# ExtractNodes(),
Expand Down Expand Up @@ -258,6 +258,7 @@ def getAlgs(self):
ImportIntoPostGIS(),
ImportIntoSpatialite(),
Intersection(),
LinesIntersection(),
LinesToPolygons(),
Merge(),
NearestNeighbourAnalysis(),
Expand Down
Binary file not shown.
Loading

0 comments on commit 8333f6a

Please sign in to comment.