@@ -60,77 +60,88 @@ def defineCharacteristics(self):
60
60
self .addOutput (OutputVector (Clip .OUTPUT , self .tr ('Clipped' )))
61
61
62
62
def processAlgorithm (self , progress ):
63
- layerA = dataobjects .getObjectFromUri (
63
+ source_layer = dataobjects .getObjectFromUri (
64
64
self .getParameterValue (Clip .INPUT ))
65
- layerB = dataobjects .getObjectFromUri (
65
+ mask_layer = dataobjects .getObjectFromUri (
66
66
self .getParameterValue (Clip .OVERLAY ))
67
67
68
68
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 )
114
116
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 )
118
120
if new_geom .isGeosEmpty () or not new_geom .isGeosValid ():
119
121
ProcessingLog .addToLog (ProcessingLog .LOG_ERROR ,
120
122
self .tr ('GEOS geoprocessing error: One or more '
121
123
'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 ))
135
146
136
147
del writer
0 commit comments