/*
 *
 * WKTRaster - Raster Types for PostGIS
 * http://trac.osgeo.org/postgis/wiki/WKTRaster
 *
 * Copyright (C) 2011-2013 Regents of the University of California
 *   <bkpark@ucdavis.edu>
 * Copyright (C) 2010-2011 Jorge Arevalo <jorge.arevalo@deimos-space.com>
 * Copyright (C) 2010-2011 David Zwarg <dzwarg@azavea.com>
 * Copyright (C) 2009-2011 Pierre Racine <pierre.racine@sbf.ulaval.ca>
 * Copyright (C) 2009-2011 Mateusz Loskot <mateusz@loskot.net>
 * Copyright (C) 2008-2009 Sandro Santilli <strk@kbt.io>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <postgres.h>
#include <fmgr.h>
#include "utils/lsyscache.h" /* for get_typlenbyvalalign */
#include <funcapi.h>
#include "utils/array.h" /* for ArrayType */
#include "utils/builtins.h" /* for text_to_cstring */
#include "utils/formatting.h" /* for asc_tolower */
#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */

#include "../../postgis_config.h"
#include "lwgeom_pg.h"



#include "access/htup_details.h" /* for heap_form_tuple() */


#include "rtpostgis.h"

/* Get pixel value */
Datum RASTER_getPixelValue(PG_FUNCTION_ARGS);
Datum RASTER_getPixelValueResample(PG_FUNCTION_ARGS);
Datum RASTER_dumpValues(PG_FUNCTION_ARGS);

/* Set pixel value(s) */
Datum RASTER_setPixelValue(PG_FUNCTION_ARGS);
Datum RASTER_setPixelValuesArray(PG_FUNCTION_ARGS);
Datum RASTER_setPixelValuesGeomval(PG_FUNCTION_ARGS);
Datum RASTER_getGeometryValues(PG_FUNCTION_ARGS);

/* Get pixels of value */
Datum RASTER_pixelOfValue(PG_FUNCTION_ARGS);

/* Get nearest value to a point */
Datum RASTER_nearestValue(PG_FUNCTION_ARGS);

/* Get the neighborhood around a pixel */
Datum RASTER_neighborhood(PG_FUNCTION_ARGS);

/**
 * Return value of a single pixel.
 * Pixel location is specified by 1-based index of Nth band of raster and
 * X,Y coordinates (X <= RT_Width(raster) and Y <= RT_Height(raster)).
 *
 * TODO: Should we return NUMERIC instead of FLOAT8 ?
 */
PG_FUNCTION_INFO_V1(RASTER_getPixelValue);
Datum RASTER_getPixelValue(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	double pixvalue = 0;
	int32_t bandindex = 0;
	int32_t x = 0;
	int32_t y = 0;
	int result = 0;
	bool exclude_nodata_value = TRUE;
		int isnodata = 0;

	/* Index is 1-based */
	bandindex = PG_GETARG_INT32(1);
	if ( bandindex < 1 ) {
		elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL");
		PG_RETURN_NULL();
	}

	x = PG_GETARG_INT32(2);

	y = PG_GETARG_INT32(3);

	exclude_nodata_value = PG_GETARG_BOOL(4);

	POSTGIS_RT_DEBUGF(3, "Pixel coordinates (%d, %d)", x, y);

	/* Deserialize raster */
	if (PG_ARGISNULL(0)) PG_RETURN_NULL();
	pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));

	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_getPixelValue: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* Fetch Nth band using 0-based internal index */
	band = rt_raster_get_band(raster, bandindex - 1);
	if (! band) {
		elog(NOTICE, "Could not find raster band of index %d when getting pixel "
				"value. Returning NULL", bandindex);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}
	/* Fetch pixel using 0-based coordinates */
	result = rt_band_get_pixel(band, x - 1, y - 1, &pixvalue, &isnodata);

	/* If the result is -1 or the value is nodata and we take nodata into account
	 * then return nodata = NULL */
	if (result != ES_NONE || (exclude_nodata_value && isnodata)) {
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}

	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);

	PG_RETURN_FLOAT8(pixvalue);
}

static rt_resample_type resample_text_to_type(text *txt)
{
	char *resample = asc_tolower(VARDATA(txt), VARSIZE_ANY_EXHDR(txt));
	if (strncmp(resample, "bilinear", 8) == 0)
		return RT_BILINEAR;
	else if (strncmp(resample, "nearest", 7) == 0)
		return RT_NEAREST;
	else {
		elog(ERROR, "Unknown resample type '%s' requested", resample);
	}
	pfree(resample);
	return RT_NEAREST;
}

/*
* ST_Value(
*   rast raster,
*   band integer,
*   pt geometry,
*   exclude_nodata_value boolean DEFAULT TRUE,
*   resample text DEFAULT 'nearest'
*/
PG_FUNCTION_INFO_V1(RASTER_getPixelValueResample);
Datum RASTER_getPixelValueResample(PG_FUNCTION_ARGS)
{
	rt_raster raster = NULL;
	rt_band band = NULL;
	rt_pgraster *pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
	int32_t bandnum = PG_GETARG_INT32(1);
	GSERIALIZED *gser;
	LWPOINT *lwpoint;
	LWGEOM *lwgeom;
	bool exclude_nodata_value = PG_GETARG_BOOL(3);
	rt_resample_type resample_type = RT_NEAREST;
	double x, y, xr, yr;
	double pixvalue = 0.0;
	int isnodata = 0;
	rt_errorstate err;

	/* Index is 1-based */
	if (bandnum < 1) {
		elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL");
		PG_RETURN_NULL();
	}

	gser = (GSERIALIZED*)PG_DETOAST_DATUM(PG_GETARG_DATUM(2));
	if (gserialized_get_type(gser) != POINTTYPE || gserialized_is_empty(gser)) {
		elog(ERROR, "Attempting to get the value of a pixel with a non-point geometry");
		PG_RETURN_NULL();
	}

	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		elog(ERROR, "RASTER_getPixelValue: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	if (gserialized_get_srid(gser) != rt_raster_get_srid(raster)) {
		elog(ERROR, "Raster and geometry do not have the same SRID");
		PG_RETURN_NULL();
	}

	if (PG_NARGS() > 4) {
		text *resample_text = PG_GETARG_TEXT_P(4);
		resample_type = resample_text_to_type(resample_text);
	}

	/* Fetch Nth band using 0-based internal index */
	band = rt_raster_get_band(raster, bandnum - 1);
	if (!band) {
		elog(NOTICE, "Could not find raster band of index %d when getting pixel "
					"value. Returning NULL", bandnum);
		PG_RETURN_NULL();
	}

	/* Get the X/Y coordinates */
	lwgeom = lwgeom_from_gserialized(gser);
	lwpoint = lwgeom_as_lwpoint(lwgeom);
	x = lwpoint_get_x(lwpoint);
	y = lwpoint_get_y(lwpoint);

	/* Convert X/Y world coordinates into raster coordinates */
	err = rt_raster_geopoint_to_rasterpoint(raster, x, y, &xr, &yr, NULL);
	if (err != ES_NONE) {
		elog(ERROR, "Could not convert world coordinate to raster coordinate");
		PG_RETURN_NULL();
	}

	/* Use appropriate resample algorithm */
	err = rt_band_get_pixel_resample(
		band,
		xr, yr,
		resample_type,
		&pixvalue, &isnodata
		);

	/* If the result is -1 or the value is nodata and we take nodata into account
	 * then return nodata = NULL */
	rt_raster_destroy(raster);
	lwgeom_free(lwgeom);
	if (err != ES_NONE || (exclude_nodata_value && isnodata)) {
		PG_RETURN_NULL();
	}
	PG_RETURN_FLOAT8(pixvalue);
}


