Skip to content

Commit afd21cd

Browse files
committedFeb 8, 2015
Merge pull request #1759 from arnaud-morvan/processing_bylocation_operators
Processing bylocation operators
2 parents b1a2ccd + 84936e5 commit afd21cd

12 files changed

+490
-129
lines changed
 

‎python/plugins/processing/algs/qgis/ExtractByLocation.py

Lines changed: 45 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from qgis.core import QGis, QgsFeatureRequest, QgsGeometry
2929
from processing.core.GeoAlgorithm import GeoAlgorithm
3030
from processing.core.parameters import ParameterVector
31-
from processing.core.parameters import ParameterBoolean
31+
from processing.core.parameters import ParameterGeometryPredicate
3232
from processing.core.outputs import OutputVector
3333
from processing.tools import dataobjects, vector
3434

@@ -37,100 +37,86 @@ class ExtractByLocation(GeoAlgorithm):
3737

3838
INPUT = 'INPUT'
3939
INTERSECT = 'INTERSECT'
40-
TOUCHES = 'TOUCHES'
41-
OVERLAPS = 'OVERLAPS'
42-
WITHIN = 'WITHIN'
40+
PREDICATE = 'PREDICATE'
4341
OUTPUT = 'OUTPUT'
4442

45-
METHODS = ['creating new selection', 'adding to current selection',
46-
'removing from current selection']
47-
opFlags = 0
48-
operators = {'TOUCHES':1,'OVERLAPS':2,'WITHIN':4}
49-
5043
def defineCharacteristics(self):
5144
self.name = 'Extract by location'
5245
self.group = 'Vector selection tools'
5346
self.addParameter(ParameterVector(self.INPUT,
54-
self.tr('Layer to select from'), [ParameterVector.VECTOR_TYPE_ANY]))
47+
self.tr('Layer to select from'),
48+
[ParameterVector.VECTOR_TYPE_ANY]))
5549
self.addParameter(ParameterVector(self.INTERSECT,
5650
self.tr('Additional layer (intersection layer)'),
5751
[ParameterVector.VECTOR_TYPE_ANY]))
58-
self.addParameter(ParameterBoolean(
59-
self.TOUCHES,
60-
self.tr('Include input features that touch the selection features'),
61-
[True]))
62-
self.addParameter(ParameterBoolean(
63-
self.OVERLAPS,
64-
self.tr('Include input features that overlap/cross the selection features'),
65-
[True]))
66-
self.addParameter(ParameterBoolean(
67-
self.WITHIN,
68-
self.tr('Include input features completely within the selection features'),
69-
[True]))
52+
self.addParameter(ParameterGeometryPredicate(self.PREDICATE,
53+
self.tr('Geometric predicate'),
54+
left=self.INPUT, right=self.INTERSECT))
7055
self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection')))
7156

7257
def processAlgorithm(self, progress):
7358
filename = self.getParameterValue(self.INPUT)
7459
layer = dataobjects.getObjectFromUri(filename)
7560
filename = self.getParameterValue(self.INTERSECT)
7661
selectLayer = dataobjects.getObjectFromUri(filename)
77-
index = vector.spatialindex(layer)
62+
predicates = self.getParameterValue(self.PREDICATE)
7863

79-
def _points_op(geomA,geomB):
80-
return geomA.intersects(geomB)
81-
82-
def _poly_lines_op(geomA,geomB):
83-
if geomA.disjoint(geomB):
84-
return False
85-
intersects = False
86-
if self.opFlags & self.operators['TOUCHES']:
87-
intersects |= geomA.touches(geomB)
88-
if not intersects and (self.opFlags & self.operators['OVERLAPS']):
89-
if geomB.type() == QGis.Line or geomA.type() == QGis.Line:
90-
intersects |= geomA.crosses(geomB)
91-
else:
92-
intersects |= geomA.overlaps(geomB)
93-
if not intersects and (self.opFlags & self.operators['WITHIN']):
94-
intersects |= geomA.contains(geomB)
95-
return intersects
96-
97-
def _sp_operator():
98-
if layer.geometryType() == QGis.Point:
99-
return _points_op
100-
else:
101-
return _poly_lines_op
102-
103-
self.opFlags = 0
104-
if self.getParameterValue(self.TOUCHES):
105-
self.opFlags |= self.operators['TOUCHES']
106-
if self.getParameterValue(self.OVERLAPS):
107-
self.opFlags |= self.operators['OVERLAPS']
108-
if self.getParameterValue(self.WITHIN):
109-
self.opFlags |= self.operators['WITHIN']
110-
111-
sp_operator = _sp_operator()
64+
index = vector.spatialindex(layer)
11265

11366
output = self.getOutputFromName(self.OUTPUT)
11467
writer = output.getVectorWriter(layer.pendingFields(),
11568
layer.dataProvider().geometryType(), layer.crs())
11669

