41
41
QgsProcessingParameterEnum ,
42
42
QgsProcessingParameterField ,
43
43
QgsProcessingParameterFeatureSink ,
44
- QgsProcessingParameterString )
44
+ QgsProcessingParameterString ,
45
+ QgsProcessingOutputNumber )
45
46
46
47
from processing .algs .qgis .QgisAlgorithm import QgisAlgorithm
47
48
from processing .tools import vector
@@ -58,6 +59,8 @@ class SpatialJoin(QgisAlgorithm):
58
59
DISCARD_NONMATCHING = "DISCARD_NONMATCHING"
59
60
PREFIX = "PREFIX"
60
61
OUTPUT = "OUTPUT"
62
+ NON_MATCHING = "NON_MATCHING"
63
+ JOINED_COUNT = "JOINED_COUNT"
61
64
62
65
def group (self ):
63
66
return self .tr ('Vector general' )
@@ -120,7 +123,19 @@ def initAlgorithm(self, config=None):
120
123
self .addParameter (QgsProcessingParameterString (self .PREFIX ,
121
124
self .tr ('Joined field prefix' ), optional = True ))
122
125
self .addParameter (QgsProcessingParameterFeatureSink (self .OUTPUT ,
123
- self .tr ('Joined layer' )))
126
+ self .tr ('Joined layer' ),
127
+ QgsProcessing .TypeVectorAnyGeometry ,
128
+ defaultValue = None , optional = True , createByDefault = True ))
129
+
130
+ non_matching = QgsProcessingParameterFeatureSink (self .NON_MATCHING ,
131
+ self .tr ('Unjoinable features from first layer' ),
132
+ QgsProcessing .TypeVectorAnyGeometry ,
133
+ defaultValue = None , optional = True , createByDefault = False )
134
+ # TODO GUI doesn't support advanced outputs yet
135
+ # non_matching.setFlags(non_matching.flags() | QgsProcessingParameterDefinition.FlagAdvanced )
136
+ self .addParameter (non_matching )
137
+
138
+ self .addOutput (QgsProcessingOutputNumber (self .JOINED_COUNT , self .tr ("Number of joined features from input table" )))
124
139
125
140
def name (self ):
126
141
return 'joinattributesbylocation'
@@ -170,9 +185,14 @@ def processAlgorithm(self, parameters, context, feedback):
170
185
171
186
(sink , dest_id ) = self .parameterAsSink (parameters , self .OUTPUT , context ,
172
187
out_fields , source .wkbType (), source .sourceCrs ())
173
- if sink is None :
188
+ if self . OUTPUT in parameters and parameters [ self . OUTPUT ] is not None and sink is None :
174
189
raise QgsProcessingException (self .invalidSinkError (parameters , self .OUTPUT ))
175
190
191
+ (non_matching_sink , non_matching_dest_id ) = self .parameterAsSink (parameters , self .NON_MATCHING , context ,
192
+ source .fields (), source .wkbType (), source .sourceCrs ())
193
+ if self .NON_MATCHING in parameters and parameters [self .NON_MATCHING ] is not None and non_matching_sink is None :
194
+ raise QgsProcessingException (self .invalidSinkError (parameters , self .NON_MATCHING ))
195
+
176
196
# do the join
177
197
178
198
# build a list of 'reversed' predicates, because in this function
@@ -182,7 +202,7 @@ def processAlgorithm(self, parameters, context, feedback):
182
202
self .parameterAsEnums (parameters , self .PREDICATE , context )]
183
203
184
204
remaining = set ()
185
- if not discard_nomatch :
205
+ if not discard_nomatch or non_matching_sink is not None :
186
206
remaining = set (source .allFeatureIds ())
187
207
188
208
added_set = set ()
@@ -191,6 +211,9 @@ def processAlgorithm(self, parameters, context, feedback):
191
211
features = join_source .getFeatures (request )
192
212
total = 100.0 / join_source .featureCount () if join_source .featureCount () else 0
193
213
214
+ joined_count = 0
215
+ unjoined_count = 0
216
+
194
217
for current , f in enumerate (features ):
195
218
if feedback .isCanceled ():
196
219
break
@@ -221,21 +244,33 @@ def processAlgorithm(self, parameters, context, feedback):
221
244
if getattr (engine , predicate )(test_feat .geometry ().constGet ()):
222
245
added_set .add (test_feat .id ())
223
246
224
- # join attributes and add
225
- attributes = test_feat .attributes ()
226
- attributes .extend (join_attributes )
227
- output_feature = test_feat
228
- output_feature .setAttributes (attributes )
229
- sink .addFeature (output_feature , QgsFeatureSink .FastInsert )
247
+ if sink is not None :
248
+ # join attributes and add
249
+ attributes = test_feat .attributes ()
250
+ attributes .extend (join_attributes )
251
+ output_feature = test_feat
252
+ output_feature .setAttributes (attributes )
253
+ sink .addFeature (output_feature , QgsFeatureSink .FastInsert )
230
254
break
231
255
232
256
feedback .setProgress (int (current * total ))
233
257
234
- if not discard_nomatch :
258
+ if not discard_nomatch or non_matching_sink is not None :
235
259
remaining = remaining .difference (added_set )
236
260
for f in source .getFeatures (QgsFeatureRequest ().setFilterFids (list (remaining ))):
237
261
if feedback .isCanceled ():
238
262
break
239
- sink .addFeature (f , QgsFeatureSink .FastInsert )
263
+ if sink is not None :
264
+ sink .addFeature (f , QgsFeatureSink .FastInsert )
265
+ if non_matching_sink is not None :
266
+ non_matching_sink .addFeature (f , QgsFeatureSink .FastInsert )
267
+
268
+ result = {}
269
+ if sink is not None :
270
+ result [self .OUTPUT ] = dest_id
271
+ if non_matching_sink is not None :
272
+ result [self .NON_MATCHING ] = non_matching_dest_id
273
+
274
+ result [self .JOINED_COUNT ] = len (added_set )
240
275
241
- return { self . OUTPUT : dest_id }
276
+ return result
0 commit comments