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 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
|
----------------------------------------------------------------------
--
-- Copyright (c) 2011 David Zwarg <dzwarg@azavea.com>
--
----------------------------------------------------------------------
--
-- Helper method to get the smallest value in a raster, based on the pixeltype.
--
CREATE OR REPLACE FUNCTION ST_MinPossibleValue(pixeltype text)
RETURNS float8 AS
$$
DECLARE
newval int := 0;
BEGIN
newval := CASE
WHEN pixeltype = '1BB' THEN 0
WHEN pixeltype = '2BUI' THEN 0
WHEN pixeltype = '4BUI' THEN 0
WHEN pixeltype = '8BUI' THEN 0
WHEN pixeltype = '8BSI' THEN -128
WHEN pixeltype = '16BUI' THEN 0
WHEN pixeltype = '16BSI' THEN -32768
WHEN pixeltype = '32BUI' THEN 0
WHEN pixeltype = '32BSI' THEN -2147483648
WHEN pixeltype = '32BF' THEN -2147483648 -- Could not find a function returning the smallest real yet
WHEN pixeltype = '64BF' THEN -2147483648 -- Could not find a function returning the smallest float8 yet
END;
RETURN newval;
END;
$$
LANGUAGE 'plpgsql';
--
--Test rasters
--
CREATE OR REPLACE FUNCTION ST_TestRaster(h integer, w integer, val float8)
RETURNS raster AS
$$
DECLARE
BEGIN
RETURN ST_AddBand(ST_MakeEmptyRaster(h, w, 0, 0, 1, 1, 0, 0, -1), '32BF', val, -1);
END;
$$
LANGUAGE 'plpgsql';
--------------------------------------------------------------------
-- ST_MapAlgebraFctNgb - (one raster version) Return a raster which values
-- are the result of a PLPGSQL user function involving a
-- neighborhood of values from the input raster band.
-- Arguments
-- rast raster - Raster on which the user function is evaluated.
-- band integer - Band number of the raster to be evaluated. Default to 1.
-- pixeltype text - Pixeltype assigned to the resulting raster. User function
-- results are truncated to this type. Default to the
-- pixeltype of the first raster.
-- ngbwidth integer - The width of the neighborhood, in cells.
-- ngbheight integer - The heigh of the neighborhood, in cells.
-- userfunction regprocedure - PLPGSQL user function to apply to with neighborhod pixels.
-- args variadic text[] - Arguments to pass into the user function.
--------------------------------------------------------------------
DROP FUNCTION IF EXISTS ST_MapAlgebraFctNgb(rast raster, band integer, pixeltype text, ngbwidth integer, ngbheight integer, userfunction text, nodatamode text, variadic args text[]);
CREATE OR REPLACE FUNCTION ST_MapAlgebraFctNgb(rast raster, band integer, pixeltype text, ngbwidth integer, ngbheight integer, userfunction text, nodatamode text, variadic args text[])
RETURNS raster AS
$$
DECLARE
width integer;
height integer;
x integer;
y integer;
r float;
newrast raster;
newval float8;
newpixeltype text;
newnodatavalue float;
newinitialvalue float;
neighborhood float[][];
nrow float[];
valuesub boolean;
BEGIN
-- Check if raster is NULL
IF rast IS NULL THEN
RAISE NOTICE 'ST_MapAlgebraFctNgb: Raster is NULL. Returning NULL';
RETURN NULL;
END IF;
width := ST_Width(rast);
height := ST_Height(rast);
-- Create a new empty raster having the same georeference as the provided raster
newrast := ST_MakeEmptyRaster(width, height, ST_UpperLeftX(rast), ST_UpperLeftY(rast), ST_ScaleX(rast), ST_ScaleY(rast), ST_SkewX(rast), ST_SkewY(rast), ST_SRID(rast));
-- If this new raster is empty (width = 0 OR height = 0) then there is nothing to compute and we return it right now
IF ST_IsEmpty(newrast) THEN
RAISE NOTICE 'ST_MapAlgebraFctNgb: Raster is empty. Returning an empty raster';
RETURN newrast;
END IF;
-- Check if rast has the required band. Otherwise return a raster without band
IF ST_HasNoBand(rast, band) THEN
RAISE NOTICE 'ST_MapAlgebraFctNgb: Raster does not have the required band. Returning a raster without a band';
RETURN newrast;
END IF;
-- Set the new pixeltype
newpixeltype := pixeltype;
IF newpixeltype IS NULL THEN
newpixeltype := ST_BandPixelType(rast, band);
ELSIF newpixeltype != '1BB' AND newpixeltype != '2BUI' AND newpixeltype != '4BUI' AND newpixeltype != '8BSI' AND newpixeltype != '8BUI' AND
newpixeltype != '16BSI' AND newpixeltype != '16BUI' AND newpixeltype != '32BSI' AND newpixeltype != '32BUI' AND newpixeltype != '32BF' AND newpixeltype != '64BF' THEN
RAISE EXCEPTION 'ST_MapAlgebraFctNgb: Invalid pixeltype "%". Aborting.', newpixeltype;
END IF;
-- Check for notada value
newnodatavalue := ST_BandNodataValue(rast, band);
IF newnodatavalue IS NULL OR newnodatavalue < ST_MinPossibleValue(newpixeltype) OR newnodatavalue > (-ST_MinPossibleValue(newpixeltype) - 1) THEN
RAISE NOTICE 'ST_MapAlgebraFctNgb: Source raster does not have a nodata value or is out of range for the new raster pixeltype, nodata value for new raster set to the min value possible';
newnodatavalue := ST_MinPossibleValue(newpixeltype);
END IF;
-- We set the initial value of the future band to nodata value.
-- If nodatavalue is null then the raster will be initialise to ST_MinPossibleValue
-- but all the values should be recomputed anyway.
newinitialvalue := newnodatavalue;
-- Optimization: If the raster is only filled with nodata value return right now a raster filled with the nodatavalueexpr
IF ST_BandIsNoData(rast, band) THEN
RETURN ST_AddBand(newrast, newpixeltype, newinitialvalue, newnodatavalue);
END IF;
--Create the raster receiving all the computed values. Initialize it to the new initial value.
newrast := ST_AddBand(newrast, newpixeltype, newinitialvalue, newnodatavalue);
IF nodatamode = 'value' THEN
valuesub := TRUE;
ELSIF nodatamode != 'ignore' AND nodatamode != 'NULL' AND NOT nodatamode ~ E'^[\-+]?[0-9]*(\.[0-9]+(e[\-+]?[0-9]+)?)?$' THEN
RAISE NOTICE 'ST_MapAlgebraFctNgb: ''nodatamode'' parameter must be one of: value, ignore, NULL, or a numerical value.';
RETURN NULL;
END IF;
-- Computation loop
FOR x IN (1+ngbwidth)..(width-ngbwidth) LOOP
FOR y IN (1+ngbheight)..(height-ngbheight) LOOP
-- create a matrix of the pixel values in the neighborhood
neighborhood := ARRAY[[]]::float[];
FOR u IN (x-ngbwidth)..(x+ngbwidth) LOOP
nrow := ARRAY[]::float[];
FOR v in (y-ngbheight)..(y+ngbheight) LOOP
nrow := nrow || ARRAY[ ST_Value(rast, band, u, v)::float ];
END LOOP;
neighborhood := neighborhood || ARRAY[ nrow ];
END LOOP;
-- RAISE NOTICE 'Neighborhood: %', neighborhood;
IF valuesub IS TRUE THEN
nodatamode := ST_Value(rast, band, x, y);
-- special case where NULL needs to be quoted
IF nodatamode IS NULL THEN
nodatamode := 'NULL';
END IF;
END IF;
EXECUTE 'SELECT ' ||userfunction || '(' || quote_literal(neighborhood) || ', ' || quote_literal(nodatamode) || ', NULL)' INTO r;
newrast = ST_SetValue(newrast, band, x, y, r);
END LOOP;
END LOOP;
RETURN newrast;
END;
$$
LANGUAGE 'plpgsql';
--
-- A simple 'callback' user function that sums up all the values in a neighborhood.
--
CREATE OR REPLACE FUNCTION ST_Sum(matrix float[][], nodatamode text, variadic args text[])
RETURNS float AS
$$
DECLARE
x1 integer;
x2 integer;
y1 integer;
y2 integer;
sum float;
BEGIN
sum := 0;
raise notice 'in callback: %', nodatamode;
FOR x in array_lower(matrix, 1)..array_upper(matrix, 1) LOOP
FOR y in array_lower(matrix, 2)..array_upper(matrix, 2) LOOP
IF matrix[x][y] IS NULL THEN
raise notice 'matrix[%][%] is NULL', x, y;
IF nodatamode = 'ignore' THEN
matrix[x][y] := 0;
ELSE
matrix[x][y] := nodatamode::float;
END IF;
END IF;
sum := sum + matrix[x][y];
END LOOP;
END LOOP;
RETURN sum;
END;
$$
LANGUAGE 'plpgsql';
-- Tests
-- Test NULL Raster. Should be true.
--SELECT ST_MapAlgebraFctNgb(NULL, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL) IS NULL FROM ST_TestRaster(0, 0, -1) rast;
-- Test empty Raster. Should be true.
--SELECT ST_IsEmpty(ST_MapAlgebraFctNgb(ST_MakeEmptyRaster(0, 10, 0, 0, 1, 1, 1, 1, -1), 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL));
-- Test has no band raster. Should be true
--SELECT ST_HasNoBand(ST_MapAlgebraFctNgb(ST_MakeEmptyRaster(10, 10, 0, 0, 1, 1, 1, 1, -1), 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL));
-- Test has no nodata value. Should return null and 7.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(
-- ST_SetBandNoDataValue(rast, NULL), 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL
-- ), 2, 2) = 7
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 2, 2, NULL) AS rast;
--
-- Test NULL nodatamode. Should return null and null.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- ) IS NULL
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 2, 2, NULL) AS rast;
--
-- Test ignore nodatamode. Should return null and 8.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'ignore', NULL), 2, 2
-- ) = 8
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 2, 2, NULL) AS rast;
--
-- Test value nodatamode. Should return null and null.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'value', NULL), 2, 2
-- ) IS NULL
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 2, 2, NULL) AS rast;
--
-- Test value nodatamode. Should return null and 9.
--SELECT
-- ST_Value(rast, 1, 1) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'value', NULL), 2, 2
-- ) = 9
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 1, 1, NULL) AS rast;
--
-- Test value nodatamode. Should return null and 0.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', '-8', NULL), 2, 2
-- ) = 0
-- FROM ST_SetValue(ST_TestRaster(3, 3, 1), 2, 2, NULL) AS rast;
--
-- Test ST_Sum user function. Should be 1 and 9.
--SELECT
-- ST_Value(rast, 2, 2) = 1,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- ) = 9
-- FROM ST_TestRaster(3, 3, 1) AS rast;
--
-- Test ST_Sum user function on a no nodata value raster. Should be null and -1.
--SELECT
-- ST_Value(rast, 2, 2) IS NULL,
-- ST_Value(
-- ST_MapAlgebraFctNgb(ST_SetBandNoDataValue(rast, NULL), 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- ) = -1
-- FROM ST_SetValue(ST_TestRaster(3, 3, 0), 2, 2, NULL) AS rast;
--
-- Test pixeltype 1. Should return 2 and 15.
--SELECT
-- ST_Value(rast, 2, 2) = 2,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, '4BUI', 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- ) = 15
-- FROM ST_SetBandNoDataValue(ST_TestRaster(3, 3, 2), 1, NULL) AS rast;
--
-- Test pixeltype 1. Should return an error.
--SELECT
-- ST_Value(rast, 2, 2),
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, '4BUId', 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- )
-- FROM ST_TestRaster(3, 3, 2) AS rast;
--
-- Test pixeltype 1. Should return 1 and 3.
--SELECT
-- ST_Value(rast, 2, 2) = 1,
-- ST_Value(
-- ST_MapAlgebraFctNgb(rast, 1, '2BUI', 1, 1, 'ST_Sum', 'NULL', NULL), 2, 2
-- ) = 3
-- FROM ST_TestRaster(3, 3, 1) AS rast;
--
-- Test that the neighborhood function leaves a border of NODATA
--SELECT
-- COUNT(*) = 1
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL)
-- )).*
-- FROM ST_TestRaster(5, 5, 1) AS rast) AS foo
-- WHERE ST_Area(geom) = 9;
--
-- Test that the neighborhood function leaves a border of NODATA
--SELECT
-- ST_Area(geom) = 8, val = 9
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL)
-- )).*
-- FROM ST_SetValue(ST_TestRaster(5, 5, 1), 1, 1, NULL) AS rast) AS foo;
--
-- Test that the neighborhood function leaves a border of NODATA
-- plus a corner where one cell has a value of 8.
--SELECT
-- (ST_Area(geom) = 1 AND val = 8) OR (ST_Area(geom) = 8 AND val = 9)
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'ignore', NULL)
-- )).*
-- FROM ST_SetValue(ST_TestRaster(5, 5, 1), 1, 1, NULL) AS rast) AS foo;
--
-- Test that the neighborhood function leaves a border of NODATA
-- plus a hole where 9 cells have NODATA
-- This results in a donut: a polygon with a hole. The polygon has
-- an area of 16, with a hole that has an area of 9
--SELECT
-- ST_NRings(geom) = 2,
-- ST_NumInteriorRings(geom) = 1,
-- ST_Area(geom) = 16,
-- val = 9,
-- ST_Area(ST_BuildArea(ST_InteriorRingN(geom, 1))) = 9
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL)
-- )).*
-- FROM ST_SetValue(ST_TestRaster(7, 7, 1), 4, 4, NULL) AS rast) AS foo;
--
-- Test that the neighborhood function leaves a border of NODATA,
-- and the center pyramids when summed twice, ignoring NODATA values
--SELECT
-- COUNT(*) = 9, SUM(ST_Area(geom)) = 9, SUM(val) = ((36+54+36) + (54+81+54) + (36+54+36))
-- --ST_AsText(geom), ST_Area(geom), val
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'ignore', NULL), 1, NULL, 1, 1, 'ST_Sum', 'ignore', NULL
-- )
-- )).*
-- FROM ST_SetBandNoDataValue(ST_TestRaster(5, 5, 1), NULL) AS rast) AS foo;
--
-- Test that the neighborhood function leaves a border of NODATA,
-- and the center contains one cel when summed twice, replacing NULL with NODATA values
--SELECT
-- COUNT(*) = 1, SUM(ST_Area(geom)) = 1, SUM(val) = 81
-- --ST_AsText(geom), ST_Area(geom), val
-- FROM (SELECT
-- (ST_DumpAsPolygons(
-- ST_MapAlgebraFctNgb(
-- ST_MapAlgebraFctNgb(rast, 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL), 1, NULL, 1, 1, 'ST_Sum', 'NULL', NULL
-- )
-- )).*
-- FROM ST_SetBandNoDataValue(ST_TestRaster(5, 5, 1), NULL) AS rast) AS foo;
--
|