70+
if 'disjoint' in predicates:
71+
disjoinSet = []
72+
for feat in vector.features(layer):
73+
disjoinSet.append(feat.id())
74+
11775
geom = QgsGeometry()
11876
selectedSet = []
11977
current = 0
12078
features = vector.features(selectLayer)
12179
featureCount = len(features)
12280
total = 100.0 / float(len(features))
123-
for current,f in enumerate(features):
81+
for current, f in enumerate(features):
12482
geom = QgsGeometry(f.geometry())
12583
intersects = index.intersects(geom.boundingBox())
12684
for i in intersects:
12785
request = QgsFeatureRequest().setFilterFid(i)
12886
feat = layer.getFeatures(request).next()
12987
tmpGeom = QgsGeometry(feat.geometry())
130-
if sp_operator(geom,tmpGeom):
131-
selectedSet.append(feat.id())
88+
res = False
89+
for predicate in predicates:
90+
if predicate == 'disjoint':
91+
if tmpGeom.intersects(geom):
92+
try:
93+
disjoinSet.remove(feat.id())
94+
except:
95+
pass # already removed
96+
else:
97+
if predicate == 'intersects':
98+
res = tmpGeom.intersects()
99+
elif predicate == 'contains':
100+
res = tmpGeom.contains(geom)
101+
elif predicate == 'equals':
102+
res = tmpGeom.equals(geom)
103+
elif predicate == 'touches':
104+
res = tmpGeom.touches(geom)
105+
elif predicate == 'overlaps':
106+
res = tmpGeom.overlaps(geom)
107+
elif predicate == 'within':
108+
res = tmpGeom.within(geom)
109+
elif predicate == 'crosses':
110+
res = tmpGeom.crosses(geom)
111+
if res:
112+
selectedSet.append(feat.id())
113+
break
114+
132115
progress.setPercentage(int(current * total))
133116

117+
if 'disjoint' in predicates:
118+
selectedSet = selectedSet + disjoinSet
119+
134120
for i, f in enumerate(vector.features(layer)):
135121
if f.id() in selectedSet:
136122
writer.addFeature(f)

‎python/plugins/processing/algs/qgis/SelectByLocation.py

Lines changed: 51 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from processing.core.GeoAlgorithm import GeoAlgorithm
3030
from processing.core.parameters import ParameterSelection
3131
from processing.core.parameters import ParameterVector
32-
from processing.core.parameters import ParameterBoolean
32+
from processing.core.parameters import ParameterGeometryPredicate
3333
from processing.core.outputs import OutputVector
3434
from processing.tools import dataobjects, vector
3535

@@ -38,83 +38,47 @@ class SelectByLocation(GeoAlgorithm):
3838

3939
INPUT = 'INPUT'
4040
INTERSECT = 'INTERSECT'
41-
TOUCHES = 'TOUCHES'
42-
OVERLAPS = 'OVERLAPS'
43-
WITHIN = 'WITHIN'
41+
PREDICATE = 'PREDICATE'
4442
METHOD = 'METHOD'
4543
OUTPUT = 'OUTPUT'
4644

47-
METHODS = ['creating new selection', 'adding to current selection',
45+
METHODS = ['creating new selection',
46+
'adding to current selection',
4847
'removing from current selection']
49-
opFlags = 0
50-
operators = {'TOUCHES':1,'OVERLAPS':2,'WITHIN':4}
51-
5248

5349
def defineCharacteristics(self):
5450
self.name = 'Select by location'
5551
self.group = 'Vector selection tools'
56-
self.addParameter(ParameterVector(self.INPUT, 'Layer to select from',
57-
[ParameterVector.VECTOR_TYPE_ANY]))
52+
self.addParameter(ParameterVector(self.INPUT,
53+
self.tr('Layer to select from'),
54+
[ParameterVector.VECTOR_TYPE_ANY]))
5855
self.addParameter(ParameterVector(self.INTERSECT,
59-
'Additional layer (intersection layer)',
60-
[ParameterVector.VECTOR_TYPE_ANY]))
61-
self.addParameter(ParameterBoolean(self.TOUCHES,
62-
'Include input features that touch the selection features',
63-
[True]))
64-
self.addParameter(ParameterBoolean(self.OVERLAPS,
65-
'Include input features that overlap/cross the selection features',
66-
[True]))
67-
self.addParameter(ParameterBoolean(self.WITHIN,
68-
'Include input features completely within the selection features',
69-
[True]))
56+
self.tr('Additional layer (intersection layer)'),
57+
[ParameterVector.VECTOR_TYPE_ANY]))
58+
self.addParameter(ParameterGeometryPredicate(self.PREDICATE,
59+
self.tr('Geometric predicate'),
60+
left=self.INPUT, right=self.INTERSECT))
7061
self.addParameter(ParameterSelection(self.METHOD,
71-
'Modify current selection by', self.METHODS, 0))
72-
self.addOutput(OutputVector(self.OUTPUT, 'Selection', True))
62+
self.tr('Modify current selection by'),
63+
self.METHODS, 0))
64+
self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection'), True))
7365

