Skip to content

Commit c695571

Browse files
authored
Merge pull request #3304 from nyalldawson/processing_clip
[FEATURE] Optimise processing clip algorithm
2 parents 420917b + d1aa03a commit c695571

File tree

4 files changed

+79
-68
lines changed

4 files changed

+79
-68
lines changed

python/plugins/processing/algs/qgis/Clip.py

Lines changed: 74 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -60,77 +60,88 @@ def defineCharacteristics(self):
6060
self.addOutput(OutputVector(Clip.OUTPUT, self.tr('Clipped')))
6161

6262
def processAlgorithm(self, progress):
63-
layerA = dataobjects.getObjectFromUri(
63+
source_layer = dataobjects.getObjectFromUri(
6464
self.getParameterValue(Clip.INPUT))
65-
layerB = dataobjects.getObjectFromUri(
65+
mask_layer = dataobjects.getObjectFromUri(
6666
self.getParameterValue(Clip.OVERLAY))
6767

6868
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
69-
layerA.pendingFields(),
70-
layerA.dataProvider().geometryType(),
71-
layerA.dataProvider().crs())
72-
73-
inFeatA = QgsFeature()
74-
inFeatB = QgsFeature()
75-
outFeat = QgsFeature()
76-
77-
index = vector.spatialindex(layerB)
78-
79-
selectionA = vector.features(layerA)
80-
81-
total = 100.0 / len(selectionA)
82-
83-
for current, inFeatA in enumerate(selectionA):
84-
geom = inFeatA.geometry()
85-
attrs = inFeatA.attributes()
86-
intersects = index.intersects(geom.boundingBox())
87-
first = True
88-
found = False
89-
if len(intersects) > 0:
90-
for i in intersects:
91-
layerB.getFeatures(
92-
QgsFeatureRequest().setFilterFid(i)).nextFeature(
93-
inFeatB)
94-
tmpGeom = inFeatB.geometry()
95-
if tmpGeom.intersects(geom):
96-
found = True
97-
if first:
98-
outFeat.setGeometry(QgsGeometry(tmpGeom))
99-
first = False
100-
else:
101-
cur_geom = outFeat.geometry()
102-
new_geom = QgsGeometry(cur_geom.combine(tmpGeom))
103-
if new_geom.isEmpty() or new_geom.isGeosEmpty() or not new_geom.isGeosValid():
104-
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
105-
self.tr('GEOS geoprocessing error: One or '
106-
'more input features have invalid '
107-
'geometry.'))
108-
break
109-
110-
outFeat.setGeometry(QgsGeometry(new_geom))
111-
if found:
112-
cur_geom = outFeat.geometry()
113-
new_geom = QgsGeometry(geom.intersection(cur_geom))
69+
source_layer.fields(),
70+
source_layer.dataProvider().geometryType(),
71+
source_layer.crs())
72+
73+
# first build up a list of clip geometries
74+
clip_geoms = []
75+
for maskFeat in vector.features(mask_layer, QgsFeatureRequest().setSubsetOfAttributes([])):
76+
clip_geoms.append(maskFeat.geometry())
77+
78+
# are we clipping against a single feature? if so, we can show finer progress reports
79+
if len(clip_geoms) > 1:
80+
combined_clip_geom = QgsGeometry.unaryUnion(clip_geoms)
81+
single_clip_feature = False
82+
else:
83+
combined_clip_geom = clip_geoms[0]
84+
single_clip_feature = True
85+
86+
# use prepared geometries for faster intersection tests
87+
engine = QgsGeometry.createGeometryEngine(combined_clip_geom.geometry())
88+
engine.prepareGeometry()
89+
90+
tested_feature_ids = set()
91+
92+
for i, clip_geom in enumerate(clip_geoms):
93+
input_features = [f for f in vector.features(source_layer, QgsFeatureRequest().setFilterRect(clip_geom.boundingBox()))]
94+
95+
if single_clip_feature:
96+
total = 100.0 / len(input_features)
97+
else:
98+
total = 0
99+
100+
for current, in_feat in enumerate(input_features):
101+
if not in_feat.geometry():
102+
continue
103+
104+
if in_feat.id() in tested_feature_ids:
105+
# don't retest a feature we have already checked
106+
continue
107+
108+
tested_feature_ids.add(in_feat.id())
109+
110+
if not engine.intersects(in_feat.geometry().geometry()):
111+
continue
112+
113+
if not engine.contains(in_feat.geometry().geometry()):
114+
cur_geom = in_feat.geometry()
115+
new_geom = combined_clip_geom.intersection(cur_geom)
114116
if new_geom.wkbType() == Qgis.WKBUnknown or QgsWKBTypes.flatType(new_geom.geometry().wkbType()) == QgsWKBTypes.GeometryCollection:
115-
int_com = QgsGeometry(geom.combine(cur_geom))
116-
int_sym = QgsGeometry(geom.symDifference(cur_geom))
117-
new_geom = QgsGeometry(int_com.difference(int_sym))
117+
int_com = in_feat.geometry().combine(new_geom)
118+
int_sym = in_feat.geometry().symDifference(new_geom)
119+
new_geom = int_com.difference(int_sym)
118120
if new_geom.isGeosEmpty() or not new_geom.isGeosValid():
119121
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
120122
self.tr('GEOS geoprocessing error: One or more '
121123
'input features have invalid geometry.'))
122-
continue
123-
try:
124-
outFeat.setGeometry(new_geom)
125-
outFeat.setAttributes(attrs)
126-
writer.addFeature(outFeat)
127-
except:
128-
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
129-
self.tr('Feature geometry error: One or more '
130-
'output features ignored due to '
131-
'invalid geometry.'))
132-
continue
133-
134-
progress.setPercentage(int(current * total))
124+
else:
125+
# clip geometry totally contains feature geometry, so no need to perform intersection
126+
new_geom = in_feat.geometry()
127+
128+
try:
129+
out_feat = QgsFeature()
130+
out_feat.setGeometry(new_geom)
131+
out_feat.setAttributes(in_feat.attributes())
132+
writer.addFeature(out_feat)
133+
except:
134+
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
135+
self.tr('Feature geometry error: One or more '
136+
'output features ignored due to '
137+
'invalid geometry.'))
138+
continue
139+
140+
if single_clip_feature:
141+
progress.setPercentage(int(current * total))
142+
143+
if not single_clip_feature:
144+
# coarse progress report for multiple clip geometries
145+
progress.setPercentage(100.0 * i / len(clip_geoms))
135146