/*
* ST_SetZ(
*   rast raster,
*   pt geometry,
*   resample text DEFAULT 'nearest' ),
*   band integer default 1,
*   dimension text default 'z'
*/
PG_FUNCTION_INFO_V1(RASTER_getGeometryValues);
Datum RASTER_getGeometryValues(PG_FUNCTION_ARGS)
{
	rt_raster raster = NULL;
	rt_pgraster *pgraster = NULL;
	GSERIALIZED *gser;
	LWGEOM *lwgeom_in, *lwgeom_out;
	rt_resample_type resample_type = RT_NEAREST;
	rt_errorstate err;
	char dimension;
	const char *func_name;
	uint16_t num_bands;
	int32_t band;

	text *resample_text = PG_GETARG_TEXT_P(2);

	/* Dimension depends on the name of calling SQL function */
	/* ST_SetZ()? or ST_SetM()? */
	func_name = get_func_name(fcinfo->flinfo->fn_oid);
	if (strcmp(func_name, "st_setz") == 0)
		dimension = 'z';
	else if (strcmp(func_name, "st_setm") == 0)
		dimension = 'm';
	else
		elog(ERROR, "%s called from unexpected SQL signature", __func__);

	/* Geometry */
	gser = (GSERIALIZED*)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
	if (gserialized_is_empty(gser)) {
		elog(ERROR, "Cannot copy value into an empty geometry");
		PG_RETURN_NULL();
	}

	/* Raster */
	pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
	raster = rt_raster_deserialize(pgraster, FALSE);
	num_bands = rt_raster_get_num_bands(raster);
	if (!raster) {
		elog(ERROR, "Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* Bandnidex is 1-based */
	band = PG_GETARG_INT32(3);
	if (band < 1 || band > num_bands) {
		elog(NOTICE, "Invalid band index %d. Must be between 1 and %u", band, num_bands);
		PG_RETURN_NULL();
	}

	/* SRID consistency */
	if (gserialized_get_srid(gser) != rt_raster_get_srid(raster)) {
		elog(ERROR, "Raster and geometry do not have the same SRID");
		PG_RETURN_NULL();
	}

	/* Process arguments */
	resample_type = resample_text_to_type(resample_text);

	/* Get the geometry */
	lwgeom_in = lwgeom_from_gserialized(gser);

	/* Run the sample */
	err = rt_raster_copy_to_geometry(
		raster,
		band - 1,      /* rtcore uses 0-based band number */
		dimension,     /* 'z' or 'm' */
		resample_type, /* bilinear or nearest */
		lwgeom_in,
		&lwgeom_out
	);

	rt_raster_destroy(raster);
	lwgeom_free(lwgeom_in);
	if (err != ES_NONE || !lwgeom_out) {
		PG_RETURN_NULL();
	}
	PG_RETURN_POINTER(gserialized_from_lwgeom(lwgeom_out, NULL));
}


/* ---------------------------------------------------------------- */
/*  ST_DumpValues function                                          */
/* ---------------------------------------------------------------- */

typedef struct rtpg_dumpvalues_arg_t *rtpg_dumpvalues_arg;
struct rtpg_dumpvalues_arg_t {
	int numbands;
	int rows;
	int columns;

	int *nbands; /* 0-based */
	Datum **values;
	bool **nodata;
};

static rtpg_dumpvalues_arg rtpg_dumpvalues_arg_init() {
	rtpg_dumpvalues_arg arg = NULL;

	arg = palloc(sizeof(struct rtpg_dumpvalues_arg_t));
	if (arg == NULL) {
		elog(ERROR, "rtpg_dumpvalues_arg_init: Could not allocate memory for arguments");
		return NULL;
	}

	arg->numbands = 0;
	arg->rows = 0;
	arg->columns = 0;

	arg->nbands = NULL;
	arg->values = NULL;
	arg->nodata = NULL;

	return arg;
}

static void rtpg_dumpvalues_arg_destroy(rtpg_dumpvalues_arg arg) {
	int i = 0;

	if (arg->numbands > 0) {
		if (arg->nbands != NULL)
			pfree(arg->nbands);

		if (arg->values != NULL) {
			for (i = 0; i < arg->numbands; i++) {

				if (arg->values[i] != NULL)
					pfree(arg->values[i]);

				if (arg->nodata[i] != NULL)
					pfree(arg->nodata[i]);
			}

			pfree(arg->values);
		}

		if (arg->nodata != NULL)
			pfree(arg->nodata);
	}

	pfree(arg);
}

#define VALUES_LENGTH 2

PG_FUNCTION_INFO_V1(RASTER_dumpValues);
Datum RASTER_dumpValues(PG_FUNCTION_ARGS)
{
	FuncCallContext *funcctx;
	TupleDesc tupdesc;
	int call_cntr;
	int max_calls;
	int i = 0;
	int x = 0;
	int y = 0;
	int z = 0;

	int16 typlen;
	bool typbyval;
	char typalign;

	rtpg_dumpvalues_arg arg1 = NULL;
	rtpg_dumpvalues_arg arg2 = NULL;

	/* stuff done only on the first call of the function */
	if (SRF_IS_FIRSTCALL()) {
		MemoryContext oldcontext;
		rt_pgraster *pgraster = NULL;
		rt_raster raster = NULL;
		rt_band band = NULL;
		int numbands = 0;
		int j = 0;
		bool exclude_nodata_value = TRUE;

		ArrayType *array;
		Oid etype;
		Datum *e;
		bool *nulls;

		double val = 0;
		int isnodata = 0;

		POSTGIS_RT_DEBUG(2, "RASTER_dumpValues first call");

		/* create a function context for cross-call persistence */
		funcctx = SRF_FIRSTCALL_INIT();

		/* switch to memory context appropriate for multiple function calls */
		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

		/* Get input arguments */
		if (PG_ARGISNULL(0)) {
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}
		pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));

		raster = rt_raster_deserialize(pgraster, FALSE);
		if (!raster) {
			PG_FREE_IF_COPY(pgraster, 0);
			ereport(ERROR, (
				errcode(ERRCODE_OUT_OF_MEMORY),
				errmsg("Could not deserialize raster")
			));
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* check that raster is not empty */
		/*
		if (rt_raster_is_empty(raster)) {
			elog(NOTICE, "Raster provided is empty");
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}
		*/

		/* raster has bands */
		numbands = rt_raster_get_num_bands(raster);
		if (!numbands) {
			elog(NOTICE, "Raster provided has no bands");
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* initialize arg1 */
		arg1 = rtpg_dumpvalues_arg_init();
		if (arg1 == NULL) {
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			elog(ERROR, "RASTER_dumpValues: Could not initialize argument structure");
			SRF_RETURN_DONE(funcctx);
		}

		/* nband, array */
		if (!PG_ARGISNULL(1)) {
			array = PG_GETARG_ARRAYTYPE_P(1);
			etype = ARR_ELEMTYPE(array);
			get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign);

			switch (etype) {
				case INT2OID:
				case INT4OID:
					break;
				default:
					rtpg_dumpvalues_arg_destroy(arg1);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					MemoryContextSwitchTo(oldcontext);
					elog(ERROR, "RASTER_dumpValues: Invalid data type for band indexes");
					SRF_RETURN_DONE(funcctx);
					break;
			}

			deconstruct_array(array, etype, typlen, typbyval, typalign, &e, &nulls, &(arg1->numbands));

			arg1->nbands = palloc(sizeof(int) * arg1->numbands);
			if (arg1->nbands == NULL) {
				rtpg_dumpvalues_arg_destroy(arg1);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				MemoryContextSwitchTo(oldcontext);
				elog(ERROR, "RASTER_dumpValues: Could not allocate memory for band indexes");
				SRF_RETURN_DONE(funcctx);
			}

			for (i = 0, j = 0; i < arg1->numbands; i++) {
				if (nulls[i]) continue;

				switch (etype) {
					case INT2OID:
						arg1->nbands[j] = DatumGetInt16(e[i]) - 1;
						break;
					case INT4OID:
						arg1->nbands[j] = DatumGetInt32(e[i]) - 1;
						break;
				}

				j++;
			}

			if (j < arg1->numbands) {
				arg1->nbands = repalloc(arg1->nbands, sizeof(int) * j);
				if (arg1->nbands == NULL) {
					rtpg_dumpvalues_arg_destroy(arg1);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					MemoryContextSwitchTo(oldcontext);
					elog(ERROR, "RASTER_dumpValues: Could not reallocate memory for band indexes");
					SRF_RETURN_DONE(funcctx);
				}

				arg1->numbands = j;
			}

			/* validate nbands */
			for (i = 0; i < arg1->numbands; i++) {
				if (!rt_raster_has_band(raster, arg1->nbands[i])) {
					elog(NOTICE, "Band at index %d not found in raster", arg1->nbands[i] + 1);
					rtpg_dumpvalues_arg_destroy(arg1);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					MemoryContextSwitchTo(oldcontext);
					SRF_RETURN_DONE(funcctx);
				}
			}

		}
		/* no bands specified, return all bands */
		else {
			arg1->numbands = numbands;
			arg1->nbands = palloc(sizeof(int) * arg1->numbands);

			if (arg1->nbands == NULL) {
				rtpg_dumpvalues_arg_destroy(arg1);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				MemoryContextSwitchTo(oldcontext);
				elog(ERROR, "RASTER_dumpValues: Could not allocate memory for band indexes");
				SRF_RETURN_DONE(funcctx);
			}

			for (i = 0; i < arg1->numbands; i++) {
				arg1->nbands[i] = i;
				POSTGIS_RT_DEBUGF(4, "arg1->nbands[%d] = %d", arg1->nbands[i], i);
			}
		}

		arg1->rows = rt_raster_get_height(raster);
		arg1->columns = rt_raster_get_width(raster);

		/* exclude_nodata_value */
		if (!PG_ARGISNULL(2))
			exclude_nodata_value = PG_GETARG_BOOL(2);
		POSTGIS_RT_DEBUGF(4, "exclude_nodata_value = %d", exclude_nodata_value);

		/* allocate memory for each band's values and nodata flags */
		arg1->values = palloc(sizeof(Datum *) * arg1->numbands);
		arg1->nodata = palloc(sizeof(bool *) * arg1->numbands);
		if (arg1->values == NULL || arg1->nodata == NULL) {
			rtpg_dumpvalues_arg_destroy(arg1);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			elog(ERROR, "RASTER_dumpValues: Could not allocate memory for pixel values");
			SRF_RETURN_DONE(funcctx);
		}
		memset(arg1->values, 0, sizeof(Datum *) * arg1->numbands);
		memset(arg1->nodata, 0, sizeof(bool *) * arg1->numbands);

		/* get each band and dump data */
		for (z = 0; z < arg1->numbands; z++) {
			/* shortcut if raster is empty */
			if (rt_raster_is_empty(raster))
				break;

			band = rt_raster_get_band(raster, arg1->nbands[z]);
			if (!band) {
				int nband = arg1->nbands[z] + 1;
				rtpg_dumpvalues_arg_destroy(arg1);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				MemoryContextSwitchTo(oldcontext);
				elog(ERROR, "RASTER_dumpValues: Could not get band at index %d", nband);
				SRF_RETURN_DONE(funcctx);
			}

			/* allocate memory for values and nodata flags */
			arg1->values[z] = palloc(sizeof(Datum) * arg1->rows * arg1->columns);
			arg1->nodata[z] = palloc(sizeof(bool) * arg1->rows * arg1->columns);
			if (arg1->values[z] == NULL || arg1->nodata[z] == NULL) {
				rtpg_dumpvalues_arg_destroy(arg1);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				MemoryContextSwitchTo(oldcontext);
				elog(ERROR, "RASTER_dumpValues: Could not allocate memory for pixel values");
				SRF_RETURN_DONE(funcctx);
			}
			memset(arg1->values[z], 0, sizeof(Datum) * arg1->rows * arg1->columns);
			memset(arg1->nodata[z], 0, sizeof(bool) * arg1->rows * arg1->columns);

			i = 0;

			/* shortcut if band is NODATA */
			if (rt_band_get_isnodata_flag(band)) {
				for (i = (arg1->rows * arg1->columns) - 1; i >= 0; i--)
					arg1->nodata[z][i] = TRUE;
				continue;
			}

			for (y = 0; y < arg1->rows; y++) {
				for (x = 0; x < arg1->columns; x++) {
					/* get pixel */
					if (rt_band_get_pixel(band, x, y, &val, &isnodata) != ES_NONE) {
						int nband = arg1->nbands[z] + 1;
						rtpg_dumpvalues_arg_destroy(arg1);
						rt_raster_destroy(raster);
						PG_FREE_IF_COPY(pgraster, 0);
						MemoryContextSwitchTo(oldcontext);
						elog(ERROR, "RASTER_dumpValues: Could not pixel (%d, %d) of band %d", x, y, nband);
						SRF_RETURN_DONE(funcctx);
					}

					arg1->values[z][i] = Float8GetDatum(val);
					POSTGIS_RT_DEBUGF(5, "arg1->values[z][i] = %f", DatumGetFloat8(arg1->values[z][i]));
					POSTGIS_RT_DEBUGF(5, "clamped is?: %d", rt_band_clamped_value_is_nodata(band, val));

					if (exclude_nodata_value && isnodata) {
						arg1->nodata[z][i] = TRUE;
						POSTGIS_RT_DEBUG(5, "nodata = 1");
					}
					else
						POSTGIS_RT_DEBUG(5, "nodata = 0");

					i++;
				}
			}
		}

		/* cleanup */
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);

		/* Store needed information */
		funcctx->user_fctx = arg1;

		/* total number of tuples to be returned */
		funcctx->max_calls = arg1->numbands;

		/* Build a tuple descriptor for our result type */
		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
			MemoryContextSwitchTo(oldcontext);
			ereport(ERROR, (
				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				errmsg(
					"function returning record called in context "
					"that cannot accept type record"
				)
			));
		}

		BlessTupleDesc(tupdesc);
		funcctx->tuple_desc = tupdesc;

		MemoryContextSwitchTo(oldcontext);
	}

	/* stuff done on every call of the function */
	funcctx = SRF_PERCALL_SETUP();

	call_cntr = funcctx->call_cntr;
	max_calls = funcctx->max_calls;
	tupdesc = funcctx->tuple_desc;
	arg2 = funcctx->user_fctx;

	/* do when there is more left to send */
	if (call_cntr < max_calls) {
		Datum values[VALUES_LENGTH];
		bool nulls[VALUES_LENGTH];
		HeapTuple tuple;
		Datum result;
		ArrayType *mdValues = NULL;
		int ndim = 2;
		int dim[2] = {arg2->rows, arg2->columns};
		int lbound[2] = {1, 1};

		POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr);
		POSTGIS_RT_DEBUGF(4, "dim = %d, %d", dim[0], dim[1]);

		memset(nulls, FALSE, sizeof(bool) * VALUES_LENGTH);

		values[0] = Int32GetDatum(arg2->nbands[call_cntr] + 1);

		/* info about the type of item in the multi-dimensional array (float8). */
		get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign);

		/* if values is NULL, return empty array */
		if (arg2->values[call_cntr] == NULL)
			ndim = 0;

		/* assemble 3-dimension array of values */
		mdValues = construct_md_array(
			arg2->values[call_cntr], arg2->nodata[call_cntr],
			ndim, dim, lbound,
			FLOAT8OID,
			typlen, typbyval, typalign
		);
		values[1] = PointerGetDatum(mdValues);

		/* build a tuple and datum */
		tuple = heap_form_tuple(tupdesc, values, nulls);
		result = HeapTupleGetDatum(tuple);

		SRF_RETURN_NEXT(funcctx, result);
	}
	/* do when there is no more left */
	else {
		rtpg_dumpvalues_arg_destroy(arg2);
		SRF_RETURN_DONE(funcctx);
	}
}