7466
def processAlgorithm(self, progress):
7567
filename = self.getParameterValue(self.INPUT)
7668
inputLayer = dataobjects.getObjectFromUri(filename)
7769
method = self.getParameterValue(self.METHOD)
7870
filename = self.getParameterValue(self.INTERSECT)
7971
selectLayer = dataobjects.getObjectFromUri(filename)
72+
predicates = self.getParameterValue(self.PREDICATE)
8073

8174
oldSelection = set(inputLayer.selectedFeaturesIds())
8275
inputLayer.removeSelection()
8376
index = vector.spatialindex(inputLayer)
8477

85-
def _points_op(geomA,geomB):
86-
return geomA.intersects(geomB)
87-
88-
def _poly_lines_op(geomA,geomB):
89-
if geomA.disjoint(geomB):
90-
return False
91-
intersects = False
92-
if self.opFlags & self.operators['TOUCHES']:
93-
intersects |= geomA.touches(geomB)
94-
if not intersects and (self.opFlags & self.operators['OVERLAPS']):
95-
if geomB.type() == QGis.Line or geomA.type() == QGis.Line:
96-
intersects |= geomA.crosses(geomB)
97-
else:
98-
intersects |= geomA.overlaps(geomB)
99-
if not intersects and (self.opFlags & self.operators['WITHIN']):
100-
intersects |= geomA.contains(geomB)
101-
return intersects
102-
103-
def _sp_operator():
104-
if inputLayer.geometryType() == QGis.Point:
105-
return _points_op
106-
else:
107-
return _poly_lines_op
108-
109-
self.opFlags = 0
110-
if self.getParameterValue(self.TOUCHES):
111-
self.opFlags |= self.operators['TOUCHES']
112-
if self.getParameterValue(self.OVERLAPS):
113-
self.opFlags |= self.operators['OVERLAPS']
114-
if self.getParameterValue(self.WITHIN):
115-
self.opFlags |= self.operators['WITHIN']
116-
117-
sp_operator = _sp_operator()
78+
if 'disjoint' in predicates:
79+
disjoinSet = []
80+
for feat in vector.features(inputLayer):
81+
disjoinSet.append(feat.id())
11882

11983
geom = QgsGeometry()
12084
selectedSet = []
@@ -123,16 +87,45 @@ def _sp_operator():
12387
total = 100.0 / float(len(features))
12488
for f in features:
12589
geom = QgsGeometry(f.geometry())
90+
12691
intersects = index.intersects(geom.boundingBox())
12792
for i in intersects:
12893
request = QgsFeatureRequest().setFilterFid(i)
12994
feat = inputLayer.getFeatures(request).next()
13095
tmpGeom = QgsGeometry(feat.geometry())
131-
if sp_operator(geom,tmpGeom):
132-
selectedSet.append(feat.id())
96+
res = False
97+
for predicate in predicates:
98+
if predicate == 'disjoint':
99+
if tmpGeom.intersects(geom):
100+
try:
101+
disjoinSet.remove(feat.id())
102+
except:
103+
pass # already removed
104+
else:
105+
if predicate == 'intersects':
106+
res = tmpGeom.intersects()
107+
elif predicate == 'contains':
108+
res = tmpGeom.contains(geom)
109+
elif predicate == 'equals':
110+
res = tmpGeom.equals(geom)
111+
elif predicate == 'touches':
112+
res = tmpGeom.touches(geom)
113+
elif predicate == 'overlaps':
114+
res = tmpGeom.overlaps(geom)
115+
elif predicate == 'within':
116+
res = tmpGeom.within(geom)
117+
elif predicate == 'crosses':
118+
res = tmpGeom.crosses(geom)
119+
if res:
120+
selectedSet.append(feat.id())
121+
break
122+
133123
current += 1
134124
progress.setPercentage(int(current * total))
135125

126+
if 'disjoint' in predicates:
127+
selectedSet = selectedSet + disjoinSet
128+
136129
if method == 1:
137130
selectedSet = list(oldSelection.union(selectedSet))
138131
elif method == 2:

‎python/plugins/processing/algs/qgis/SpatialJoin.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from processing.core.GeoAlgorithm import GeoAlgorithm
3030
from processing.core.parameters import ParameterVector
31+
from processing.core.parameters import ParameterGeometryPredicate
3132
from processing.core.parameters import ParameterSelection
3233
from processing.core.parameters import ParameterString
3334
from processing.core.outputs import OutputVector
@@ -37,6 +38,7 @@
3738
class SpatialJoin(GeoAlgorithm):
3839
TARGET = "TARGET"
3940
JOIN = "JOIN"
41+
PREDICATE = "PREDICATE"
4042
SUMMARY = "SUMMARY"
4143
STATS = "STATS"
4244
KEEP = "KEEP"
@@ -57,23 +59,32 @@ def defineCharacteristics(self):
5759
self.group = "Vector general tools"
5860

