2013-08-06 19:55:18 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
***************************************************************************
|
|
|
|
ZonalStatistics.py
|
|
|
|
---------------------
|
|
|
|
Date : August 2013
|
|
|
|
Copyright : (C) 2013 by Alexander Bruy
|
|
|
|
Email : alexander dot bruy at gmail dot com
|
|
|
|
***************************************************************************
|
|
|
|
* *
|
|
|
|
* 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. *
|
|
|
|
* *
|
|
|
|
***************************************************************************
|
|
|
|
"""
|
|
|
|
|
|
|
|
__author__ = 'Alexander Bruy'
|
|
|
|
__date__ = 'August 2013'
|
|
|
|
__copyright__ = '(C) 2013, Alexander Bruy'
|
2013-10-01 20:52:22 +03:00
|
|
|
|
2013-08-06 19:55:18 +03:00
|
|
|
# This will get replaced with a git SHA1 when you do a git archive
|
2013-10-01 20:52:22 +03:00
|
|
|
|
2013-08-06 19:55:18 +03:00
|
|
|
__revision__ = '$Format:%H$'
|
|
|
|
|
|
|
|
from PyQt4.QtCore import *
|
|
|
|
import numpy
|
|
|
|
from osgeo import gdal, ogr, osr
|
|
|
|
from qgis.core import *
|
2013-08-27 19:47:44 +03:00
|
|
|
from processing.core.GeoAlgorithm import GeoAlgorithm
|
|
|
|
from processing.parameters.ParameterVector import ParameterVector
|
|
|
|
from processing.parameters.ParameterRaster import ParameterRaster
|
|
|
|
from processing.parameters.ParameterString import ParameterString
|
|
|
|
from processing.parameters.ParameterNumber import ParameterNumber
|
|
|
|
from processing.parameters.ParameterBoolean import ParameterBoolean
|
|
|
|
from processing.outputs.OutputVector import OutputVector
|
2013-10-01 20:52:22 +03:00
|
|
|
from processing.tools.raster import mapToPixel
|
|
|
|
from processing.tools import dataobjects, vector
|
|
|
|
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
class ZonalStatistics(GeoAlgorithm):
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
INPUT_RASTER = 'INPUT_RASTER'
|
|
|
|
RASTER_BAND = 'RASTER_BAND'
|
|
|
|
INPUT_VECTOR = 'INPUT_VECTOR'
|
|
|
|
COLUMN_PREFIX = 'COLUMN_PREFIX'
|
|
|
|
GLOBAL_EXTENT = 'GLOBAL_EXTENT'
|
|
|
|
OUTPUT_LAYER = 'OUTPUT_LAYER'
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
def defineCharacteristics(self):
|
2013-10-01 20:52:22 +03:00
|
|
|
self.name = 'Zonal Statistics'
|
|
|
|
self.group = 'Raster tools'
|
|
|
|
|
|
|
|
self.addParameter(ParameterRaster(self.INPUT_RASTER, 'Raster layer'))
|
|
|
|
self.addParameter(ParameterNumber(self.RASTER_BAND, 'Raster band', 1,
|
|
|
|
999, 1))
|
|
|
|
self.addParameter(ParameterVector(self.INPUT_VECTOR,
|
|
|
|
'Vector layer containing zones',
|
|
|
|
[ParameterVector.VECTOR_TYPE_POLYGON]))
|
|
|
|
self.addParameter(ParameterString(self.COLUMN_PREFIX,
|
|
|
|
'Output column prefix', '_'))
|
|
|
|
self.addParameter(ParameterBoolean(self.GLOBAL_EXTENT,
|
|
|
|
'Load whole raster in memory'))
|
|
|
|
self.addOutput(OutputVector(self.OUTPUT_LAYER, 'Output layer'))
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
def processAlgorithm(self, progress):
|
2013-10-01 20:52:22 +03:00
|
|
|
layer = dataobjects.getObjectFromUri(
|
|
|
|
self.getParameterValue(self.INPUT_VECTOR))
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
rasterPath = unicode(self.getParameterValue(self.INPUT_RASTER))
|
|
|
|
bandNumber = self.getParameterValue(self.RASTER_BAND)
|
|
|
|
columnPrefix = self.getParameterValue(self.COLUMN_PREFIX)
|
|
|
|
useGlobalExtent = self.getParameterValue(self.GLOBAL_EXTENT)
|
|
|
|
|
|
|
|
rasterDS = gdal.Open(rasterPath, gdal.GA_ReadOnly)
|
|
|
|
geoTransform = rasterDS.GetGeoTransform()
|
|
|
|
rasterBand = rasterDS.GetRasterBand(bandNumber)
|
|
|
|
noData = rasterBand.GetNoDataValue()
|
|
|
|
|
|
|
|
cellXSize = abs(geoTransform[1])
|
|
|
|
cellYSize = abs(geoTransform[5])
|
|
|
|
rasterXSize = rasterDS.RasterXSize
|
|
|
|
rasterYSize = rasterDS.RasterYSize
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
rasterBBox = QgsRectangle(geoTransform[0], geoTransform[3] - cellYSize
|
|
|
|
* rasterYSize, geoTransform[0] + cellXSize
|
|
|
|
* rasterXSize, geoTransform[3])
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
rasterGeom = QgsGeometry.fromRect(rasterBBox)
|
|
|
|
|
|
|
|
crs = osr.SpatialReference()
|
|
|
|
crs.ImportFromProj4(str(layer.crs().toProj4()))
|
|
|
|
|
|
|
|
if useGlobalExtent:
|
|
|
|
xMin = rasterBBox.xMinimum()
|
|
|
|
xMax = rasterBBox.xMaximum()
|
|
|
|
yMin = rasterBBox.yMinimum()
|
|
|
|
yMax = rasterBBox.yMaximum()
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
(startColumn, startRow) = mapToPixel(xMin, yMax, geoTransform)
|
|
|
|
(endColumn, endRow) = mapToPixel(xMax, yMin, geoTransform)
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
width = endColumn - startColumn
|
|
|
|
height = endRow - startRow
|
|
|
|
|
|
|
|
srcOffset = (startColumn, startRow, width, height)
|
|
|
|
srcArray = rasterBand.ReadAsArray(*srcOffset)
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
newGeoTransform = (
|
|
|
|
geoTransform[0] + srcOffset[0] * geoTransform[1],
|
|
|
|
geoTransform[1],
|
|
|
|
0.0,
|
|
|
|
geoTransform[3] + srcOffset[1] * geoTransform[5],
|
|
|
|
0.0,
|
|
|
|
geoTransform[5],
|
|
|
|
)
|
2013-08-06 19:55:18 +03:00
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
memVectorDriver = ogr.GetDriverByName('Memory')
|
|
|
|
memRasterDriver = gdal.GetDriverByName('MEM')
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
fields = layer.pendingFields()
|
2013-10-01 20:52:22 +03:00
|
|
|
(idxMin, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'min', 21, 6)
|
|
|
|
(idxMax, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'max', 21, 6)
|
|
|
|
(idxSum, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'sum', 21, 6)
|
|
|
|
(idxCount, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'count', 21, 6)
|
|
|
|
(idxMean, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'mean', 21, 6)
|
|
|
|
(idxStd, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'std', 21, 6)
|
|
|
|
(idxUnique, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'unique', 21, 6)
|
|
|
|
(idxRange, fields) = vector.findOrCreateField(layer, fields,
|
|
|
|
columnPrefix + 'range', 21, 6)
|
|
|
|
(idxCV, fields) = vector.findOrCreateField(layer, fields, columnPrefix
|
|
|
|
+ 'cv', 21, 6)
|
|
|
|
|
|
|
|
# idxMedian, fields = ftools_utils.findOrCreateField(layer, fields,
|
|
|
|
# columnPrefix + "median", 21, 6)
|
|
|
|
|
|
|
|
writer = self.getOutputFromName(
|
|
|
|
self.OUTPUT_LAYER).getVectorWriter(fields.toList(),
|
|
|
|
layer.dataProvider().geometryType(), layer.crs())
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
outFeat = QgsFeature()
|
|
|
|
|
|
|
|
outFeat.initAttributes(len(fields))
|
|
|
|
outFeat.setFields(fields)
|
|
|
|
|
|
|
|
current = 0
|
2013-09-11 19:32:38 +02:00
|
|
|
features = vector.features(layer)
|
2013-08-06 19:55:18 +03:00
|
|
|
total = 100.0 / len(features)
|
|
|
|
for f in features:
|
|
|
|
geom = f.geometry()
|
|
|
|
|
|
|
|
intersectedGeom = rasterGeom.intersection(geom)
|
|
|
|
ogrGeom = ogr.CreateGeometryFromWkt(intersectedGeom.exportToWkt())
|
|
|
|
|
|
|
|
if not useGlobalExtent:
|
|
|
|
bbox = intersectedGeom.boundingBox()
|
|
|
|
|
|
|
|
xMin = bbox.xMinimum()
|
|
|
|
xMax = bbox.xMaximum()
|
|
|
|
yMin = bbox.yMinimum()
|
|
|
|
yMax = bbox.yMaximum()
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
(startColumn, startRow) = mapToPixel(xMin, yMax, geoTransform)
|
|
|
|
(endColumn, endRow) = mapToPixel(xMax, yMin, geoTransform)
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
width = endColumn - startColumn
|
|
|
|
height = endRow - startRow
|
|
|
|
|
|
|
|
if width == 0 or height == 0:
|
|
|
|
continue
|
|
|
|
|
|
|
|
srcOffset = (startColumn, startRow, width, height)
|
|
|
|
srcArray = rasterBand.ReadAsArray(*srcOffset)
|
|
|
|
|
2013-10-01 20:52:22 +03:00
|
|
|
newGeoTransform = (
|
|
|
|
geoTransform[0] + srcOffset[0] * geoTransform[1],
|
|
|
|
geoTransform[1],
|
|
|
|
0.0,
|
|
|
|
geoTransform[3] + srcOffset[1] * geoTransform[5],
|
|
|
|
0.0,
|
|
|
|
geoTransform[5],
|
|
|
|
)
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
# Create a temporary vector layer in memory
|
2013-10-01 20:52:22 +03:00
|
|
|
memVDS = memVectorDriver.CreateDataSource('out')
|
2013-08-06 19:55:18 +03:00
|
|
|
memLayer = memVDS.CreateLayer('poly', crs, ogr.wkbPolygon)
|
|
|
|
|
|
|
|
ft = ogr.Feature(memLayer.GetLayerDefn())
|
|
|
|
ft.SetGeometry(ogrGeom)
|
|
|
|
memLayer.CreateFeature(ft)
|
|
|
|
ft.Destroy()
|
|
|
|
|
|
|
|
# Rasterize it
|
2013-10-01 20:52:22 +03:00
|
|
|
rasterizedDS = memRasterDriver.Create('', srcOffset[2],
|
|
|
|
srcOffset[3], 1, gdal.GDT_Byte)
|
2013-08-06 19:55:18 +03:00
|
|
|
rasterizedDS.SetGeoTransform(newGeoTransform)
|
|
|
|
gdal.RasterizeLayer(rasterizedDS, [1], memLayer, burn_values=[1])
|
|
|
|
rasterizedArray = rasterizedDS.ReadAsArray()
|
|
|
|
|
|
|
|
srcArray = numpy.nan_to_num(srcArray)
|
|
|
|
masked = numpy.ma.MaskedArray(srcArray,
|
2013-10-01 20:52:22 +03:00
|
|
|
mask=numpy.logical_or(srcArray == noData,
|
|
|
|
numpy.logical_not(rasterizedArray)))
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
outFeat.setGeometry(geom)
|
|
|
|
|
|
|
|
attrs = f.attributes()
|
|
|
|
attrs.insert(idxMin, float(masked.min()))
|
|
|
|
attrs.insert(idxMax, float(masked.max()))
|
|
|
|
attrs.insert(idxSum, float(masked.sum()))
|
|
|
|
attrs.insert(idxCount, int(masked.count()))
|
|
|
|
attrs.insert(idxMean, float(masked.mean()))
|
|
|
|
attrs.insert(idxStd, float(masked.std()))
|
|
|
|
attrs.insert(idxUnique, numpy.unique(masked.compressed()).size)
|
|
|
|
attrs.insert(idxRange, float(masked.max()) - float(masked.min()))
|
|
|
|
attrs.insert(idxCV, float(masked.var()))
|
2013-10-01 20:52:22 +03:00
|
|
|
# attrs.insert(idxMedian, float(masked.median()))
|
2013-08-06 19:55:18 +03:00
|
|
|
|
|
|
|
outFeat.setAttributes(attrs)
|
|
|
|
writer.addFeature(outFeat)
|
|
|
|
|
|
|
|
memVDS = None
|
|
|
|
rasterizedDS = None
|
|
|
|
|
|
|
|
current += 1
|
|
|
|
progress.setPercentage(int(current * total))
|
|
|
|
|
|
|
|
rasterDS = None
|
|
|
|
|
|
|
|
del writer
|