/**
 * Write value of raster sample on given position and in specified band.
 */
PG_FUNCTION_INFO_V1(RASTER_setPixelValue);
Datum RASTER_setPixelValue(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_pgraster *pgrtn = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	double pixvalue = 0;
	int32_t bandindex = 0;
	int32_t x = 0;
	int32_t y = 0;
	bool skipset = FALSE;

	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();

	/* Check index is not NULL or < 1 */
	if (PG_ARGISNULL(1))
		bandindex = -1;
	else
		bandindex = PG_GETARG_INT32(1);

	if (bandindex < 1) {
		elog(NOTICE, "Invalid band index (must use 1-based). Value not set. Returning original raster");
		skipset = TRUE;
	}

	/* Validate pixel coordinates are not null */
	if (PG_ARGISNULL(2)) {
		elog(NOTICE, "X coordinate can not be NULL when setting pixel value. Value not set. Returning original raster");
		skipset = TRUE;
	}
	else
		x = PG_GETARG_INT32(2);

	if (PG_ARGISNULL(3)) {
		elog(NOTICE, "Y coordinate can not be NULL when setting pixel value. Value not set. Returning original raster");
		skipset = TRUE;
	}
	else
		y = PG_GETARG_INT32(3);

	POSTGIS_RT_DEBUGF(3, "Pixel coordinates (%d, %d)", x, y);

	/* Deserialize raster */
	pgraster = (rt_pgraster *) PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));

	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValue: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	if (!skipset) {
		/* Fetch requested band */
		band = rt_raster_get_band(raster, bandindex - 1);
		if (!band) {
			elog(NOTICE, "Could not find raster band of index %d when setting "
				"pixel value. Value not set. Returning original raster",
				bandindex);
			PG_RETURN_POINTER(pgraster);
		}
		else {
			/* Set the pixel value */
			if (PG_ARGISNULL(4)) {
				if (!rt_band_get_hasnodata_flag(band)) {
					elog(NOTICE, "Raster do not have a nodata value defined. "
						"Set band nodata value first. Nodata value not set. "
						"Returning original raster");
					PG_RETURN_POINTER(pgraster);
				}
				else {
					rt_band_get_nodata(band, &pixvalue);
					rt_band_set_pixel(band, x - 1, y - 1, pixvalue, NULL);
				}
			}
			else {
				pixvalue = PG_GETARG_FLOAT8(4);
				rt_band_set_pixel(band, x - 1, y - 1, pixvalue, NULL);
			}
		}
	}

	pgrtn = rt_raster_serialize(raster);
	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);
	if (!pgrtn)
		PG_RETURN_NULL();

	SET_VARSIZE(pgrtn, pgrtn->size);
	PG_RETURN_POINTER(pgrtn);
}

/**
 * Set pixels to value from array
 */