136147
del writer

python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon.gml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</gml:featureMember>
2424
<gml:featureMember>
2525
<ogr:clip_lines_by_multipolygon fid="lines.3">
26-
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 4,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>4,1 3,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
2727
</ogr:clip_lines_by_multipolygon>
2828
</gml:featureMember>
2929
<gml:featureMember>

python/plugins/processing/tests/testdata/expected/clip_multipolygons_by_polygons.gml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313

1414
<gml:featureMember>
1515
<ogr:clip_multipolygons_by_polygons fid="multipolys.0">
16-
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4.0,1.666666666666667 4,1 2,1 2,2 3,2 4.0,1.666666666666667</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
16+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2 1, 2 2, 3 2, 4 1.66666666666667, 4 1, 2 1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
1717
<ogr:Bname>Test</ogr:Bname>
1818
<ogr:Bintval>1</ogr:Bintval>
1919
<ogr:Bfloatval>0.123</ogr:Bfloatval>
2020
</ogr:clip_multipolygons_by_polygons>
2121
</gml:featureMember>
2222
<gml:featureMember>
2323
<ogr:clip_multipolygons_by_polygons fid="multipolys.1">
24-
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8,1 8,0 7,0 7,1 8,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
24+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7 0, 7 1, 8 1, 8 0, 7 0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
2525
</ogr:clip_multipolygons_by_polygons>
2626
</gml:featureMember>
2727
<gml:featureMember>

python/plugins/processing/tests/testdata/expected/clip_polys_by_multipolygon.gml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
</gml:featureMember>
2222
<gml:featureMember>
2323
<ogr:clip_polys_by_multipolygon fid="polys.3">
24-
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8,0 7,0 7,1 8,1 8,0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
24+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7 1, 8 1, 8 0, 7 0, 7 1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
2525
<ogr:name>ASDF</ogr:name>
2626
<ogr:intval>0</ogr:intval>
2727
</ogr:clip_polys_by_multipolygon>
2828
</gml:featureMember>
2929
<gml:featureMember>
3030
<ogr:clip_polys_by_multipolygon fid="polys.5">
31-
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 4.0,1.666666666666667 4,1 2,1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
31+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2 1, 2 2, 3 2, 4 1.66666666666667, 4 1, 2 1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
3232
<ogr:name>elim</ogr:name>
3333
<ogr:intval>2</ogr:intval>
3434
<ogr:floatval>3.33</ogr:floatval>

0 commit comments

Comments
 (0)