5961
self.addParameter(ParameterVector(self.TARGET,
60-
'Target vector layer', [ParameterVector.VECTOR_TYPE_ANY]))
62+
self.tr('Target vector layer'),
63+
[ParameterVector.VECTOR_TYPE_ANY]))
6164
self.addParameter(ParameterVector(self.JOIN,
62-
'Join vector layer', [ParameterVector.VECTOR_TYPE_ANY]))
65+
self.tr('Join vector layer'),
66+
[ParameterVector.VECTOR_TYPE_ANY]))
67+
predicates = list(ParameterGeometryPredicate.predicates)
68+
predicates.remove('disjoint')
69+
self.addParameter(ParameterGeometryPredicate(self.PREDICATE,
70+
self.tr('Geometric predicate'),
71+
left=self.TARGET, right=self.JOIN,
72+
enabledPredicates=predicates))
6373
self.addParameter(ParameterSelection(self.SUMMARY,
64-
'Attribute summary', self.SUMMARYS))
74+
self.tr('Attribute summary'), self.SUMMARYS))
6575
self.addParameter(ParameterString(self.STATS,
66-
'Statistics for summary (comma separated)',
67-
'sum,mean,min,max,median'))
76+
self.tr('Statistics for summary (comma separated)'),
77+
'sum,mean,min,max,median', optional=True))
6878
self.addParameter(ParameterSelection(self.KEEP,
69-
'Output table', self.KEEPS))
70-
self.addOutput(OutputVector(self.OUTPUT, 'Output layer'))
79+
self.tr('Output table'), self.KEEPS))
80+
self.addOutput(OutputVector(self.OUTPUT, self.tr('Output layer')))
7181

7282
def processAlgorithm(self, progress):
7383
target = dataobjects.getObjectFromUri(
7484
self.getParameterValue(self.TARGET))
7585
join = dataobjects.getObjectFromUri(
7686
self.getParameterValue(self.JOIN))
87+
predicates = self.getParameterValue(self.PREDICATE)
7788

7889
summary = self.getParameterValue(self.SUMMARY) == 1
7990
keep = self.getParameterValue(self.KEEP) == 1
@@ -151,7 +162,28 @@ def processAlgorithm(self, progress):
151162
count = 0
152163
for i in joinList:
153164
inFeatB = mapP2[i]
154-
if inGeom.intersects(inFeatB.geometry()):
165+
inGeomB = inFeatB.geometry()
166+
167+
res = False
168+
for predicate in predicates:
169+
if predicate == 'intersects':
170+
res = inGeom.intersects(inGeomB)
171+
elif predicate == 'contains':
172+
res = inGeom.contains(inGeomB)
173+
elif predicate == 'equals':
174+
res = inGeom.equals(inGeomB)
175+
elif predicate == 'touches':
176+
res = inGeom.touches(inGeomB)
177+
elif predicate == 'overlaps':
178+
res = inGeom.overlaps(inGeomB)
179+
elif predicate == 'within':
180+
res = inGeom.within(inGeomB)
181+
elif predicate == 'crosses':
182+
res = inGeom.crosses(inGeomB)
183+
if res:
184+
break
185+
186+
if res:
155187
count = count + 1
156188
none = False
157189
atMap2 = inFeatB.attributes()
@@ -173,7 +205,7 @@ def processAlgorithm(self, progress):
173205
atMap.append(sum(self._filterNull(numFields[j])))
174206
elif k == 'mean':
175207
try:
176-
nn_count = sum( 1 for _ in self._filterNull(numFields[j]) )
208+
nn_count = sum(1 for _ in self._filterNull(numFields[j]))
177209
atMap.append(sum(self._filterNull(numFields[j])) / nn_count)
178210
except ZeroDivisionError:
179211
atMap.append(NULL)
@@ -221,8 +253,8 @@ def _median(self, data):
221253

222254
median = 0
223255
if count > 1:
224-
if ( count % 2 ) == 0:
225-
median = 0.5 * ((data[count / 2 - 1]) + (data[count / 2]))
256+
if (count % 2) == 0:
257+
median = 0.5 * ((data[count / 2 - 1]) + (data[count / 2]))
226258
else:
227259
median = data[(count + 1) / 2 - 1]
228260

‎python/plugins/processing/core/parameters.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,3 +782,41 @@ def dataType(self):
782782
types += 'any, '
783783