PG_FUNCTION_INFO_V1(RASTER_setPixelValuesArray);
Datum RASTER_setPixelValuesArray(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_pgraster *pgrtn = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	int numbands = 0;

	int nband = 0;
	int width = 0;
	int height = 0;

	ArrayType *array;
	Oid etype;
	Datum *elements;
	bool *nulls;
	int16 typlen;
	bool typbyval;
	char typalign;
	int ndims = 1;
	int *dims;
	int num = 0;

	int ul[2] = {0};
	struct pixelvalue {
		int x;
		int y;

		bool noset;
		bool nodata;
		double value;
	};
	struct pixelvalue *pixval = NULL;
	int numpixval = 0;
	int dimpixval[2] = {1, 1};
	int dimnoset[2] = {1, 1};
	int hasnodata = FALSE;
	double nodataval = 0;
	bool keepnodata = FALSE;
	bool hasnosetval = FALSE;
	bool nosetvalisnull = FALSE;
	double nosetval = 0;

	int rtn = 0;
	double val = 0;
	int isnodata = 0;

	int i = 0;
	int j = 0;
	int x = 0;
	int y = 0;

	/* pgraster is null, return null */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
	pgraster = (rt_pgraster *) PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));

	/* raster */
	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesArray: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* raster attributes */
	numbands = rt_raster_get_num_bands(raster);
	width = rt_raster_get_width(raster);
	height = rt_raster_get_height(raster);

	/* nband */
	if (PG_ARGISNULL(1)) {
		elog(NOTICE, "Band index cannot be NULL.  Value must be 1-based.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	nband = PG_GETARG_INT32(1);
	if (nband < 1 || nband > numbands) {
		elog(NOTICE, "Band index is invalid.  Value must be 1-based.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	/* x, y */
	for (i = 2, j = 0; i < 4; i++, j++) {
		if (PG_ARGISNULL(i)) {
			elog(NOTICE, "%s cannot be NULL.  Value must be 1-based.  Returning original raster", j < 1 ? "X" : "Y");
			rt_raster_destroy(raster);
			PG_RETURN_POINTER(pgraster);
		}

		ul[j] = PG_GETARG_INT32(i);
		if (
			(ul[j] < 1) || (
				(j < 1 && ul[j] > width) ||
				(j > 0 && ul[j] > height)
			)
		) {
			elog(NOTICE, "%s is invalid.  Value must be 1-based.  Returning original raster", j < 1 ? "X" : "Y");
			rt_raster_destroy(raster);
			PG_RETURN_POINTER(pgraster);
		}

		/* force 0-based from 1-based */
		ul[j] -= 1;
	}

	/* new value set */
	if (PG_ARGISNULL(4)) {
		elog(NOTICE, "No values to set.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	array = PG_GETARG_ARRAYTYPE_P(4);
	etype = ARR_ELEMTYPE(array);
	get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign);

	switch (etype) {
		case FLOAT4OID:
		case FLOAT8OID:
			break;
		default:
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesArray: Invalid data type for new values");
			PG_RETURN_NULL();
			break;
	}

	ndims = ARR_NDIM(array);
	dims = ARR_DIMS(array);
	POSTGIS_RT_DEBUGF(4, "ndims = %d", ndims);

	if (ndims < 1 || ndims > 2) {
		elog(NOTICE, "New values array must be of 1 or 2 dimensions.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}
	/* outer element, then inner element */
	/* i = 0, y */
	/* i = 1, x */
	if (ndims != 2)
		dimpixval[1] = dims[0];
	else {
		dimpixval[0] = dims[0];
		dimpixval[1] = dims[1];
	}
	POSTGIS_RT_DEBUGF(4, "dimpixval = (%d, %d)", dimpixval[0], dimpixval[1]);

	deconstruct_array(
		array,
		etype,
		typlen, typbyval, typalign,
		&elements, &nulls, &num
	);

	/* # of elements doesn't match dims */
	if (num < 1 || num != (dimpixval[0] * dimpixval[1])) {
		if (num) {
			pfree(elements);
			pfree(nulls);
		}
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesArray: Could not deconstruct new values array");
		PG_RETURN_NULL();
	}

	/* allocate memory for pixval */
	numpixval = num;
	pixval = palloc(sizeof(struct pixelvalue) * numpixval);
	if (pixval == NULL) {
		pfree(elements);
		pfree(nulls);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesArray: Could not allocate memory for new pixel values");
		PG_RETURN_NULL();
	}

	/* load new values into pixval */
	i = 0;
	for (y = 0; y < dimpixval[0]; y++) {
		for (x = 0; x < dimpixval[1]; x++) {
			/* 0-based */
			pixval[i].x = ul[0] + x;
			pixval[i].y = ul[1] + y;

			pixval[i].noset = FALSE;
			pixval[i].nodata = FALSE;
			pixval[i].value = 0;

			if (nulls[i])
				pixval[i].nodata = TRUE;
			else {
				switch (etype) {
					case FLOAT4OID:
						pixval[i].value = DatumGetFloat4(elements[i]);
						break;
					case FLOAT8OID:
						pixval[i].value = DatumGetFloat8(elements[i]);
						break;
				}
			}

			i++;
		}
	}

	pfree(elements);
	pfree(nulls);

	/* now load noset flags */
	if (!PG_ARGISNULL(5)) {
		array = PG_GETARG_ARRAYTYPE_P(5);
		etype = ARR_ELEMTYPE(array);
		get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign);

		switch (etype) {
			case BOOLOID:
				break;
			default:
				pfree(pixval);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				elog(ERROR, "RASTER_setPixelValuesArray: Invalid data type for noset flags");
				PG_RETURN_NULL();
				break;
		}

		ndims = ARR_NDIM(array);
		dims = ARR_DIMS(array);
		POSTGIS_RT_DEBUGF(4, "ndims = %d", ndims);

		if (ndims < 1 || ndims > 2) {
			elog(NOTICE, "Noset flags array must be of 1 or 2 dimensions.  Returning original raster");
			pfree(pixval);
			rt_raster_destroy(raster);
			PG_RETURN_POINTER(pgraster);
		}
		/* outer element, then inner element */
		/* i = 0, y */
		/* i = 1, x */
		if (ndims != 2)
			dimnoset[1] = dims[0];
		else {
			dimnoset[0] = dims[0];
			dimnoset[1] = dims[1];
		}
		POSTGIS_RT_DEBUGF(4, "dimnoset = (%d, %d)", dimnoset[0], dimnoset[1]);

		deconstruct_array(
			array,
			etype,
			typlen, typbyval, typalign,
			&elements, &nulls, &num
		);

		/* # of elements doesn't match dims */
		if (num < 1 || num != (dimnoset[0] * dimnoset[1])) {
			pfree(pixval);
			if (num) {
				pfree(elements);
				pfree(nulls);
			}
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesArray: Could not deconstruct noset flags array");
			PG_RETURN_NULL();
		}

		i = 0;
		j = 0;
		for (y = 0; y < dimnoset[0]; y++) {
			if (y >= dimpixval[0]) break;

			for (x = 0; x < dimnoset[1]; x++) {
				/* fast forward noset elements */
				if (x >= dimpixval[1]) {
					i += (dimnoset[1] - dimpixval[1]);
					break;
				}

				if (!nulls[i] && DatumGetBool(elements[i]))
					pixval[j].noset = TRUE;

				i++;
				j++;
			}

			/* fast forward pixval */
			if (x < dimpixval[1])
				j += (dimpixval[1] - dimnoset[1]);
		}

		pfree(elements);
		pfree(nulls);
	}
	/* hasnosetvalue and nosetvalue */
	else if (!PG_ARGISNULL(6) && PG_GETARG_BOOL(6)) {
		hasnosetval = TRUE;
		if (PG_ARGISNULL(7))
			nosetvalisnull = TRUE;
		else
			nosetval = PG_GETARG_FLOAT8(7);
	}

#if POSTGIS_DEBUG_LEVEL > 0
	for (i = 0; i < numpixval; i++) {
		POSTGIS_RT_DEBUGF(4, "pixval[%d](x, y, noset, nodata, value) = (%d, %d, %d, %d, %f)",
			i,
			pixval[i].x,
			pixval[i].y,
			pixval[i].noset,
			pixval[i].nodata,
			pixval[i].value
		);
	}
#endif

	/* keepnodata flag */
	if (!PG_ARGISNULL(8))
		keepnodata = PG_GETARG_BOOL(8);

	/* get band */
	band = rt_raster_get_band(raster, nband - 1);
	if (!band) {
		elog(NOTICE, "Could not find band at index %d. Returning original raster", nband);
		pfree(pixval);
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	/* get band nodata info */
	/* has NODATA, use NODATA */
	hasnodata = rt_band_get_hasnodata_flag(band);
	if (hasnodata)
		rt_band_get_nodata(band, &nodataval);
	/* no NODATA, use min possible value */
	else
		nodataval = rt_band_get_min_value(band);

	/* set pixels */
	for (i = 0; i < numpixval; i++) {
		/* noset = true, skip */
		if (pixval[i].noset)
			continue;
		/* check against nosetval */
		else if (hasnosetval) {
			/* pixel = NULL AND nosetval = NULL */
			if (pixval[i].nodata && nosetvalisnull)
				continue;
			/* pixel value = nosetval */
			else if (!pixval[i].nodata && !nosetvalisnull && FLT_EQ(pixval[i].value, nosetval))
				continue;
		}

		/* if pixel is outside bounds, skip */
		if (
			(pixval[i].x < 0 || pixval[i].x >= width) ||
			(pixval[i].y < 0 || pixval[i].y >= height)
		) {
			elog(NOTICE, "Cannot set value for pixel (%d, %d) outside raster bounds: %d x %d",
				pixval[i].x + 1, pixval[i].y + 1,
				width, height
			);
			continue;
		}

		/* if hasnodata = TRUE and keepnodata = TRUE, inspect pixel value */
		if (hasnodata && keepnodata) {
			rtn = rt_band_get_pixel(band, pixval[i].x, pixval[i].y, &val, &isnodata);
			if (rtn != ES_NONE) {
				pfree(pixval);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				elog(ERROR, "Cannot get value of pixel");
				PG_RETURN_NULL();
			}

			/* pixel value = NODATA, skip */
			if (isnodata) {
				continue;
			}
		}

		if (pixval[i].nodata)
			rt_band_set_pixel(band, pixval[i].x, pixval[i].y, nodataval, NULL);
		else
			rt_band_set_pixel(band, pixval[i].x, pixval[i].y, pixval[i].value, NULL);
	}

	pfree(pixval);

	/* serialize new raster */
	pgrtn = rt_raster_serialize(raster);
	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);
	if (!pgrtn)
		PG_RETURN_NULL();

	SET_VARSIZE(pgrtn, pgrtn->size);
	PG_RETURN_POINTER(pgrtn);
}

/* ---------------------------------------------------------------- */
/*  ST_SetValues using geomval array                                */
/* ---------------------------------------------------------------- */

typedef struct rtpg_setvaluesgv_arg_t *rtpg_setvaluesgv_arg;
typedef struct rtpg_setvaluesgv_geomval_t *rtpg_setvaluesgv_geomval;

struct rtpg_setvaluesgv_arg_t {
	int ngv;
	rtpg_setvaluesgv_geomval gv;

	bool keepnodata;
};

struct rtpg_setvaluesgv_geomval_t {
	struct {
		int nodata;
		double value;
	} pixval;

	LWGEOM *geom;
	rt_raster mask;
};

static rtpg_setvaluesgv_arg rtpg_setvaluesgv_arg_init() {
	rtpg_setvaluesgv_arg arg = palloc(sizeof(struct rtpg_setvaluesgv_arg_t));
	if (arg == NULL) {
		elog(ERROR, "rtpg_setvaluesgv_arg_init: Could not allocate memory for function arguments");
		return NULL;
	}

	arg->ngv = 0;
	arg->gv = NULL;
	arg->keepnodata = 0;

	return arg;
}

static void rtpg_setvaluesgv_arg_destroy(rtpg_setvaluesgv_arg arg) {
	int i = 0;

	if (arg->gv != NULL) {
		for (i = 0; i < arg->ngv; i++) {
			if (arg->gv[i].geom != NULL)
				lwgeom_free(arg->gv[i].geom);
			if (arg->gv[i].mask != NULL)
				rt_raster_destroy(arg->gv[i].mask);
		}

		pfree(arg->gv);
	}

	pfree(arg);
}

static int rtpg_setvalues_geomval_callback(
	rt_iterator_arg arg, void *userarg,
	double *value, int *nodata
) {
	rtpg_setvaluesgv_arg funcarg = (rtpg_setvaluesgv_arg) userarg;
	int i = 0;
	int j = 0;

	*value = 0;
	*nodata = 0;

	POSTGIS_RT_DEBUGF(4, "keepnodata = %d", funcarg->keepnodata);

	/* keepnodata = TRUE */
	if (funcarg->keepnodata && arg->nodata[0][0][0]) {
		POSTGIS_RT_DEBUG(3, "keepnodata = 1 AND source raster pixel is NODATA");
		*nodata = 1;
		return 1;
	}

	for (i = arg->rasters - 1, j = funcarg->ngv - 1; i > 0; i--, j--) {
		POSTGIS_RT_DEBUGF(4, "checking raster %d", i);

		/* mask is NODATA */
		if (arg->nodata[i][0][0])
			continue;
		/* mask is NOT NODATA */
		else {
			POSTGIS_RT_DEBUGF(4, "Using information from geometry %d", j);

			if (funcarg->gv[j].pixval.nodata)
				*nodata = 1;
			else
				*value = funcarg->gv[j].pixval.value;

			return 1;
		}
	}

	POSTGIS_RT_DEBUG(4, "Using information from source raster");

	/* still here */
	if (arg->nodata[0][0][0])
		*nodata = 1;
	else
		*value = arg->values[0][0][0];

	return 1;
}

PG_FUNCTION_INFO_V1(RASTER_setPixelValuesGeomval);
Datum RASTER_setPixelValuesGeomval(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_pgraster *pgrtn = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	rt_raster _raster = NULL;
	rt_band _band = NULL;
	int nband = 0; /* 1-based */

	int numbands = 0;
	int width = 0;
	int height = 0;
	int32_t srid = 0;
	double gt[6] = {0};

	rt_pixtype pixtype = PT_END;
	int hasnodata = 0;
	double nodataval = 0;

	rtpg_setvaluesgv_arg arg = NULL;
	int allpoint = 0;

	ArrayType *array;
	Oid etype;
	Datum *e;
	bool *nulls;
	int16 typlen;
	bool typbyval;
	char typalign;
	int n = 0;

	HeapTupleHeader tup;
	bool isnull;
	Datum tupv;

	GSERIALIZED *gser = NULL;
	uint8_t gtype;
	lwvarlena_t *wkb = NULL;

	int i = 0;
	uint32_t j = 0;
	int noerr = 1;

	/* pgraster is null, return null */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
	pgraster = (rt_pgraster *) PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));

	/* raster */
	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesGeomval: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* raster attributes */
	numbands = rt_raster_get_num_bands(raster);
	width = rt_raster_get_width(raster);
	height = rt_raster_get_height(raster);
	srid = clamp_srid(rt_raster_get_srid(raster));
	rt_raster_get_geotransform_matrix(raster, gt);

	/* nband */
	if (PG_ARGISNULL(1)) {
		elog(NOTICE, "Band index cannot be NULL.  Value must be 1-based.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	nband = PG_GETARG_INT32(1);
	if (nband < 1 || nband > numbands) {
		elog(NOTICE, "Band index is invalid.  Value must be 1-based.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	/* get band attributes */
	band = rt_raster_get_band(raster, nband - 1);
	pixtype = rt_band_get_pixtype(band);
	hasnodata = rt_band_get_hasnodata_flag(band);
	if (hasnodata)
		rt_band_get_nodata(band, &nodataval);

	/* array of geomval (2) */
	if (PG_ARGISNULL(2)) {
		elog(NOTICE, "No values to set.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	array = PG_GETARG_ARRAYTYPE_P(2);
	etype = ARR_ELEMTYPE(array);
	get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign);

	deconstruct_array(
		array,
		etype,
		typlen, typbyval, typalign,
		&e, &nulls, &n
	);

	if (!n) {
		elog(NOTICE, "No values to set.  Returning original raster");
		rt_raster_destroy(raster);
		PG_RETURN_POINTER(pgraster);
	}

	/* init arg */
	arg = rtpg_setvaluesgv_arg_init();
	if (arg == NULL) {
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesGeomval: Could not initialize argument structure");
		PG_RETURN_NULL();
	}

	arg->gv = palloc(sizeof(struct rtpg_setvaluesgv_geomval_t) * n);
	if (arg->gv == NULL) {
		rtpg_setvaluesgv_arg_destroy(arg);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_setPixelValuesGeomval: Could not allocate memory for geomval array");
		PG_RETURN_NULL();
	}

	/* process each element */
	arg->ngv = 0;
	for (i = 0; i < n; i++) {
		if (nulls[i])
			continue;

		arg->gv[arg->ngv].pixval.nodata = 0;
		arg->gv[arg->ngv].pixval.value = 0;
		arg->gv[arg->ngv].geom = NULL;
		arg->gv[arg->ngv].mask = NULL;

		/* each element is a tuple */
		tup = (HeapTupleHeader) DatumGetPointer(e[i]);
		if (NULL == tup) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Invalid argument for geomval at index %d", i);
			PG_RETURN_NULL();
		}

		/* first element, geometry */
		POSTGIS_RT_DEBUG(4, "Processing first element (geometry)");
		tupv = GetAttributeByName(tup, "geom", &isnull);
		if (isnull) {
			elog(NOTICE, "First argument (geom) of geomval at index %d is NULL. Skipping", i);
			continue;
		}

		gser = (GSERIALIZED *) PG_DETOAST_DATUM(tupv);
		arg->gv[arg->ngv].geom = lwgeom_from_gserialized(gser);
		if (arg->gv[arg->ngv].geom == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not deserialize geometry of geomval at index %d", i);
			PG_RETURN_NULL();
		}

		/* empty geometry */
		if (lwgeom_is_empty(arg->gv[arg->ngv].geom)) {
			elog(NOTICE, "First argument (geom) of geomval at index %d is an empty geometry. Skipping", i);
			continue;
		}

		/* check SRID */
		if (clamp_srid(gserialized_get_srid(gser)) != srid) {
			elog(NOTICE, "Geometry provided for geomval at index %d does not have the same SRID as the raster: %d. Returning original raster", i, srid);
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_RETURN_POINTER(pgraster);
		}

		/* Get a 2D version of the geometry if necessary */
		if (lwgeom_ndims(arg->gv[arg->ngv].geom) > 2) {
			LWGEOM *geom2d = lwgeom_force_2d(arg->gv[arg->ngv].geom);
			lwgeom_free(arg->gv[arg->ngv].geom);
			arg->gv[arg->ngv].geom = geom2d;
		}

		/* filter for types */
		gtype = gserialized_get_type(gser);

		/* shortcuts for POINT and MULTIPOINT */
		if (gtype == POINTTYPE || gtype == MULTIPOINTTYPE)
			allpoint++;

		/* get wkb of geometry */
		POSTGIS_RT_DEBUG(3, "getting wkb of geometry");
		wkb = lwgeom_to_wkb_varlena(arg->gv[arg->ngv].geom, WKB_SFSQL);

		/* rasterize geometry */
		arg->gv[arg->ngv].mask = rt_raster_gdal_rasterize((unsigned char *)wkb->data,
								  LWSIZE_GET(wkb->size) - LWVARHDRSZ,
								  NULL,
								  0,
								  NULL,
								  NULL,
								  NULL,
								  NULL,
								  NULL,
								  NULL,
								  NULL,
								  &(gt[1]),
								  &(gt[5]),
								  NULL,
								  NULL,
								  &(gt[0]),
								  &(gt[3]),
								  &(gt[2]),
								  &(gt[4]),
								  NULL);

		pfree(wkb);
		if (gtype != POINTTYPE && gtype != MULTIPOINTTYPE) {
			lwgeom_free(arg->gv[arg->ngv].geom);
			arg->gv[arg->ngv].geom = NULL;
		}

		if (arg->gv[arg->ngv].mask == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not rasterize geometry of geomval at index %d", i);
			PG_RETURN_NULL();
		}

		/* set SRID */
		rt_raster_set_srid(arg->gv[arg->ngv].mask, srid);

		/* second element, value */
		POSTGIS_RT_DEBUG(4, "Processing second element (val)");
		tupv = GetAttributeByName(tup, "val", &isnull);
		if (isnull) {
			elog(NOTICE, "Second argument (val) of geomval at index %d is NULL. Treating as NODATA", i);
			arg->gv[arg->ngv].pixval.nodata = 1;
		}
		else
			arg->gv[arg->ngv].pixval.value = DatumGetFloat8(tupv);

		(arg->ngv)++;
	}

	/* redim arg->gv if needed */
	if (arg->ngv < n) {
		arg->gv = repalloc(arg->gv, sizeof(struct rtpg_setvaluesgv_geomval_t) * arg->ngv);
		if (arg->gv == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not reallocate memory for geomval array");
			PG_RETURN_NULL();
		}
	}

	/* keepnodata */
	if (!PG_ARGISNULL(3))
		arg->keepnodata = PG_GETARG_BOOL(3);
	POSTGIS_RT_DEBUGF(3, "keepnodata = %d", arg->keepnodata);

	/* keepnodata = TRUE and band is NODATA */
	if (arg->keepnodata && rt_band_get_isnodata_flag(band)) {
		POSTGIS_RT_DEBUG(3, "keepnodata = TRUE and band is NODATA. Not doing anything");
	}
	/* all elements are points */
	else if (allpoint == arg->ngv) {
		double igt[6] = {0};
		double xy[2] = {0};
		double value = 0;
		int isnodata = 0;

		LWCOLLECTION *coll = NULL;
		LWPOINT *point = NULL;
		POINT2D p;

		POSTGIS_RT_DEBUG(3, "all geometries are points, using direct to pixel method");

		/* cache inverse gretransform matrix */
		rt_raster_get_inverse_geotransform_matrix(NULL, gt, igt);

		/* process each geometry */
		for (i = 0; i < arg->ngv; i++) {
			/* convert geometry to collection */
			coll = lwgeom_as_lwcollection(lwgeom_as_multi(arg->gv[i].geom));

			/* process each point in collection */
			for (j = 0; j < coll->ngeoms; j++) {
				point = lwgeom_as_lwpoint(coll->geoms[j]);
				getPoint2d_p(point->point, 0, &p);

				if (rt_raster_geopoint_to_cell(raster, p.x, p.y, &(xy[0]), &(xy[1]), igt) != ES_NONE) {
					rtpg_setvaluesgv_arg_destroy(arg);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					elog(ERROR, "RASTER_setPixelValuesGeomval: Could not process coordinates of point");
					PG_RETURN_NULL();
				}

				/* skip point if outside raster */
				if (
					(xy[0] < 0 || xy[0] >= width) ||
					(xy[1] < 0 || xy[1] >= height)
				) {
					elog(NOTICE, "Point is outside raster extent. Skipping");
					continue;
				}

				/* get pixel value */
				if (rt_band_get_pixel(band, xy[0], xy[1], &value, &isnodata) != ES_NONE) {
					rtpg_setvaluesgv_arg_destroy(arg);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					elog(ERROR, "RASTER_setPixelValuesGeomval: Could not get pixel value");
					PG_RETURN_NULL();
				}

				/* keepnodata = TRUE AND pixel value is NODATA */
				if (arg->keepnodata && isnodata)
					continue;

				/* set pixel */
				if (arg->gv[i].pixval.nodata)
					noerr = rt_band_set_pixel(band, xy[0], xy[1], nodataval, NULL);
				else
					noerr = rt_band_set_pixel(band, xy[0], xy[1], arg->gv[i].pixval.value, NULL);

				if (noerr != ES_NONE) {
					rtpg_setvaluesgv_arg_destroy(arg);
					rt_raster_destroy(raster);
					PG_FREE_IF_COPY(pgraster, 0);
					elog(ERROR, "RASTER_setPixelValuesGeomval: Could not set pixel value");
					PG_RETURN_NULL();
				}
			}
		}
	}
	/* run iterator otherwise */
	else {
		rt_iterator itrset;

		POSTGIS_RT_DEBUG(3, "a mix of geometries, using iterator method");

		/* init itrset */
		itrset = palloc(sizeof(struct rt_iterator_t) * (arg->ngv + 1));
		if (itrset == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not allocate memory for iterator arguments");
			PG_RETURN_NULL();
		}

		/* set first raster's info */
		itrset[0].raster = raster;
		itrset[0].nband = nband - 1;
		itrset[0].nbnodata = 1;

		/* set other raster's info */
		for (i = 0, j = 1; i < arg->ngv; i++, j++) {
			itrset[j].raster = arg->gv[i].mask;
			itrset[j].nband = 0;
			itrset[j].nbnodata = 1;
		}

		/* pass to iterator */
		noerr = rt_raster_iterator(
			itrset, arg->ngv + 1,
			ET_FIRST, NULL,
			pixtype,
			hasnodata, nodataval,
			0, 0,
			NULL,
			arg,
			rtpg_setvalues_geomval_callback,
			&_raster
		);
		pfree(itrset);

		if (noerr != ES_NONE) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not run raster iterator function");
			PG_RETURN_NULL();
		}

		/* copy band from _raster to raster */
		_band = rt_raster_get_band(_raster, 0);
		if (_band == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(_raster);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not get band from working raster");
			PG_RETURN_NULL();
		}

		_band = rt_raster_replace_band(raster, _band, nband - 1);
		if (_band == NULL) {
			rtpg_setvaluesgv_arg_destroy(arg);
			rt_raster_destroy(_raster);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			elog(ERROR, "RASTER_setPixelValuesGeomval: Could not replace band in output raster");
			PG_RETURN_NULL();
		}

		rt_band_destroy(_band);
		rt_raster_destroy(_raster);
	}

	rtpg_setvaluesgv_arg_destroy(arg);

	pgrtn = rt_raster_serialize(raster);
	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);

	POSTGIS_RT_DEBUG(3, "Finished");

	if (!pgrtn)
		PG_RETURN_NULL();

	SET_VARSIZE(pgrtn, pgrtn->size);
	PG_RETURN_POINTER(pgrtn);
}

#undef VALUES_LENGTH
#define VALUES_LENGTH 3

/**
 * Get pixels of value
 */
PG_FUNCTION_INFO_V1(RASTER_pixelOfValue);
Datum RASTER_pixelOfValue(PG_FUNCTION_ARGS)
{
	FuncCallContext *funcctx;
	TupleDesc tupdesc;

	rt_pixel pixels = NULL;
	rt_pixel pixels2 = NULL;
	int count = 0;
	int i = 0;
	int n = 0;
	int call_cntr;
	int max_calls;

	if (SRF_IS_FIRSTCALL()) {
		MemoryContext oldcontext;

		rt_pgraster *pgraster = NULL;
		rt_raster raster = NULL;
		rt_band band = NULL;
		int nband = 1;
		int num_bands = 0;
		double *search = NULL;
		int nsearch = 0;
		double val;
		bool exclude_nodata_value = TRUE;

		ArrayType *array;
		Oid etype;
		Datum *e;
		bool *nulls;
		int16 typlen;
		bool typbyval;
		char typalign;

		/* create a function context for cross-call persistence */
		funcctx = SRF_FIRSTCALL_INIT();

		/* switch to memory context appropriate for multiple function calls */
		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

		if (PG_ARGISNULL(0)) {
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}
		pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
		raster = rt_raster_deserialize(pgraster, FALSE);
		if (!raster) {
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			elog(ERROR, "RASTER_pixelOfValue: Could not deserialize raster");
			SRF_RETURN_DONE(funcctx);
		}

		/* num_bands */
		num_bands = rt_raster_get_num_bands(raster);
		if (num_bands < 1) {
			elog(NOTICE, "Raster provided has no bands");
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* band index is 1-based */
		if (!PG_ARGISNULL(1))
			nband = PG_GETARG_INT32(1);
		if (nband < 1 || nband > num_bands) {
			elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL");
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* search values */
		array = PG_GETARG_ARRAYTYPE_P(2);
		etype = ARR_ELEMTYPE(array);
		get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign);

		switch (etype) {
			case FLOAT4OID:
			case FLOAT8OID:
				break;
			default:
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				MemoryContextSwitchTo(oldcontext);
				elog(ERROR, "RASTER_pixelOfValue: Invalid data type for pixel values");
				SRF_RETURN_DONE(funcctx);
				break;
		}

		deconstruct_array(array, etype, typlen, typbyval, typalign, &e,
			&nulls, &n);

		search = palloc(sizeof(double) * n);
		for (i = 0, nsearch = 0; i < n; i++) {
			if (nulls[i]) continue;

			switch (etype) {
				case FLOAT4OID:
					val = (double) DatumGetFloat4(e[i]);
					break;
				case FLOAT8OID:
					val = (double) DatumGetFloat8(e[i]);
					break;
			}

			search[nsearch] = val;
			POSTGIS_RT_DEBUGF(3, "search[%d] = %f", nsearch, search[nsearch]);
			nsearch++;
		}

		/* not searching for anything */
		if (nsearch < 1) {
			elog(NOTICE, "No search values provided. Returning NULL");
			pfree(search);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}
		else if (nsearch < n)
			search = repalloc(search, sizeof(double) * nsearch);

		/* exclude_nodata_value flag */
		if (!PG_ARGISNULL(3))
			exclude_nodata_value = PG_GETARG_BOOL(3);

		/* get band */
		band = rt_raster_get_band(raster, nband - 1);
		if (!band) {
			elog(NOTICE, "Could not find band at index %d. Returning NULL", nband);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* get pixels of values */
		count = rt_band_get_pixel_of_value(
			band, exclude_nodata_value,
			search, nsearch,
			&pixels
		);
		pfree(search);
		rt_band_destroy(band);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		if (count < 1) {
			/* error */
			if (count < 0)
				elog(NOTICE, "Could not get the pixels of search values for band at index %d", nband);
			/* no nearest pixel */
			else
				elog(NOTICE, "No pixels of search values found for band at index %d", nband);

			MemoryContextSwitchTo(oldcontext);
			SRF_RETURN_DONE(funcctx);
		}

		/* Store needed information */
		funcctx->user_fctx = pixels;

		/* total number of tuples to be returned */
		funcctx->max_calls = count;

		/* Build a tuple descriptor for our result type */
		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
			ereport(ERROR, (
				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				errmsg(
					"function returning record called in context "
					"that cannot accept type record"
				)
			));
		}

		BlessTupleDesc(tupdesc);
		funcctx->tuple_desc = tupdesc;

		MemoryContextSwitchTo(oldcontext);
	}

	/* stuff done on every call of the function */
	funcctx = SRF_PERCALL_SETUP();

	call_cntr = funcctx->call_cntr;
	max_calls = funcctx->max_calls;
	tupdesc = funcctx->tuple_desc;
	pixels2 = funcctx->user_fctx;

	/* do when there is more left to send */
	if (call_cntr < max_calls) {
		Datum values[VALUES_LENGTH];
		bool nulls[VALUES_LENGTH];
		HeapTuple tuple;
		Datum result;

		memset(nulls, FALSE, sizeof(bool) * VALUES_LENGTH);

		/* 0-based to 1-based */
		pixels2[call_cntr].x += 1;
		pixels2[call_cntr].y += 1;

		values[0] = Float8GetDatum(pixels2[call_cntr].value);
		values[1] = Int32GetDatum(pixels2[call_cntr].x);
		values[2] = Int32GetDatum(pixels2[call_cntr].y);

		/* build a tuple */
		tuple = heap_form_tuple(tupdesc, values, nulls);

		/* make the tuple into a datum */
		result = HeapTupleGetDatum(tuple);

		SRF_RETURN_NEXT(funcctx, result);
	}
	else {
		pfree(pixels2);
		SRF_RETURN_DONE(funcctx);
	}
}

/**
 * Return nearest value to a point
 */
PG_FUNCTION_INFO_V1(RASTER_nearestValue);
Datum RASTER_nearestValue(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	int bandindex = 1;
	int num_bands = 0;
	GSERIALIZED *geom;
	bool exclude_nodata_value = TRUE;
	LWGEOM *lwgeom;
	LWPOINT *point = NULL;
	POINT2D p;

	double x;
	double y;
	int count;
	rt_pixel npixels = NULL;
	double value = 0;
	int hasvalue = 0;
	int isnodata = 0;

	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
	pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_nearestValue: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* band index is 1-based */
	if (!PG_ARGISNULL(1))
		bandindex = PG_GETARG_INT32(1);
	num_bands = rt_raster_get_num_bands(raster);
	if (bandindex < 1 || bandindex > num_bands) {
		elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}

	/* point */
	geom = PG_GETARG_GSERIALIZED_P(2);
	if (gserialized_get_type(geom) != POINTTYPE) {
		elog(NOTICE, "Geometry provided must be a point");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_FREE_IF_COPY(geom, 2);
		PG_RETURN_NULL();
	}

	/* exclude_nodata_value flag */
	if (!PG_ARGISNULL(3))
		exclude_nodata_value = PG_GETARG_BOOL(3);

	/* SRIDs of raster and geometry must match  */
	if (clamp_srid(gserialized_get_srid(geom)) != clamp_srid(rt_raster_get_srid(raster))) {
		elog(NOTICE, "SRIDs of geometry and raster do not match");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_FREE_IF_COPY(geom, 2);
		PG_RETURN_NULL();
	}

	/* get band */
	band = rt_raster_get_band(raster, bandindex - 1);
	if (!band) {
		elog(NOTICE, "Could not find band at index %d. Returning NULL", bandindex);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_FREE_IF_COPY(geom, 2);
		PG_RETURN_NULL();
	}

	/* process geometry */
	lwgeom = lwgeom_from_gserialized(geom);

	if (lwgeom_is_empty(lwgeom)) {
		elog(NOTICE, "Geometry provided cannot be empty");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_FREE_IF_COPY(geom, 2);
		PG_RETURN_NULL();
	}

	/* Get a 2D version of the geometry if necessary */
	if (lwgeom_ndims(lwgeom) > 2) {
		LWGEOM *lwgeom2d = lwgeom_force_2d(lwgeom);
		lwgeom_free(lwgeom);
		lwgeom = lwgeom2d;
	}

	point = lwgeom_as_lwpoint(lwgeom);
	getPoint2d_p(point->point, 0, &p);

	if (rt_raster_geopoint_to_cell(
		raster,
		p.x, p.y,
		&x, &y,
		NULL
	) != ES_NONE) {
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		lwgeom_free(lwgeom);
		PG_FREE_IF_COPY(geom, 2);
		elog(ERROR, "RASTER_nearestValue: Could not compute pixel coordinates from spatial coordinates");
		PG_RETURN_NULL();
	}

	/* get value at point */
	if (
		(x >= 0 && x < rt_raster_get_width(raster)) &&
		(y >= 0 && y < rt_raster_get_height(raster))
	) {
		if (rt_band_get_pixel(band, x, y, &value, &isnodata) != ES_NONE) {
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			lwgeom_free(lwgeom);
			PG_FREE_IF_COPY(geom, 2);
			elog(ERROR, "RASTER_nearestValue: Could not get pixel value for band at index %d", bandindex);
			PG_RETURN_NULL();
		}

		/* value at point, return value */
		if (!exclude_nodata_value || !isnodata) {
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			lwgeom_free(lwgeom);
			PG_FREE_IF_COPY(geom, 2);

			PG_RETURN_FLOAT8(value);
		}
	}

	/* get neighborhood */
	count = rt_band_get_nearest_pixel(
		band,
		x, y,
		0, 0,
		exclude_nodata_value,
		&npixels
	);
	rt_band_destroy(band);
	/* error or no neighbors */
	if (count < 1) {
		/* error */
		if (count < 0)
			elog(NOTICE, "Could not get the nearest value for band at index %d", bandindex);
		/* no nearest pixel */
		else
			elog(NOTICE, "No nearest value found for band at index %d", bandindex);

		lwgeom_free(lwgeom);
		PG_FREE_IF_COPY(geom, 2);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}

	/* more than one nearest value, see which one is closest */
	if (count > 1) {
		int i = 0;
		LWPOLY *poly = NULL;
		double lastdist = -1;
		double dist;

		for (i = 0; i < count; i++) {
			/* convex-hull of pixel */
			poly = rt_raster_pixel_as_polygon(raster, npixels[i].x, npixels[i].y);
			if (!poly) {
				lwgeom_free(lwgeom);
				PG_FREE_IF_COPY(geom, 2);
				rt_raster_destroy(raster);
				PG_FREE_IF_COPY(pgraster, 0);
				elog(ERROR, "RASTER_nearestValue: Could not get polygon of neighboring pixel");
				PG_RETURN_NULL();
			}

			/* distance between convex-hull and point */
			dist = lwgeom_mindistance2d(lwpoly_as_lwgeom(poly), lwgeom);
			if (lastdist < 0 || dist < lastdist) {
				value = npixels[i].value;
				hasvalue = 1;
			}
			lastdist = dist;

			lwpoly_free(poly);
		}
	}
	else {
		value = npixels[0].value;
		hasvalue = 1;
	}

	pfree(npixels);
	lwgeom_free(lwgeom);
	PG_FREE_IF_COPY(geom, 2);
	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);

	if (hasvalue)
		PG_RETURN_FLOAT8(value);
	else
		PG_RETURN_NULL();
}

/**
 * Return the neighborhood around a pixel
 */
PG_FUNCTION_INFO_V1(RASTER_neighborhood);
Datum RASTER_neighborhood(PG_FUNCTION_ARGS)
{
	rt_pgraster *pgraster = NULL;
	rt_raster raster = NULL;
	rt_band band = NULL;
	int bandindex = 1;
	int num_bands = 0;
	int x = 0;
	int y = 0;
	int _x = 0;
	int _y = 0;
	int distance[2] = {0};
	bool exclude_nodata_value = TRUE;
	double pixval;
	int isnodata = 0;

	rt_pixel npixels = NULL;
	int count;
	double **value2D = NULL;
	int **nodata2D = NULL;

	int i = 0;
	int j = 0;
	int k = 0;
	Datum *value1D = NULL;
	bool *nodata1D = NULL;
	int dim[2] = {0};
	int lbound[2] = {1, 1};
	ArrayType *mdArray = NULL;

	int16 typlen;
	bool typbyval;
	char typalign;

	/* pgraster is null, return nothing */
	if (PG_ARGISNULL(0))
		PG_RETURN_NULL();
	pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));

	raster = rt_raster_deserialize(pgraster, FALSE);
	if (!raster) {
		PG_FREE_IF_COPY(pgraster, 0);
		elog(ERROR, "RASTER_neighborhood: Could not deserialize raster");
		PG_RETURN_NULL();
	}

	/* band index is 1-based */
	if (!PG_ARGISNULL(1))
		bandindex = PG_GETARG_INT32(1);
	num_bands = rt_raster_get_num_bands(raster);
	if (bandindex < 1 || bandindex > num_bands) {
		elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}

	/* pixel column, 1-based */
	x = PG_GETARG_INT32(2);
	_x = x - 1;

	/* pixel row, 1-based */
	y = PG_GETARG_INT32(3);
	_y = y - 1;

	/* distance X axis */
	distance[0] = PG_GETARG_INT32(4);
	if (distance[0] < 0) {
		elog(NOTICE, "Invalid value for distancex (must be >= zero). Returning NULL");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}
	distance[0] = (uint16_t) distance[0];

	/* distance Y axis */
	distance[1] = PG_GETARG_INT32(5);
	if (distance[1] < 0) {
		elog(NOTICE, "Invalid value for distancey (must be >= zero). Returning NULL");
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}
	distance[1] = (uint16_t) distance[1];

	/* exclude_nodata_value flag */
	if (!PG_ARGISNULL(6))
		exclude_nodata_value = PG_GETARG_BOOL(6);

	/* get band */
	band = rt_raster_get_band(raster, bandindex - 1);
	if (!band) {
		elog(NOTICE, "Could not find band at index %d. Returning NULL", bandindex);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);
		PG_RETURN_NULL();
	}

	/* get neighborhood */
	count = 0;
	npixels = NULL;
	if (distance[0] > 0 || distance[1] > 0) {
		count = rt_band_get_nearest_pixel(
			band,
			_x, _y,
			distance[0], distance[1],
			exclude_nodata_value,
			&npixels
		);
		/* error */
		if (count < 0) {
			elog(NOTICE, "Could not get the pixel's neighborhood for band at index %d", bandindex);

			rt_band_destroy(band);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);

			PG_RETURN_NULL();
		}
	}

	/* get pixel's value */
	if (
		(_x >= 0 && _x < rt_band_get_width(band)) &&
		(_y >= 0 && _y < rt_band_get_height(band))
	) {
		if (rt_band_get_pixel(
			band,
			_x, _y,
			&pixval,
			&isnodata
		) != ES_NONE) {
			elog(NOTICE, "Could not get the pixel of band at index %d. Returning NULL", bandindex);
			rt_band_destroy(band);
			rt_raster_destroy(raster);
			PG_FREE_IF_COPY(pgraster, 0);
			PG_RETURN_NULL();
		}
	}
	/* outside band extent, set to NODATA */
	else {
		/* has NODATA, use NODATA */
		if (rt_band_get_hasnodata_flag(band))
			rt_band_get_nodata(band, &pixval);
		/* no NODATA, use min possible value */
		else
			pixval = rt_band_get_min_value(band);
		isnodata = 1;
	}
	POSTGIS_RT_DEBUGF(4, "pixval: %f", pixval);


	/* add pixel to neighborhood */
	count++;
	if (count > 1)
		npixels = (rt_pixel) repalloc(npixels, sizeof(struct rt_pixel_t) * count);
	else
		npixels = (rt_pixel) palloc(sizeof(struct rt_pixel_t));
	if (npixels == NULL) {

		rt_band_destroy(band);
		rt_raster_destroy(raster);
		PG_FREE_IF_COPY(pgraster, 0);

		elog(ERROR, "RASTER_neighborhood: Could not reallocate memory for neighborhood");
		PG_RETURN_NULL();
	}
	npixels[count - 1].x = _x;
	npixels[count - 1].y = _y;
	npixels[count - 1].nodata = 1;
	npixels[count - 1].value = pixval;

	/* set NODATA */
	if (!exclude_nodata_value || !isnodata) {
		npixels[count - 1].nodata = 0;
	}

	/* free unnecessary stuff */
	rt_band_destroy(band);
	rt_raster_destroy(raster);
	PG_FREE_IF_COPY(pgraster, 0);

	/* convert set of rt_pixel to 2D array */
	/* dim is passed with element 0 being Y-axis and element 1 being X-axis */
	count = rt_pixel_set_to_array(
		npixels, count, NULL,
		_x, _y,
		distance[0], distance[1],
		&value2D,
		&nodata2D,
		&(dim[1]), &(dim[0])
	);
	pfree(npixels);
	if (count != ES_NONE) {
		elog(NOTICE, "Could not create 2D array of neighborhood");
		PG_RETURN_NULL();
	}

	/* 1D arrays for values and nodata from 2D arrays */
	value1D = palloc(sizeof(Datum) * dim[0] * dim[1]);
	nodata1D = palloc(sizeof(bool) * dim[0] * dim[1]);

	if (value1D == NULL || nodata1D == NULL) {

		for (i = 0; i < dim[0]; i++) {
			pfree(value2D[i]);
			pfree(nodata2D[i]);
		}
		pfree(value2D);
		pfree(nodata2D);

		elog(ERROR, "RASTER_neighborhood: Could not allocate memory for return 2D array");
		PG_RETURN_NULL();
	}

	/* copy values from 2D arrays to 1D arrays */
	k = 0;
	/* Y-axis */
	for (i = 0; i < dim[0]; i++) {
		/* X-axis */
		for (j = 0; j < dim[1]; j++) {
			nodata1D[k] = (bool) nodata2D[i][j];
			if (!nodata1D[k])
				value1D[k] = Float8GetDatum(value2D[i][j]);
			else
				value1D[k] = PointerGetDatum(NULL);

			k++;
		}
	}

	/* no more need for 2D arrays */
	for (i = 0; i < dim[0]; i++) {
		pfree(value2D[i]);
		pfree(nodata2D[i]);
	}
	pfree(value2D);
	pfree(nodata2D);

	/* info about the type of item in the multi-dimensional array (float8). */
	get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign);

	mdArray = construct_md_array(
		value1D, nodata1D,
		2, dim, lbound,
		FLOAT8OID,
		typlen, typbyval, typalign
	);

	pfree(value1D);
	pfree(nodata1D);

	PG_RETURN_ARRAYTYPE_P(mdArray);
}

