4
4
***************************************************************************
5
5
ZonalStatistics.py
6
6
---------------------
7
- Date : August 2013
8
- Copyright : (C) 2013 by Alexander Bruy
7
+ Date : September 2016
8
+ Copyright : (C) 2016 by Alexander Bruy
9
9
Email : alexander dot bruy at gmail dot com
10
10
***************************************************************************
11
11
* *
16
16
* *
17
17
***************************************************************************
18
18
"""
19
- from builtins import str
20
19
21
20
__author__ = 'Alexander Bruy'
22
- __date__ = 'August 2013 '
23
- __copyright__ = '(C) 2013 , Alexander Bruy'
21
+ __date__ = 'September 2016 '
22
+ __copyright__ = '(C) 2016 , Alexander Bruy'
24
23
25
24
# This will get replaced with a git SHA1 when you do a git archive
26
25
27
26
__revision__ = '$Format:%H$'
28
27
29
- import numpy
28
+ import os
30
29
31
- try :
32
- from scipy .stats .mstats import mode
33
- hasSciPy = True
34
- except :
35
- hasSciPy = False
30
+ from qgis .PyQt .QtGui import QIcon
36
31
37
- from osgeo import gdal , ogr , osr
38
- from qgis .core import (QgsApplication ,
39
- QgsFeatureSink ,
40
- QgsRectangle ,
41
- QgsGeometry ,
42
- QgsFeature ,
43
- QgsProcessingUtils )
32
+ from qgis .analysis import QgsZonalStatistics
33
+ from qgis .core import (QgsFeatureSink ,
34
+ QgsProcessingUtils ,
35
+ QgsProcessingParameterDefinition ,
36
+ QgsProcessingParameterVectorLayer ,
37
+ QgsProcessingParameterRasterLayer ,
38
+ QgsProcessingParameterString ,
39
+ QgsProcessingParameterNumber ,
40
+ QgsProcessingParameterEnum ,
41
+ QgsProcessingOutputVectorLayer )
44
42
45
43
from processing .algs .qgis .QgisAlgorithm import QgisAlgorithm
46
- from processing .core .parameters import ParameterVector
47
- from processing .core .parameters import ParameterRaster
48
- from processing .core .parameters import ParameterString
49
- from processing .core .parameters import ParameterNumber
50
- from processing .core .parameters import ParameterBoolean
51
- from processing .core .outputs import OutputVector
52
- from processing .tools .raster import mapToPixel
53
- from processing .tools import dataobjects , vector
44
+
45
+ pluginPath = os .path .split (os .path .split (os .path .dirname (__file__ ))[0 ])[0 ]
54
46
55
47
56
48
class ZonalStatistics (QgisAlgorithm ):
@@ -59,26 +51,48 @@ class ZonalStatistics(QgisAlgorithm):
59
51
RASTER_BAND = 'RASTER_BAND'
60
52
INPUT_VECTOR = 'INPUT_VECTOR'
61
53
COLUMN_PREFIX = 'COLUMN_PREFIX'
62
- GLOBAL_EXTENT = 'GLOBAL_EXTENT'
63
- OUTPUT_LAYER = 'OUTPUT_LAYER'
54
+ STATISTICS = 'STATS'
55
+
56
+ def icon (self ):
57
+ return QIcon (os .path .join (pluginPath , 'images' , 'zonalstats.png' ))
64
58
65
59
def group (self ):
66
60
return self .tr ('Raster tools' )
67
61
68
62
def __init__ (self ):
69
63
super ().__init__ ()
70
- self .addParameter (ParameterRaster (self .INPUT_RASTER ,
71
- self .tr ('Raster layer' )))
72
- self .addParameter (ParameterNumber (self .RASTER_BAND ,
73
- self .tr ('Raster band' ), 1 , 999 , 1 ))
74
- self .addParameter (ParameterVector (self .INPUT_VECTOR ,
75
- self .tr ('Vector layer containing zones' ),
76
- [dataobjects .TYPE_VECTOR_POLYGON ]))
77
- self .addParameter (ParameterString (self .COLUMN_PREFIX ,
78
- self .tr ('Output column prefix' ), '_' ))
79
- self .addParameter (ParameterBoolean (self .GLOBAL_EXTENT ,
80
- self .tr ('Load whole raster in memory' )))
81
- self .addOutput (OutputVector (self .OUTPUT_LAYER , self .tr ('Zonal statistics' ), datatype = [dataobjects .TYPE_VECTOR_POLYGON ]))
64
+ self .STATS = {self .tr ('Count' ): QgsZonalStatistics .Count ,
65
+ self .tr ('Sum' ): QgsZonalStatistics .Sum ,
66
+ self .tr ('Mean' ): QgsZonalStatistics .Mean ,
67
+ self .tr ('Median' ): QgsZonalStatistics .Median ,
68
+ self .tr ('Std. dev.' ): QgsZonalStatistics .StDev ,
69
+ self .tr ('Min' ): QgsZonalStatistics .Min ,
70
+ self .tr ('Max' ): QgsZonalStatistics .Max ,
71
+ self .tr ('Range' ): QgsZonalStatistics .Range ,
72
+ self .tr ('Minority' ): QgsZonalStatistics .Minority ,
73
+ self .tr ('Majority (mode)' ): QgsZonalStatistics .Majority ,
74
+ self .tr ('Variety' ): QgsZonalStatistics .Variety ,
75
+ self .tr ('Variance' ): QgsZonalStatistics .Variance ,
76
+ self .tr ('All' ): QgsZonalStatistics .All
77
+ }
78
+
79
+ self .addParameter (QgsProcessingParameterRasterLayer (self .INPUT_RASTER ,
80
+ self .tr ('Raster layer' )))
81
+ self .addParameter (QgsProcessingParameterNumber (self .RASTER_BAND ,
82
+ self .tr ('Raster band' ),
83
+ minValue = 1 , maxValue = 999 , defaultValue = 1 ))
84
+ self .addParameter (QgsProcessingParameterVectorLayer (self .INPUT_VECTOR ,
85
+ self .tr ('Vector layer containing zones' ),
86
+ [QgsProcessingParameterDefinition .TypeVectorPolygon ]))
87
+ self .addParameter (QgsProcessingParameterString (self .COLUMN_PREFIX ,
88
+ self .tr ('Output column prefix' ), '_' ))
89
+ self .addParameter (QgsProcessingParameterEnum (self .STATISTICS ,
90
+ self .tr ('Statistics to calculate' ),
91
+ list (self .STATS .keys ()),
92
+ allowMultiple = True ))
93
+ self .addOutput (QgsProcessingOutputVectorLayer (self .INPUT_VECTOR ,
94
+ self .tr ('Zonal statistics' ),
95
+ QgsProcessingParameterDefinition .TypeVectorPolygon ))
82
96
83
97
def name (self ):
84
98
return 'zonalstatistics'
@@ -87,191 +101,23 @@ def displayName(self):
87
101
return self .tr ('Zonal Statistics' )
88
102
89
103
def processAlgorithm (self , parameters , context , feedback ):
90
- """ Based on code by Matthew Perry
91
- https://gist.github.com/perrygeo/5667173
92
- :param parameters:
93
- :param context:
94
- """
95
-
96
- layer = QgsProcessingUtils .mapLayerFromString (self .getParameterValue (self .INPUT_VECTOR ), context )
97
-
98
- rasterPath = str (self .getParameterValue (self .INPUT_RASTER ))
99
- bandNumber = self .getParameterValue (self .RASTER_BAND )
100
- columnPrefix = self .getParameterValue (self .COLUMN_PREFIX )
101
- useGlobalExtent = self .getParameterValue (self .GLOBAL_EXTENT )
102
-
103
- rasterDS = gdal .Open (rasterPath , gdal .GA_ReadOnly )
104
- geoTransform = rasterDS .GetGeoTransform ()
105
- rasterBand = rasterDS .GetRasterBand (bandNumber )
106
- noData = rasterBand .GetNoDataValue ()
107
-
108
- cellXSize = abs (geoTransform [1 ])
109
- cellYSize = abs (geoTransform [5 ])
110
- rasterXSize = rasterDS .RasterXSize
111
- rasterYSize = rasterDS .RasterYSize
112
-
113
- rasterBBox = QgsRectangle (geoTransform [0 ],
114
- geoTransform [3 ] - cellYSize * rasterYSize ,
115
- geoTransform [0 ] + cellXSize * rasterXSize ,
116
- geoTransform [3 ])
117
-
118
- rasterGeom = QgsGeometry .fromRect (rasterBBox )
119
-
120
- crs = osr .SpatialReference ()
121
- crs .ImportFromProj4 (str (layer .crs ().toProj4 ()))
122
-
123
- if useGlobalExtent :
124
- xMin = rasterBBox .xMinimum ()
125
- xMax = rasterBBox .xMaximum ()
126
- yMin = rasterBBox .yMinimum ()
127
- yMax = rasterBBox .yMaximum ()
128
-
129
- (startColumn , startRow ) = mapToPixel (xMin , yMax , geoTransform )
130
- (endColumn , endRow ) = mapToPixel (xMax , yMin , geoTransform )
131
-
132
- width = endColumn - startColumn
133
- height = endRow - startRow
134
-
135
- srcOffset = (startColumn , startRow , width , height )
136
- srcArray = rasterBand .ReadAsArray (* srcOffset )
137
- srcArray = srcArray * rasterBand .GetScale () + rasterBand .GetOffset ()
138
-
139
- newGeoTransform = (
140
- geoTransform [0 ] + srcOffset [0 ] * geoTransform [1 ],
141
- geoTransform [1 ],
142
- 0.0 ,
143
- geoTransform [3 ] + srcOffset [1 ] * geoTransform [5 ],
144
- 0.0 ,
145
- geoTransform [5 ],
146
- )
147
-
148
- memVectorDriver = ogr .GetDriverByName ('Memory' )
149
- memRasterDriver = gdal .GetDriverByName ('MEM' )
150
-
151
- fields = layer .fields ()
152
- (idxMin , fields ) = vector .findOrCreateField (layer , fields ,
153
- columnPrefix + 'min' , 21 , 6 )
154
- (idxMax , fields ) = vector .findOrCreateField (layer , fields ,
155
- columnPrefix + 'max' , 21 , 6 )
156
- (idxSum , fields ) = vector .findOrCreateField (layer , fields ,
157
- columnPrefix + 'sum' , 21 , 6 )
158
- (idxCount , fields ) = vector .findOrCreateField (layer , fields ,
159
- columnPrefix + 'count' , 21 , 6 )
160
- (idxMean , fields ) = vector .findOrCreateField (layer , fields ,
161
- columnPrefix + 'mean' , 21 , 6 )
162
- (idxStd , fields ) = vector .findOrCreateField (layer , fields ,
163
- columnPrefix + 'std' , 21 , 6 )
164
- (idxUnique , fields ) = vector .findOrCreateField (layer , fields ,
165
- columnPrefix + 'unique' , 21 , 6 )
166
- (idxRange , fields ) = vector .findOrCreateField (layer , fields ,
167
- columnPrefix + 'range' , 21 , 6 )
168
- (idxVar , fields ) = vector .findOrCreateField (layer , fields ,
169
- columnPrefix + 'var' , 21 , 6 )
170
- (idxMedian , fields ) = vector .findOrCreateField (layer , fields ,
171
- columnPrefix + 'median' , 21 , 6 )
172
- if hasSciPy :
173
- (idxMode , fields ) = vector .findOrCreateField (layer , fields ,
174
- columnPrefix + 'mode' , 21 , 6 )
175
-
176
- writer = self .getOutputFromName (self .OUTPUT_LAYER ).getVectorWriter (fields , layer .wkbType (),
177
- layer .crs (), context )
178
-
179
- outFeat = QgsFeature ()
180
-
181
- outFeat .initAttributes (len (fields ))
182
- outFeat .setFields (fields )
183
-
184
- features = QgsProcessingUtils .getFeatures (layer , context )
185
- total = 100.0 / layer .featureCount () if layer .featureCount () else 0
186
- for current , f in enumerate (features ):
187
- geom = f .geometry ()
188
-
189
- intersectedGeom = rasterGeom .intersection (geom )
190
- ogrGeom = ogr .CreateGeometryFromWkt (intersectedGeom .exportToWkt ())
191
-
192
- if not useGlobalExtent :
193
- bbox = intersectedGeom .boundingBox ()
194
-
195
- xMin = bbox .xMinimum ()
196
- xMax = bbox .xMaximum ()
197
- yMin = bbox .yMinimum ()
198
- yMax = bbox .yMaximum ()
199
-
200
- (startColumn , startRow ) = mapToPixel (xMin , yMax , geoTransform )
201
- (endColumn , endRow ) = mapToPixel (xMax , yMin , geoTransform )
202
-
203
- width = endColumn - startColumn
204
- height = endRow - startRow
205
-
206
- if width == 0 or height == 0 :
207
- continue
208
-
209
- srcOffset = (startColumn , startRow , width , height )
210
- srcArray = rasterBand .ReadAsArray (* srcOffset )
211
- srcArray = srcArray * rasterBand .GetScale () + rasterBand .GetOffset ()
212
-
213
- newGeoTransform = (
214
- geoTransform [0 ] + srcOffset [0 ] * geoTransform [1 ],
215
- geoTransform [1 ],
216
- 0.0 ,
217
- geoTransform [3 ] + srcOffset [1 ] * geoTransform [5 ],
218
- 0.0 ,
219
- geoTransform [5 ],
220
- )
221
-
222
- # Create a temporary vector layer in memory
223
- memVDS = memVectorDriver .CreateDataSource ('out' )
224
- memLayer = memVDS .CreateLayer ('poly' , crs , ogr .wkbPolygon )
225
-
226
- ft = ogr .Feature (memLayer .GetLayerDefn ())
227
- ft .SetGeometry (ogrGeom )
228
- memLayer .CreateFeature (ft )
229
- ft .Destroy ()
230
-
231
- # Rasterize it
232
- rasterizedDS = memRasterDriver .Create ('' , srcOffset [2 ],
233
- srcOffset [3 ], 1 , gdal .GDT_Byte )
234
- rasterizedDS .SetGeoTransform (newGeoTransform )
235
- gdal .RasterizeLayer (rasterizedDS , [1 ], memLayer , burn_values = [1 ])
236
- rasterizedArray = rasterizedDS .ReadAsArray ()
237
-
238
- srcArray = numpy .nan_to_num (srcArray )
239
- masked = numpy .ma .MaskedArray (srcArray ,
240
- mask = numpy .logical_or (srcArray == noData ,
241
- numpy .logical_not (rasterizedArray )))
242
-
243
- outFeat .setGeometry (geom )
244
-
245
- attrs = f .attributes ()
246
- v = float (masked .min ())
247
- attrs .insert (idxMin , None if numpy .isnan (v ) else v )
248
- v = float (masked .max ())
249
- attrs .insert (idxMax , None if numpy .isnan (v ) else v )
250
- v = float (masked .sum ())
251
- attrs .insert (idxSum , None if numpy .isnan (v ) else v )
252
- attrs .insert (idxCount , int (masked .count ()))
253
- v = float (masked .mean ())
254
- attrs .insert (idxMean , None if numpy .isnan (v ) else v )
255
- v = float (masked .std ())
256
- attrs .insert (idxStd , None if numpy .isnan (v ) else v )
257
- attrs .insert (idxUnique , numpy .unique (masked .compressed ()).size )
258
- v = float (masked .max ()) - float (masked .min ())
259
- attrs .insert (idxRange , None if numpy .isnan (v ) else v )
260
- v = float (masked .var ())
261
- attrs .insert (idxVar , None if numpy .isnan (v ) else v )
262
- v = float (numpy .ma .median (masked ))
263
- attrs .insert (idxMedian , None if numpy .isnan (v ) else v )
264
- if hasSciPy :
265
- attrs .insert (idxMode , float (mode (masked , axis = None )[0 ][0 ]))
266
-
267
- outFeat .setAttributes (attrs )
268
- writer .addFeature (outFeat , QgsFeatureSink .FastInsert )
269
-
270
- memVDS = None
271
- rasterizedDS = None
272
-
273
- feedback .setProgress (int (current * total ))
274
-
275
- rasterDS = None
276
-
277
- del writer
104
+ bandNumber = self .parameterAsInt (parameters , self .RASTER_BAND , context )
105
+ columnPrefix = self .parameterAsString (parameters , self .COLUMN_PREFIX , context )
106
+ st = self .parameterAsEnums (parameters , self .STATISTICS , context )
107
+
108
+ vectorLayer = self .parameterAsVectorLayer (parameters , self .INPUT_VECTOR , context )
109
+ rasterLayer = self .parameterAsRasterLayer (parameters , self .INPUT_RASTER , context )
110
+
111
+ keys = list (self .STATS .keys ())
112
+ selectedStats = 0
113
+ for i in st :
114
+ selectedStats |= self .STATS [keys [i ]]
115
+
116
+ zs = QgsZonalStatistics (vectorLayer ,
117
+ rasterLayer ,
118
+ columnPrefix ,
119
+ bandNumber ,
120
+ selectedStats )
121
+ zs .calculateStatistics (feedback )
122
+
123
+ return {self .INPUT_VECTOR : vectorLayer }
0 commit comments