784784
return types[:-2]
785+
786+
787+
class ParameterGeometryPredicate(Parameter):
788+
789+
predicates = ('intersects',
790+
'contains',
791+
'disjoint',
792+
'equals',
793+
'touches',
794+
'overlaps',
795+
'within',
796+
'crosses')
797+
798+
def __init__(self, name='', description='', left=None, right=None,
799+
optional=False, enabledPredicates=None):
800+
Parameter.__init__(self, name, description)
801+
self.left = left
802+
self.right = right
803+
self.value = None
804+
self.default = []
805+
self.optional = parseBool(optional)
806+
self.enabledPredicates = enabledPredicates
807+
if self.enabledPredicates is None:
808+
self.enabledPredicates = self.predicates
809+
810+
def getValueAsCommandLineParameter(self):
811+
return '"' + unicode(self.value) + '"'
812+
813+
def setValue(self, value):
814+
if value is None:
815+
return self.optional
816+
elif len(value) == 0:
817+
return self.optional
818+
if isinstance(value, unicode):
819+
self.value = value.split(';') # relates to ModelerAlgorithm.resolveValue
820+
else:
821+
self.value = value
822+
return True

‎python/plugins/processing/gui/AlgorithmDialog.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from processing.core.parameters import ParameterNumber
5151
from processing.core.parameters import ParameterFile
5252
from processing.core.parameters import ParameterCrs
53+
from processing.core.parameters import ParameterGeometryPredicate
5354

5455
from processing.core.outputs import OutputRaster
5556
from processing.core.outputs import OutputVector
@@ -135,6 +136,8 @@ def setParamValue(self, param, widget, alg=None):
135136
return param.setValue(unicode(widget.toPlainText()))
136137
else:
137138
return param.setValue(unicode(widget.text()))
139+
elif isinstance(param, ParameterGeometryPredicate):
140+
return param.setValue(widget.value())
138141
else:
139142
return param.setValue(unicode(widget.text()))
140143

‎python/plugins/processing/gui/BatchAlgorithmDialog.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from processing.core.parameters import ParameterSelection
4646
from processing.core.parameters import ParameterFixedTable
4747
from processing.core.parameters import ParameterMultipleInput
48+
from processing.core.parameters import ParameterGeometryPredicate
4849
from processing.core.outputs import OutputNumber
4950
from processing.core.outputs import OutputString
5051
from processing.core.outputs import OutputHTML
@@ -84,6 +85,8 @@ def setParamValue(self, param, widget, alg=None):
8485
return param.setValue(widget.getValue())
8586
elif isinstance(param, (ParameterCrs, ParameterFile)):
8687
return param.setValue(widget.getValue())
88+
elif isinstance(param, ParameterGeometryPredicate):
89+
return param.setValue(widget.value())
8790
else:
8891
return param.setValue(widget.text())
8992

‎python/plugins/processing/gui/BatchPanel.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from processing.gui.FixedTablePanel import FixedTablePanel
3636
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
3737
from processing.gui.BatchOutputSelectionPanel import BatchOutputSelectionPanel
38+
from processing.gui.GeometryPredicateSelectionPanel import GeometryPredicateSelectionPanel
3839

3940
from processing.core.parameters import ParameterFile
4041
from processing.core.parameters import ParameterRaster
@@ -46,6 +47,7 @@
4647
from processing.core.parameters import ParameterSelection
4748
from processing.core.parameters import ParameterFixedTable
4849
from processing.core.parameters import ParameterMultipleInput
50+
from processing.core.parameters import ParameterGeometryPredicate
4951

5052
from processing.ui.ui_widgetBatchPanel import Ui_Form
5153

