Skip to content

Commit 8481ebc

Browse files
committed
[processing] add zonal statistics algorithm
1 parent 72536f6 commit 8481ebc

File tree

3 files changed

+327
-1
lines changed

3 files changed

+327
-1
lines changed

python/plugins/processing/algs/QGISAlgorithmProvider.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
from processing.algs.PointsLayerFromTable import PointsLayerFromTable
8888

8989
from processing.algs.PointsDisplacement import PointsDisplacement
90+
from sextante.algs.ZonalStatistics import ZonalStatistics
9091

9192
#from processing.algs.VectorLayerHistogram import VectorLayerHistogram
9293
#from processing.algs.VectorLayerScatterplot import VectorLayerScatterplot
@@ -141,7 +142,8 @@ def __init__(self):
141142
#VectorLayerHistogram(), VectorLayerScatterplot(), RasterLayerHistogram(),
142143
#MeanAndStdDevPlot(), BarPlot(), PolarPlot()
143144
# ------ vector ------
144-
PointsDisplacement()
145+
PointsDisplacement(),
146+
ZonalStatistics()
145147
]
146148

147149
def initializeSettings(self):
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
QGISUtils.py
6+
---------------------
7+
Date : August 2013
8+
Copyright : (C) 2013 by Alexander Bruy
9+
Email : alexander dot bruy at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Alexander Bruy'
21+
__date__ = 'August 2013'
22+
__copyright__ = '(C) 2013, Alexander Bruy'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
import math
27+
28+
from PyQt4.QtCore import *
29+
30+
from qgis.core import *
31+
32+
33+
def mapToPixel(mX, mY, geoTransform):
34+
'''Convert map coordinates to pixel coordinates.
35+
36+
@param mX Input map X coordinate (double)
37+
@param mY Input map Y coordinate (double)
38+
@param geoTransform Input geotransform (six doubles)
39+
@return pX, pY Output coordinates (two doubles)
40+
'''
41+
if geoTransform[2] + geoTransform[4] == 0:
42+
pX = (mX - geoTransform[0]) / geoTransform[1]
43+
pY = (mY - geoTransform[3]) / geoTransform[5]
44+
else:
45+
pX, pY = applyGeoTransform(mX, mY, invertGeoTransform(geoTransform))
46+
return int(pX), int(pY)
47+
48+
def pixelToMap(pX, pY, geoTransform):
49+
'''Convert pixel coordinates to map coordinates.
50+
51+
@param pX Input pixel X coordinate (double)
52+
@param pY Input pixel Y coordinate (double)
53+
@param geoTransform Input geotransform (six doubles)
54+
@return mX, mY Output coordinates (two doubles)
55+
'''
56+
mX, mY = applyGeoTransform(pX + 0.5, pY + 0.5, geoTransform)
57+
return mX, mY
58+
59+
def applyGeoTransform(inX, inY, geoTransform):
60+
'''Apply a geotransform to coordinates.
61+
62+
@param inX Input coordinate (double)
63+
@param inY Input coordinate (double)
64+
@param geoTransform Input geotransform (six doubles)
65+
@return outX, outY Output coordinates (two doubles)
66+
'''
67+
outX = geoTransform[0] + inX * geoTransform[1] + inY * geoTransform[2]
68+
outY = geoTransform[3] + inX * geoTransform[4] + inY * geoTransform[5]
69+
return outX, outY
70+
71+
def invertGeoTransform(geoTransform):
72+
'''Invert standard 3x2 set of geotransform coefficients.
73+
74+
@param geoTransform Input GeoTransform (six doubles - unaltered)
75+
@return outGeoTransform Output GeoTransform (six doubles - updated)
76+
on success, None if the equation is uninvertable
77+
'''
78+
# we assume a 3rd row that is [1 0 0]
79+
# compute determinate
80+
det = geoTransform[1] * geoTransform[5] - geoTransform[2] * geoTransform[4]
81+
82+
if abs(det) < 0.000000000000001:
83+
return
84+
85+
invDet = 1.0 / det
86+
87+
# compute adjoint and divide by determinate
88+
outGeoTransform = [0, 0, 0, 0, 0, 0]
89+
outGeoTransform[1] = geoTransform[5] * invDet
90+
outGeoTransform[4] = -geoTransform[4] * invDet
91+
92+
outGeoTransform[2] = -geoTransform[2] * invDet
93+
outGeoTransfrom[5] = geoTransform[1] * invDet
94+
95+
outGeoTransform[0] = (geoTransform[2] * geoTransform[3] - geoTransform[0] * geoTransform[5]) * invDet
96+
outGeoTransform[3] = (-geoTransform[1] * geoTransform[3] + geoTransform[0] * geoTransform[4]) * invDet
97+
98+
return outGeoTransform
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ZonalStatistics.py
6+
---------------------
7+
Date : August 2013
8+
Copyright : (C) 2013 by Alexander Bruy
9+
Email : alexander dot bruy at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Alexander Bruy'
21+
__date__ = 'August 2013'
22+
__copyright__ = '(C) 2013, Alexander Bruy'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
from PyQt4.QtCore import *
27+
28+
import numpy
29+
from osgeo import gdal, ogr, osr
30+
31+
from qgis.core import *
32+
33+
from sextante.core.GeoAlgorithm import GeoAlgorithm
34+
from sextante.core.QGisLayers import QGisLayers
35+
36+
from sextante.parameters.ParameterVector import ParameterVector
37+
from sextante.parameters.ParameterRaster import ParameterRaster
38+
from sextante.parameters.ParameterString import ParameterString
39+
from sextante.parameters.ParameterNumber import ParameterNumber
40+
from sextante.parameters.ParameterBoolean import ParameterBoolean
41+
42+
from sextante.outputs.OutputVector import OutputVector
43+
44+
from sextante.algs.ftools import FToolsUtils as ftools_utils
45+
from sextante.algs import QGISUtils as utils
46+
47+
class ZonalStatistics(GeoAlgorithm):
48+
49+
INPUT_RASTER = "INPUT_RASTER"
50+
RASTER_BAND = "RASTER_BAND"
51+
INPUT_VECTOR = "INPUT_VECTOR"
52+
COLUMN_PREFIX = "COLUMN_PREFIX"
53+
GLOBAL_EXTENT = "GLOBAL_EXTENT"
54+
OUTPUT_LAYER = "OUTPUT_LAYER"
55+
56+
def defineCharacteristics(self):
57+
self.name = "Zonal Statistics"
58+
self.group = "Raster tools"
59+
60+
self.addParameter(ParameterRaster(self.INPUT_RASTER, "Raster layer"))
61+
self.addParameter(ParameterNumber(self.RASTER_BAND, "Raster band", 1, 999, 1))
62+
self.addParameter(ParameterVector(self.INPUT_VECTOR, "Vector layer containing zones", [ParameterVector.VECTOR_TYPE_POLYGON]))
63+
self.addParameter(ParameterString(self.COLUMN_PREFIX, "Output column prefix", "_"))
64+
self.addParameter(ParameterBoolean(self.GLOBAL_EXTENT, "Load whole raster in memory"))
65+
self.addOutput(OutputVector(self.OUTPUT_LAYER, "Output layer"))
66+
67+
def processAlgorithm(self, progress):
68+
layer = QGisLayers.getObjectFromUri(self.getParameterValue(self.INPUT_VECTOR))
69+
70+
rasterPath = unicode(self.getParameterValue(self.INPUT_RASTER))
71+
bandNumber = self.getParameterValue(self.RASTER_BAND)
72+
columnPrefix = self.getParameterValue(self.COLUMN_PREFIX)
73+
useGlobalExtent = self.getParameterValue(self.GLOBAL_EXTENT)
74+
75+
rasterDS = gdal.Open(rasterPath, gdal.GA_ReadOnly)
76+
geoTransform = rasterDS.GetGeoTransform()
77+
rasterBand = rasterDS.GetRasterBand(bandNumber)
78+
noData = rasterBand.GetNoDataValue()
79+
80+
cellXSize = abs(geoTransform[1])
81+
cellYSize = abs(geoTransform[5])
82+
rasterXSize = rasterDS.RasterXSize
83+
rasterYSize = rasterDS.RasterYSize
84+
85+
rasterBBox = QgsRectangle(geoTransform[0],
86+
geoTransform[3] - cellYSize * rasterYSize,
87+
geoTransform[0] + cellXSize * rasterXSize,
88+
geoTransform[3]
89+
)
90+
91+
rasterGeom = QgsGeometry.fromRect(rasterBBox)
92+
93+
crs = osr.SpatialReference()
94+
crs.ImportFromProj4(str(layer.crs().toProj4()))
95+
96+
if useGlobalExtent:
97+
xMin = rasterBBox.xMinimum()
98+
xMax = rasterBBox.xMaximum()
99+
yMin = rasterBBox.yMinimum()
100+
yMax = rasterBBox.yMaximum()
101+
102+
startColumn, startRow = utils.mapToPixel(xMin, yMax, geoTransform)
103+
endColumn, endRow = utils.mapToPixel(xMax, yMin, geoTransform)
104+
105+
width = endColumn - startColumn
106+
height = endRow - startRow
107+
108+
srcOffset = (startColumn, startRow, width, height)
109+
srcArray = rasterBand.ReadAsArray(*srcOffset)
110+
111+
newGeoTransform = (geoTransform[0] + srcOffset[0] * geoTransform[1],
112+
geoTransform[1],
113+
0.0,
114+
geoTransform[3] + srcOffset[1] * geoTransform[5],
115+
0.0,
116+
geoTransform[5]
117+
)
118+
119+
memVectorDriver = ogr.GetDriverByName("Memory")
120+
memRasterDriver = gdal.GetDriverByName("MEM")
121+
122+
fields = layer.pendingFields()
123+
idxMin, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "min", 21, 6)
124+
idxMax, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "max", 21, 6)
125+
idxSum, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "sum", 21, 6)
126+
idxCount, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "count", 21, 6)
127+
idxMean, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "mean", 21, 6)
128+
idxStd, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "std", 21, 6)
129+
idxUnique, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "unique", 21, 6)
130+
idxRange, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "range", 21, 6)
131+
idxCV, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "cv", 21, 6)
132+
#idxMedian, fields = ftools_utils.findOrCreateField(layer, fields, columnPrefix + "median", 21, 6)
133+
134+
writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(fields.toList(),
135+
layer.dataProvider().geometryType(), layer.crs())
136+
137+
outFeat = QgsFeature()
138+
139+
outFeat.initAttributes(len(fields))
140+
outFeat.setFields(fields)
141+
142+
current = 0
143+
features = QGisLayers.features(layer)
144+
total = 100.0 / len(features)
145+
for f in features:
146+
geom = f.geometry()
147+
148+
intersectedGeom = rasterGeom.intersection(geom)
149+
ogrGeom = ogr.CreateGeometryFromWkt(intersectedGeom.exportToWkt())
150+
151+
if not useGlobalExtent:
152+
bbox = intersectedGeom.boundingBox()
153+
154+
xMin = bbox.xMinimum()
155+
xMax = bbox.xMaximum()
156+
yMin = bbox.yMinimum()
157+
yMax = bbox.yMaximum()
158+
159+
startColumn, startRow = utils.mapToPixel(xMin, yMax, geoTransform)
160+
endColumn, endRow = utils.mapToPixel(xMax, yMin, geoTransform)
161+
162+
width = endColumn - startColumn
163+
height = endRow - startRow
164+
165+
if width == 0 or height == 0:
166+
continue
167+
168+
srcOffset = (startColumn, startRow, width, height)
169+
srcArray = rasterBand.ReadAsArray(*srcOffset)
170+
171+
newGeoTransform = (geoTransform[0] + srcOffset[0] * geoTransform[1],
172+
geoTransform[1],
173+
0.0,
174+
geoTransform[3] + srcOffset[1] * geoTransform[5],
175+
0.0,
176+
geoTransform[5]
177+
)
178+
179+
# Create a temporary vector layer in memory
180+
memVDS = memVectorDriver.CreateDataSource("out")
181+
memLayer = memVDS.CreateLayer('poly', crs, ogr.wkbPolygon)
182+
183+
ft = ogr.Feature(memLayer.GetLayerDefn())
184+
ft.SetGeometry(ogrGeom)
185+
memLayer.CreateFeature(ft)
186+
ft.Destroy()
187+
188+
# Rasterize it
189+
rasterizedDS = memRasterDriver.Create('', srcOffset[2], srcOffset[3], 1, gdal.GDT_Byte)
190+
rasterizedDS.SetGeoTransform(newGeoTransform)
191+
gdal.RasterizeLayer(rasterizedDS, [1], memLayer, burn_values=[1])
192+
rasterizedArray = rasterizedDS.ReadAsArray()
193+
194+
srcArray = numpy.nan_to_num(srcArray)
195+
masked = numpy.ma.MaskedArray(srcArray,
196+
mask=numpy.logical_or(srcArray == noData,
197+
numpy.logical_not(rasterizedArray)
198+
)
199+
)
200+
201+
outFeat.setGeometry(geom)
202+
203+
attrs = f.attributes()
204+
attrs.insert(idxMin, float(masked.min()))
205+
attrs.insert(idxMax, float(masked.max()))
206+
attrs.insert(idxSum, float(masked.sum()))
207+
attrs.insert(idxCount, int(masked.count()))
208+
attrs.insert(idxMean, float(masked.mean()))
209+
attrs.insert(idxStd, float(masked.std()))
210+
attrs.insert(idxUnique, numpy.unique(masked.compressed()).size)
211+
attrs.insert(idxRange, float(masked.max()) - float(masked.min()))
212+
attrs.insert(idxCV, float(masked.var()))
213+
#attrs.insert(idxMedian, float(masked.median()))
214+
215+
outFeat.setAttributes(attrs)
216+
writer.addFeature(outFeat)
217+
218+
memVDS = None
219+
rasterizedDS = None
220+
221+
current += 1
222+
progress.setPercentage(int(current * total))
223+
224+
rasterDS = None
225+
226+
del writer

0 commit comments

Comments
 (0)