-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfilter_wdg.py
363 lines (282 loc) · 11.4 KB
/
filter_wdg.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : GEM Modellers Toolkit plugin (GEM-MT)
Description : Analysing and Processing Earthquake Catalogue Data
Date : Jun 18, 2012
copyright : (C) 2012 by Giuseppe Sucameli (Faunalia)
email : [email protected]
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
import re
import traceback
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
from qgis.gui import *
from utils import Utils
from MapTools import PolygonDrawer
from ui.filterWdg_ui import Ui_FilterWdg
class FilterWdg(QWidget, Ui_FilterWdg):
PLOT_HIST, PLOT_HIST_LOG, PLOT_SCATTER = range(3)
def __init__(self, iface, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
# store the iface and the event layer
self.iface = iface
self.vl = Utils.eventsVl()
self.canvas = self.iface.mapCanvas()
self._prevMapTool = None
# setup the magnitude, depth and range filters
self.setupFilters()
# create the maptool to draw polygons
self.polygonDrawer = PolygonDrawer( self.canvas, {'color':QColor(102,102,102, 90), 'enableSnap':False, 'keepAfterEnd':True} )
self.polygonDrawer.setAction( self.drawPolygonBtn )
self.connect(self.polygonDrawer, SIGNAL("geometryEmitted"), self.polygonCreated)
# populate plot type combo
self.plotTypeCombo.addItem( "Histogram", self.PLOT_HIST )
self.plotTypeCombo.addItem( "Histogram (log10)", self.PLOT_HIST_LOG )
self.plotTypeCombo.addItem( "Scatter plot", self.PLOT_SCATTER )
self.updateAxesCombos()
# populate both axis combos with field names
fields = self.vl.dataProvider().fields()
for index in range(fields.count()):
self.xAxisCombo.addItem( fields[index].name(), index )
self.yAxisCombo.addItem( fields[index].name(), index )
# connect actions to the widgets
self.connect(self.drawPolygonBtn, SIGNAL("clicked()"), self.drawPolygon)
self.connect(self.clearPolygonBtn, SIGNAL("clicked()"), self.clearPolygon)
self.connect(self.plotBtn, SIGNAL("clicked()"), self.createPlot)
self.connect(self.plotTypeCombo, SIGNAL("currentIndexChanged(int)"), self.updateAxesCombos)
def storePrevMapTool(self):
prevMapTool = self.canvas.mapTool()
if prevMapTool not in (self.polygonDrawer,):
self._prevMapTool = prevMapTool
def restorePrevMapTool(self):
self.polygonDrawer.stopCapture()
if self._prevMapTool:
self.canvas.setMapTool(self._prevMapTool)
def showEvent(self, event):
self.showRubberBands(True)
QWidget.showEvent(self, event)
def hideEvent(self, event):
self.showRubberBands(False)
self.restorePrevMapTool()
QWidget.hideEvent(self, event)
def deleteLater(self, *args):
#print "deleting", self
self.clearPolygon()
# restore the previous maptool
self.restorePrevMapTool()
# delete the polygon drawer maptool
self.polygonDrawer.deleteLater()
self.polygonDrawer = None
QWidget.deleteLater(self, *args)
def _filterForKey(self, key):
if key == None: return None
if 'magnitude' in key: return self.magnitudeRangeFilter
if 'depth' in key: return self.depthRangeFilter
if 'date' in key: return self.dateRangeFilter
return None
def _hasPlotYField(self, plotType):
return plotType not in (self.PLOT_HIST, self.PLOT_HIST_LOG)
def _getPlotType(self):
return self.plotTypeCombo.itemData( self.plotTypeCombo.currentIndex() )
def updateAxesCombos(self):
self.yAxisCombo.setEnabled( self._hasPlotYField( self._getPlotType() ) )
def setupFilters(self):
# setup the magnitude, depth and range filters
self.magnitudeRangeFilter.setEnabled(False)
self.magnitudeRangeFilter.setOrientation( Qt.Horizontal )
self.magnitudeRangeFilter.setDecimals( 1 )
self.magnitudeRangeFilter.setMinimum( 0 )
self.magnitudeRangeFilter.setMaximum( 10 )
self.depthRangeFilter.setEnabled(False)
self.depthRangeFilter.setOrientation( Qt.Horizontal )
self.depthRangeFilter.setMinimum( 0 )
self.depthRangeFilter.setMaximum( 1000 )
self.dateRangeFilter.setEnabled(False)
self.dateRangeFilter.setOrientation( Qt.Horizontal )
# take min/max values index from the data provider,
# buf first let's reset the subset string to get real min/max
pr = self.vl.dataProvider()
pr.setSubsetString("")
key2index = Utils.key2indexFieldMap( pr.fields() )
for key, index in key2index.iteritems():
filterWdg = self._filterForKey( key )
if not filterWdg:
continue
minVal = pr.minimumValue( index )
maxVal = pr.maximumValue( index )
if minVal != None and maxVal != None:
# check for empty strings (the provider could be unable to retrieve values)
if minVal is None or maxVal is None:
# let's search the real min/max values
request = QgsFeatureRequest()
request.setSubsetOfAttributes([index])
for f in self.vl.getFeatures( request ):
fval = Utils.valueFromQVariant( f.attributes()[index] )
if not minVal or fval < minVal:
minVal = fval
if not maxVal or fval > maxVal:
maxVal = fval
else:
minVal = Utils.valueFromQVariant( minVal )
maxVal = Utils.valueFromQVariant( maxVal )
# setup filter min/max range
try:
filterWdg.setMinimum( minVal )
filterWdg.setLowValue( minVal )
filterWdg.setMaximum( maxVal )
filterWdg.setHighValue( maxVal )
except Exception, ex:
traceback.print_exc()
#raise
# unable to set min/max, skip the filter
continue
filterWdg.setEnabled(True)
self.connect(filterWdg, SIGNAL("changeFinished"), self.updateMap)
# filter default ranges
self.magnitudeRangeFilter.setLowValue( 3.5 )
self.magnitudeRangeFilter.setHighValue( 10 )
self.depthRangeFilter.setLowValue( 0 )
self.depthRangeFilter.setHighValue( 250 )
self.updateMap()
def updateMap(self, *args):
pr = self.vl.dataProvider()
key2index = Utils.key2indexFieldMap( pr.fields() )
# convert filters to subset string
subsets = []
for key, index in key2index.iteritems():
filterWdg = self._filterForKey( key )
if not filterWdg or not filterWdg.isEnabled():
continue
name = pr.fields()[index].name()
# define a new subset string when the low value is greather then the minimum value
if filterWdg.lowValue() > filterWdg.minimum():
minVal = filterWdg.lowValue()
if type(minVal) in (QDate, QDateTime):
dataFormat = "yyyy/MM/dd" if self.vl.providerType() == 'ogr' else "yyyy-MM-dd"
minVal = u"'%s'" % minVal.toString(dataFormat)
else:
minVal = str(minVal)
subsets.append( u"\"%s\" >= %s" % (re.sub('"', '""', name), minVal) )
# define a new subset string when the high value is less then the maximum value
if filterWdg.highValue() < filterWdg.maximum():
maxVal = filterWdg.highValue()
if type(maxVal) in (QDate, QDateTime):
dataFormat = "yyyy/MM/dd" if self.vl.providerType() == 'ogr' else "yyyy-MM-dd"
maxVal = u"'%s'" % maxVal.toString(dataFormat)
else:
maxVal = str(maxVal)
subsets.append( u"\"%s\" <= %s" % (re.sub('"', '""', name), maxVal) )
# set the subset string, then update the layer
if self.vl.setSubsetString( u" AND ".join(subsets) ):
self.vl.triggerRepaint()
def showRubberBands(self, show=True):
""" show/hide all the rubberbands """
if self.polygonDrawer.isEmittingPoints:
self.polygonDrawer.reset()
else:
self.polygonDrawer.rubberBand.show() if show else self.polygonDrawer.rubberBand.hide()
def drawPolygon(self):
# store the previous maptool
self.storePrevMapTool()
# set the polygon drawer as current maptool
self.polygonDrawer.startCapture()
def clearPolygon(self):
# remove the displayed polygon
self.polygonDrawer.reset()
def polygonCreated(self, polygon):
# restore the previous maptool
self.restorePrevMapTool()
def createPlot(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
try:
dlg = self._createPlotDlg()
finally:
QApplication.restoreOverrideCursor()
if dlg:
dlg.show()
dlg.exec_()
dlg.deleteLater()
def _createPlotDlg(self):
plotType = self._getPlotType()
hasYField = self._hasPlotYField( plotType )
# spatial filter
spatialFilter = self.polygonDrawer.geometry()
if not spatialFilter:
extent = self.vl.extent()
else:
# convert the spatial filter to the layer CRS
toLayerCrsTransform = QgsCoordinateTransform( self.canvas.mapRenderer().destinationCrs(), self.vl.crs() )
ret = spatialFilter.transform( toLayerCrsTransform )
if ret != 0:
QMessageBox.warning(self, "Invalid area", "Unable to transform the selected area to the layer CRS.")
return
extent = spatialFilter.boundingBox()
# get the indexes of fields selected in the combos
xIndex = int(self.xAxisCombo.itemData( self.xAxisCombo.currentIndex() ))
yIndex = int(self.yAxisCombo.itemData( self.yAxisCombo.currentIndex() ))
pr = self.vl.dataProvider()
index2key = Utils.index2keyFieldMap( pr.fields() )
indexes = []
for fld in pr.fields().toList():
index = pr.fields().indexFromName(fld.name())
if index == xIndex:
indexes.append( xIndex )
continue
if index == yIndex and hasYField:
indexes.append( yIndex )
continue
filterWdg = self._filterForKey( Utils.fieldName2key( fld.name() ) )
if filterWdg and filterWdg.isEnabled():
indexes.append( index )
# the following lists will contain x and y values
x, y = ([], [])
# fetch and loop through the features
request = QgsFeatureRequest()
request.setFilterRect( extent )
request.setSubsetOfAttributes( indexes )
for f in self.vl.getFeatures( request ):
# filter features by spatial filter
if spatialFilter and not spatialFilter.contains( f.geometry() ):
continue
# filter features by attribute values
attrs = f.attributes()
ok = True
for index, val in enumerate(attrs):
if not index2key.has_key( index ):
continue # unused field
filterWdg = self._filterForKey( index2key[ index ] )
if not filterWdg:
continue # the field has no associated filter widget
if filterWdg.isActive() and not filterWdg.checkValue( val ):
ok = False
break
if not ok: # the feature was filtered out
continue
# append feature values to the lists
x.append( attrs[ xIndex ] )
if hasYField: y.append( attrs[ yIndex ] )
if len(x) <= 0:
QMessageBox.information(self, "Plot", "No features in the result")
return
from plot_wdg import HistogramPlotDlg, ScatterPlotDlg
if plotType == self.PLOT_HIST:
dlg = HistogramPlotDlg(None, [x], [self.xAxisCombo.currentText(), "Frequency"], self.titleEdit.text())
elif plotType == self.PLOT_HIST_LOG:
dlg = HistogramPlotDlg(None, [x], [self.xAxisCombo.currentText(), "Frequency"], self.titleEdit.text(), {'yscale':'log'})
elif plotType == self.PLOT_SCATTER:
dlg = ScatterPlotDlg(None, [x, y], [self.xAxisCombo.currentText(), self.yAxisCombo.currentText()], self.titleEdit.text())
else:
return
return dlg