2929
3030from qgis .PyQt .QtGui import QIcon
3131
32- from qgis .core import QgsGeometry , QgsFeatureRequest , QgsProcessingUtils
32+ from qgis .core import (QgsGeometry ,
33+ QgsFeatureRequest ,
34+ QgsProcessingUtils ,
35+ QgsProcessing ,
36+ QgsProcessingParameterVectorLayer ,
37+ QgsProcessingParameterFeatureSource ,
38+ QgsProcessingParameterEnum ,
39+ QgsProcessingParameterNumber ,
40+ QgsProcessingOutputVectorLayer ,
41+ QgsVectorLayer )
42+
3343
3444from processing .algs .qgis .QgisAlgorithm import QgisAlgorithm
35- from processing .core .parameters import ParameterSelection
36- from processing .core .parameters import ParameterVector
37- from processing .core .parameters import ParameterNumber
38- from processing .core .outputs import OutputVector
3945from processing .tools import vector
4046
4147pluginPath = os .path .split (os .path .split (os .path .dirname (__file__ ))[0 ])[0 ]
@@ -63,32 +69,43 @@ def initAlgorithm(self, config=None):
6369 self .predicates = (
6470 ('intersects' , self .tr ('intersects' )),
6571 ('contains' , self .tr ('contains' )),
66- ('disjoint' , self .tr ('disjoint' )),
67- ('equals ' , self .tr ('equals' )),
72+ ('disjoint' , self .tr ('is disjoint' )),
73+ ('isEqual ' , self .tr ('equals' )),
6874 ('touches' , self .tr ('touches' )),
6975 ('overlaps' , self .tr ('overlaps' )),
7076 ('within' , self .tr ('within' )),
7177 ('crosses' , self .tr ('crosses' )))
7278
79+ self .reversed_predicates = {'intersects' : 'intersects' ,
80+ 'contains' : 'within' ,
81+ 'disjoint' : 'disjoint' ,
82+ 'isEqual' : 'isEqual' ,
83+ 'touches' : 'touches' ,
84+ 'overlaps' : 'overlaps' ,
85+ 'within' : 'contains' ,
86+ 'crosses' : 'crosses' }
87+
7388 self .methods = [self .tr ('creating new selection' ),
7489 self .tr ('adding to current selection' ),
90+ self .tr ('select within current selection' ),
7591 self .tr ('removing from current selection' )]
7692
77- self .addParameter (ParameterVector (self .INPUT ,
78- self .tr ('Layer to select from' )))
79- self .addParameter (ParameterVector (self .INTERSECT ,
80- self .tr ('Additional layer (intersection layer)' )))
81- self .addParameter (ParameterSelection (self .PREDICATE ,
82- self .tr ('Geometric predicate' ),
83- self .predicates ,
84- multiple = True ))
85- self .addParameter (ParameterNumber (self .PRECISION ,
86- self .tr ('Precision' ),
87- 0.0 , None , 0.0 ))
88- self .addParameter (ParameterSelection (self .METHOD ,
89- self .tr ('Modify current selection by' ),
90- self .methods , 0 ))
91- self .addOutput (OutputVector (self .OUTPUT , self .tr ('Selected (location)' ), True ))
93+ self .addParameter (QgsProcessingParameterVectorLayer (self .INPUT ,
94+ self .tr ('Select features from' ), types = [QgsProcessing .TypeVectorAnyGeometry ]))
95+ self .addParameter (QgsProcessingParameterEnum (self .PREDICATE ,
96+ self .tr ('Where the features are (geometric predicate)' ),
97+ options = [p [1 ] for p in self .predicates ],
98+ allowMultiple = True , defaultValue = [0 ]))
99+ self .addParameter (QgsProcessingParameterFeatureSource (self .INTERSECT ,
100+ self .tr ('By comparing to the features from' ), types = [QgsProcessing .TypeVectorAnyGeometry ]))
101+ self .addParameter (QgsProcessingParameterNumber (self .PRECISION ,
102+ self .tr ('Precision' ), type = QgsProcessingParameterNumber .Double ,
103+ minValue = 0.0 , defaultValue = 0.0 ))
104+ self .addParameter (QgsProcessingParameterEnum (self .METHOD ,
105+ self .tr ('Modify current selection by' ),
106+ options = self .methods , defaultValue = 0 ))
107+
108+ self .addOutput (QgsProcessingOutputVectorLayer (self .OUTPUT , self .tr ('Selected (by location)' )))
92109
93110 def name (self ):
94111 return 'selectbylocation'
@@ -97,60 +114,61 @@ def displayName(self):
97114 return self .tr ('Select by location' )
98115
99116 def processAlgorithm (self , parameters , context , feedback ):
100- filename = self .getParameterValue (self .INPUT )
101- inputLayer = QgsProcessingUtils .mapLayerFromString (filename , context )
102- method = self .getParameterValue (self .METHOD )
103- filename2 = self .getParameterValue (self .INTERSECT )
104- selectLayer = QgsProcessingUtils .mapLayerFromString (filename2 , context )
105- predicates = self .getParameterValue (self .PREDICATE )
106- precision = self .getParameterValue (self .PRECISION )
107-
108- oldSelection = set (inputLayer .selectedFeatureIds ())
109- inputLayer .removeSelection ()
110- index = QgsProcessingUtils .createSpatialIndex (inputLayer , context )
117+ select_layer = self .parameterAsVectorLayer (parameters , self .INPUT , context )
118+ method = QgsVectorLayer .SelectBehavior (self .parameterAsEnum (parameters , self .METHOD , context ))
119+ intersect_source = self .parameterAsSource (parameters , self .INTERSECT , context )
120+ # build a list of 'reversed' predicates, because in this function
121+ # we actually test the reverse of what the user wants (allowing us
122+ # to prepare geometries and optimise the algorithm)
123+ predicates = [self .reversed_predicates [self .predicates [i ][0 ]] for i in self .parameterAsEnums (parameters , self .PREDICATE , context )]
124+ precision = self .parameterAsDouble (parameters , self .PRECISION , context )
111125
112126 if 'disjoint' in predicates :
113- disjoinSet = []
114- for feat in QgsProcessingUtils . getFeatures ( inputLayer , context ) :
115- disjoinSet . append ( feat . id ())
116-
117- geom = QgsGeometry ()
118- selectedSet = []
119- features = QgsProcessingUtils .getFeatures (selectLayer , context )
120- total = 100.0 / selectLayer .featureCount () if selectLayer .featureCount () else 0
127+ disjoint_set = select_layer . allFeatureIds ()
128+ else :
129+ disjoint_set = None
130+
131+ selected_set = set ()
132+ request = QgsFeatureRequest (). setSubsetOfAttributes ([]). setDestinationCrs ( select_layer . crs ())
133+ features = intersect_source .getFeatures (request )
134+ total = 100.0 / intersect_source .featureCount () if intersect_source .featureCount () else 0
121135 for current , f in enumerate (features ):
122- geom = vector .snapToPrecision (f .geometry (), precision )
123- bbox = geom .boundingBox ()
136+ if feedback .isCanceled ():
137+ break
138+
139+ if not f .hasGeometry ():
140+ continue
141+
142+ engine = QgsGeometry .createGeometryEngine (f .geometry ().geometry ())
143+ engine .prepareGeometry ()
144+ bbox = f .geometry ().boundingBox ()
124145 bbox .grow (0.51 * precision )
125- intersects = index .intersects (bbox )
126146
127- request = QgsFeatureRequest ().setFilterFids (intersects ).setSubsetOfAttributes ([])
128- for feat in inputLayer .getFeatures (request ):
129- tmpGeom = vector .snapToPrecision (feat .geometry (), precision )
147+ request = QgsFeatureRequest ().setFlags (QgsFeatureRequest .NoGeometry ).setFilterRect (bbox ).setSubsetOfAttributes ([])
148+ for test_feat in select_layer .getFeatures (request ):
149+ if feedback .isCanceled ():
150+ break
151+
152+ if test_feat in selected_set :
153+ # already added this one, no need for further tests
154+ continue
130155
131- res = False
132156 for predicate in predicates :
133157 if predicate == 'disjoint' :
134- if tmpGeom . intersects (geom ):
158+ if test_feat . geometry (). intersects (f . geometry () ):
135159 try :
136- disjoinSet .remove (feat .id ())
160+ disjoint_set .remove (test_feat .id ())
137161 except :
138162 pass # already removed
139163 else :
140- res = getattr (tmpGeom , predicate )(geom )
141- if res :
142- selectedSet .append (feat .id ())
164+ if getattr (engine , predicate )(test_feat .geometry ().geometry ()):
165+ selected_set .add (test_feat .id ())
143166 break
144167
145168 feedback .setProgress (int (current * total ))
146169
147170 if 'disjoint' in predicates :
148- selectedSet = selectedSet + disjoinSet
149-
150- if method == 1 :
151- selectedSet = list (oldSelection .union (selectedSet ))
152- elif method == 2 :
153- selectedSet = list (oldSelection .difference (selectedSet ))
171+ selected_set = list (selected_set ) + disjoint_set
154172
155- inputLayer .selectByIds (selectedSet )
156- self .setOutputValue ( self . OUTPUT , filename )
173+ select_layer .selectByIds (list ( selected_set ), method )
174+ return { self .OUTPUT : parameters [ self . INPUT ]}
0 commit comments