@@ -136,6 +138,11 @@ def getWidgetFromParameter(self, param, row, col):
136138
item = CrsSelectionPanel(param.default)
137139
elif isinstance(param, ParameterFile):
138140
item = FileSelectionPanel(param.isFolder)
141+
elif isinstance(param, ParameterGeometryPredicate):
142+
item = GeometryPredicateSelectionPanel(param.enabledPredicates, rows=1)
143+
width = max(self.tblParameters.columnWidth(col),
144+
item.sizeHint().width())
145+
self.tblParameters.setColumnWidth(col, width)
139146
else:
140147
item = QLineEdit()
141148
try:
@@ -214,6 +221,10 @@ def fillParameterValues(self, column):
214221
widgetValue = widget.getText()
215222
for row in range(1, self.tblParameters.rowCount()):
216223
self.tblParameters.cellWidget(row, column).setText(widgetValue)
224+
elif isinstance(widget, GeometryPredicateSelectionPanel):
225+
widgetValue = widget.value()
226+
for row in range(1, self.tblParameters.rowCount()):
227+
self.tblParameters.cellWidget(row, column).setValue(widgetValue)
217228
else:
218229
pass
219230

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
PredicatePanel.py
6+
---------------------
7+
Date : January 2015
8+
Copyright : (C) 2015 by Arnaud Morvan
9+
Email : arnaud dot morvan at camptocamp dot com
10+
Contributors : Arnaud Morvan
11+
***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************
19+
"""
20+
21+
__author__ = 'Arnaud Morvan'
22+
__date__ = 'January 2015'
23+
__copyright__ = '(C) 2015, Arnaud Morvan'
24+
25+
# This will get replaced with a git SHA1 when you do a git archive
26+
__revision__ = '$Format:%H$'
27+
28+
29+
from PyQt4.QtGui import QWidget, QCheckBox
30+
from qgis.core import QGis, QgsVectorLayer
31+
32+
from processing.core.parameters import ParameterGeometryPredicate
33+
from processing.ui.ui_widgetGeometryPredicateSelector import Ui_Form
34+
35+
36+
class GeometryPredicateSelectionPanel(QWidget, Ui_Form):
37+
38+
unusablePredicates = {
39+
QGis.Point : {
40+
QGis.Point : ('touches', 'crosses'),
41+
QGis.Line : ('equals', 'contains', 'overlaps'),
42+
QGis.Polygon : ('equals', 'contains', 'overlaps')
43+
},
44+
QGis.Line : {
45+
QGis.Point : ('equals', 'within', 'overlaps'),
46+
QGis.Line : [],
47+
QGis.Polygon : ('equals', 'contains', 'overlaps')
48+
},
49+
QGis.Polygon : {
50+
QGis.Point : ('equals', 'within', 'overlaps'),
51+
QGis.Line : ('equals', 'within', 'overlaps'),
52+
QGis.Polygon : ('crosses')
53+
}
54+
}
55+
56+
def __init__(self,
57+
enabledPredicated=ParameterGeometryPredicate.predicates,
58+
rows=4):
59+
QWidget.__init__(self)
60+
self.setupUi(self)
61+
62+
self.enabledPredicated = enabledPredicated
63+
self.leftLayer = None
64+
self.rightLayer = None
65+
self.setRows(rows)
66+
self.updatePredicates()
67+
68+
def onLeftLayerChange(self):
69+
sender = self.sender()
70+
self.leftLayer = sender.itemData(sender.currentIndex())
71+
self.updatePredicates()
72+
73+
def onRightLayerChange(self):
74+
sender = self.sender()
75+
self.rightLayer = sender.itemData(sender.currentIndex())
76+
self.updatePredicates()
77+
78+
def updatePredicates(self):
79+
if (isinstance(self.leftLayer, QgsVectorLayer)
80+
and isinstance(self.rightLayer, QgsVectorLayer)):
81+
leftType = self.leftLayer.geometryType()
82+
rightType = self.rightLayer.geometryType()
83+
unusablePredicates = self.unusablePredicates[leftType][rightType]
84+
else:
85+
unusablePredicates = []
86+
for predicate in ParameterGeometryPredicate.predicates:
87+
widget = self.getWidget(predicate)
88+
widget.setEnabled(predicate in self.enabledPredicated
89+
and not predicate in unusablePredicates)
90+
91+
def setRows(self, rows):
92+
widgets = []
93+
for predicate in ParameterGeometryPredicate.predicates:
94+
widget = self.getWidget(predicate)
95+
self.gridLayout.removeWidget(widget)
96+
widgets.append(widget)
97+
for i in xrange(0, len(widgets)):
98+
widget = widgets[i]
99+
self.gridLayout.addWidget(widget, i % rows, i / rows)
100+
101+
def getWidget(self, predicate):
102+
return self.findChild(QCheckBox, predicate + 'Box')
103+
104+
def value(self):
105+
values = []
106+
for predicate in ParameterGeometryPredicate.predicates:
107+
widget = self.getWidget(predicate)
108+
if widget.isEnabled() and widget.isChecked():
109+
values.append(predicate)
110+
return values
111+
112+
def setValue(self, values):
113+
for predicate in ParameterGeometryPredicate.predicates:
114+
widget = self.getWidget(predicate)
115+
widget.setChecked(predicate in values)
116+
return True

‎python/plugins/processing/gui/ParametersPanel.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
from processing.gui.ExtentSelectionPanel import ExtentSelectionPanel
4848
from processing.gui.FileSelectionPanel import FileSelectionPanel
4949
from processing.gui.CrsSelectionPanel import CrsSelectionPanel
50+
from processing.gui.GeometryPredicateSelectionPanel import \
51+
GeometryPredicateSelectionPanel
5052

5153
from processing.core.parameters import ParameterRaster
5254
from processing.core.parameters import ParameterVector
@@ -62,6 +64,7 @@
6264
from processing.core.parameters import ParameterFile
6365
from processing.core.parameters import ParameterCrs
6466
from processing.core.parameters import ParameterString
67+
from processing.core.parameters import ParameterGeometryPredicate
6568

6669
from processing.core.outputs import OutputRaster
6770
from processing.core.outputs import OutputTable
@@ -321,6 +324,22 @@ def getWidgetFromParameter(self, param):
321324
else:
322325
item = QLineEdit()
323326
item.setText(str(param.default))
327+
elif isinstance(param, ParameterGeometryPredicate):
328+
item = GeometryPredicateSelectionPanel(param.enabledPredicates)
329+
if param.left:
330+
widget = self.valueItems[param.left]
331+
if isinstance(widget, InputLayerSelectorPanel):
332+
widget = widget.cmbText
333+
widget.currentIndexChanged.connect(item.onLeftLayerChange)
334+
item.leftLayer = widget.itemData(widget.currentIndex())
335+
if param.right:
336+
widget = self.valueItems[param.right]
337+
if isinstance(widget, InputLayerSelectorPanel):
338+
widget = widget.cmbText
339+
widget.currentIndexChanged.connect(item.onRightLayerChange)
340+
item.rightLayer = widget.itemData(widget.currentIndex())
341+
item.updatePredicates()
342+
item.setValue(param.default)
324343
else:
325344
item = QLineEdit()
326345
item.setText(str(param.default))

‎python/plugins/processing/modeler/ModelerParametersDialog.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
from processing.gui.MultipleInputPanel import MultipleInputPanel
3636
from processing.gui.FixedTablePanel import FixedTablePanel
3737
from processing.gui.RangePanel import RangePanel
38+
from processing.gui.GeometryPredicateSelectionPanel import \
39+
GeometryPredicateSelectionPanel
3840
from processing.modeler.MultilineTextPanel import MultilineTextPanel
39-
from processing.core.parameters import ParameterExtent, ParameterRaster, ParameterVector, ParameterBoolean, ParameterTable, ParameterFixedTable, ParameterMultipleInput, ParameterSelection, ParameterRange, ParameterNumber, ParameterString, ParameterCrs, ParameterTableField, ParameterFile
41+
from processing.core.parameters import ParameterExtent, ParameterRaster, ParameterVector, ParameterBoolean, ParameterTable, ParameterFixedTable, ParameterMultipleInput, ParameterSelection, ParameterRange, ParameterNumber, ParameterString, ParameterCrs, ParameterTableField, ParameterFile, ParameterGeometryPredicate
4042
from processing.core.outputs import OutputRaster, OutputVector, OutputTable, OutputHTML, OutputFile, OutputDirectory, OutputNumber, OutputString, OutputExtent
4143

4244

@@ -335,6 +337,8 @@ def getWidgetFromParameter(self, param):
335337
files = self.getAvailableValuesOfType(ParameterFile, OutputFile)
336338
for f in files:
337339
item.addItem(self.resolveValueDescription(f), f)
340+
elif isinstance(param, ParameterGeometryPredicate):
341+
item = GeometryPredicateSelectionPanel(param.enabledPredicates)
338342
else:
339343
item = QLineEdit()
340344
try:
@@ -438,6 +442,8 @@ def setPreviousValues(self):
438442
if opt in value:
439443
selected.append(i)
440444
widget.setSelectedItems(selected)
445+
elif isinstance(param, ParameterGeometryPredicate):
446+
widget.setValue(value)
441447

442448
for name, out in alg.outputs.iteritems():
443449
widget = self.valueItems[name].setText(out.description)
@@ -619,6 +625,9 @@ def setParamValue(self, alg, param, widget):
619625
return False
620626
alg.params[param.name] = values
621627
return True
628+
elif isinstance(param, ParameterGeometryPredicate):
629+
alg.params[param.name] = widget.value()
630+
return True
622631
else:
623632
alg.params[param.name] = unicode(widget.text())
624633
return True
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Form implementation generated from reading ui file 'widgetGeometryPredicateSelector.ui'
4+
#
5+
# Created: Mon Jan 19 11:52:29 2015
6+
# by: PyQt4 UI code generator 4.10.4
7+
#
8+
# WARNING! All changes made in this file will be lost!
9+
10+
from PyQt4 import QtCore, QtGui
11+
12+
try:
13+
_fromUtf8 = QtCore.QString.fromUtf8
14+
except AttributeError:
15+
def _fromUtf8(s):
16+
return s
17+
18+
try:
19+
_encoding = QtGui.QApplication.UnicodeUTF8
20+
def _translate(context, text, disambig):
21+
return QtGui.QApplication.translate(context, text, disambig, _encoding)
22+
except AttributeError:
23+
def _translate(context, text, disambig):
24+
return QtGui.QApplication.translate(context, text, disambig)
25+
26+
class Ui_Form(object):
27+
def setupUi(self, Form):
28+
Form.setObjectName(_fromUtf8("Form"))
29+
Form.resize(609, 213)
30+
self.gridLayout = QtGui.QGridLayout(Form)
31+
self.gridLayout.setMargin(0)
32+
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
33+
self.equalsBox = QtGui.QCheckBox(Form)
34+
self.equalsBox.setObjectName(_fromUtf8("equalsBox"))
35+
self.gridLayout.addWidget(self.equalsBox, 0, 0, 1, 1)
36+
self.containsBox = QtGui.QCheckBox(Form)
37+
self.containsBox.setObjectName(_fromUtf8("containsBox"))
38+
self.gridLayout.addWidget(self.containsBox, 1, 0, 1, 1)
39+
self.touchesBox = QtGui.QCheckBox(Form)
40+
self.touchesBox.setObjectName(_fromUtf8("touchesBox"))
41+
self.gridLayout.addWidget(self.touchesBox, 3, 0, 1, 1)
42+
self.intersectsBox = QtGui.QCheckBox(Form)
43+
self.intersectsBox.setObjectName(_fromUtf8("intersectsBox"))
44+
self.gridLayout.addWidget(self.intersectsBox, 2, 0, 1, 1)
45+
self.withinBox = QtGui.QCheckBox(Form)
46+
self.withinBox.setObjectName(_fromUtf8("withinBox"))
47+
self.gridLayout.addWidget(self.withinBox, 0, 1, 1, 1)
48+
self.overlapsBox = QtGui.QCheckBox(Form)
49+
self.overlapsBox.setObjectName(_fromUtf8("overlapsBox"))
50+
self.gridLayout.addWidget(self.overlapsBox, 1, 1, 1, 1)
51+
self.crossesBox = QtGui.QCheckBox(Form)
52+
self.crossesBox.setObjectName(_fromUtf8("crossesBox"))
53+
self.gridLayout.addWidget(self.crossesBox, 2, 1, 1, 1)
54+
self.disjointBox = QtGui.QCheckBox(Form)
55+
self.disjointBox.setObjectName(_fromUtf8("disjointBox"))
56+
self.gridLayout.addWidget(self.disjointBox, 3, 1, 1, 1)
57+
58+
self.retranslateUi(Form)
59+
QtCore.QMetaObject.connectSlotsByName(Form)
60+
61+
def retranslateUi(self, Form):
62+
Form.setWindowTitle(_translate("Form", "Form", None))
63+
self.equalsBox.setText(_translate("Form", "equals", None))
64+
self.containsBox.setText(_translate("Form", "contains", None))
65+
self.touchesBox.setText(_translate("Form", "touches", None))
66+
self.intersectsBox.setText(_translate("Form", "intersects", None))
67+
self.withinBox.setText(_translate("Form", "within", None))
68+
self.overlapsBox.setText(_translate("Form", "overlaps", None))
69+
self.crossesBox.setText(_translate("Form", "crosses", None))
70+
self.disjointBox.setText(_translate("Form", "disjoint", None))
71+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>Form</class>
4+
<widget class="QWidget" name="Form">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>609</width>
10+
<height>213</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Form</string>
15+
</property>
16+
<layout class="QGridLayout" name="gridLayout">
17+
<property name="margin">
18+
<number>0</number>
19+
</property>
20+
<item row="0" column="0">
21+
<widget class="QCheckBox" name="equalsBox">
22+
<property name="text">
23+
<string>equals</string>
24+
</property>
25+
</widget>
26+
</item>
27+
<item row="1" column="0">
28+
<widget class="QCheckBox" name="containsBox">
29+
<property name="text">
30+
<string>contains</string>
31+
</property>
32+
</widget>
33+
</item>
34+
<item row="3" column="0">
35+
<widget class="QCheckBox" name="touchesBox">
36+
<property name="text">
37+
<string>touches</string>
38+
</property>
39+
</widget>
40+
</item>
41+
<item row="2" column="0">
42+
<widget class="QCheckBox" name="intersectsBox">
43+
<property name="text">
44+
<string>intersects</string>
45+
</property>
46+
</widget>
47+
</item>
48+
<item row="0" column="1">
49+
<widget class="QCheckBox" name="withinBox">
50+
<property name="text">
51+
<string>within</string>
52+
</property>
53+
</widget>
54+
</item>
55+
<item row="1" column="1">
56+
<widget class="QCheckBox" name="overlapsBox">
57+
<property name="text">
58+
<string>overlaps</string>
59+
</property>
60+
</widget>
61+
</item>
62+
<item row="2" column="1">
63+
<widget class="QCheckBox" name="crossesBox">
64+
<property name="text">
65+
<string>crosses</string>
66+
</property>
67+
</widget>
68+
</item>
69+
<item row="3" column="1">
70+
<widget class="QCheckBox" name="disjointBox">
71+
<property name="text">
72+
<string>disjoint</string>
73+
</property>
74+
</widget>
75+
</item>
76+
</layout>
77+
</widget>
78+
<resources/>
79+
<connections/>
80+
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.