From: Bborie Park Date: Fri, 2 Aug 2013 19:51:09 +0000 (+0000) Subject: Split rt_core/rt_api.c and rt_pg/rt_pg.c into smaller files. X-Git-Tag: 2.2.0rc1~1430 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c9e428f82e50f9ba5e2c9bc2b17a68c4449790ed;p=postgis Split rt_core/rt_api.c and rt_pg/rt_pg.c into smaller files. git-svn-id: http://svn.osgeo.org/postgis/trunk@11719 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/NEWS b/NEWS index 5d9ea5bb6..cd08fd79a 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ PostGIS 2.2.0 * Important / Breaking Changes * + - Split raster/rt_core/rt_api.c and raster/rt_pg/rt_pg.c files into + smaller files for ease of long-term maintenance and development + * Deprecated signatures * * New Features * diff --git a/raster/loader/raster2pgsql.h b/raster/loader/raster2pgsql.h index 4ba618625..ad51f8b23 100644 --- a/raster/loader/raster2pgsql.h +++ b/raster/loader/raster2pgsql.h @@ -41,7 +41,10 @@ #include #include -#include "../rt_core/rt_api.h" +#include "librtcore.h" + +#include "../../postgis_config.h" +#include "../raster_config.h" #define CSEQUAL(a,b) (strcmp(a,b)==0) diff --git a/raster/rt_core/Makefile.in b/raster/rt_core/Makefile.in index 941343226..f0f7206d6 100644 --- a/raster/rt_core/Makefile.in +++ b/raster/rt_core/Makefile.in @@ -8,22 +8,38 @@ # ############################################################################# -AR=ar rs - -CC=@CC@ -LIBLWGEOM_LDFLAGS=../../liblwgeom/.libs/liblwgeom.a -LIBLWGEOM_CFLAGS=-I../../liblwgeom -LIBGDAL_CFLAGS=@LIBGDAL_CFLAGS@ -LIBGDAL_LDFLAGS=@LIBGDAL_LDFLAGS@ -GEOS_LDFLAGS=@GEOS_LDFLAGS@ -lgeos_c -PROJ_LDFLAGS=@PROJ_LDFLAGS@ -lproj -LDFLAGS=$(LIBLWGEOM_LDFLAGS) $(LIBGDAL_LDFLAGS) $(PROJ_LDFLAGS) $(GEOS_LDFLAGS) -CFLAGS=@CFLAGS@ @PICFLAGS@ @WARNFLAGS@ $(LIBLWGEOM_CFLAGS) $(LIBGDAL_CFLAGS) @PROJ_CPPFLAGS@ @GEOS_CPPFLAGS@ +AR = ar rs + +CC = @CC@ +LIBLWGEOM_LDFLAGS = ../../liblwgeom/.libs/liblwgeom.a +LIBLWGEOM_CFLAGS = -I../../liblwgeom +LIBGDAL_CFLAGS = @LIBGDAL_CFLAGS@ +LIBGDAL_LDFLAGS = @LIBGDAL_LDFLAGS@ +GEOS_LDFLAGS = @GEOS_LDFLAGS@ -lgeos_c +PROJ_LDFLAGS = @PROJ_LDFLAGS@ -lproj +LDFLAGS = $(LIBLWGEOM_LDFLAGS) $(LIBGDAL_LDFLAGS) $(PROJ_LDFLAGS) $(GEOS_LDFLAGS) +CFLAGS = @CFLAGS@ @PICFLAGS@ @WARNFLAGS@ $(LIBLWGEOM_CFLAGS) $(LIBGDAL_CFLAGS) @PROJ_CPPFLAGS@ @GEOS_CPPFLAGS@ # Standalone RTCORE objects -RT_OBJS=rt_api.o -RT_LIB=librtcore.a -RT_HEADERS=rt_api.h +RT_OBJS = \ + rt_util.o \ + rt_spatial_relationship.o \ + rt_mapalgebra.o \ + rt_geometry.o \ + rt_statistics.o \ + rt_pixel.o \ + rt_warp.o \ + rt_band.o \ + rt_raster.o \ + rt_serialize.o \ + rt_wkb.o \ + rt_context.o + +RT_LIB = librtcore.a +RT_HEADERS = \ + rt_serialize.h \ + librtcore.h \ + librtcore_internal.h all: $(RT_LIB) diff --git a/raster/rt_core/rt_api.h b/raster/rt_core/librtcore.h similarity index 99% rename from raster/rt_core/rt_api.h rename to raster/rt_core/librtcore.h index 1e937e18f..9dd4204b1 100644 --- a/raster/rt_core/rt_api.h +++ b/raster/rt_core/librtcore.h @@ -28,8 +28,36 @@ * */ -#ifndef RT_API_H_INCLUDED -#define RT_API_H_INCLUDED +/** + * @file librtcore.h + * + * This library is the generic raster handling section of PostGIS. The raster + * objects, constructors, destructors, and a set of spatial processing functions + * are implemented here. + * + * The library is designed for use in non-PostGIS applications if necessary. The + * units tests at test/core (and the future loader/dumper programs) are examples + * of non-PostGIS applications using rt_core. + * + * Programs using this library should set up the default memory managers and error + * handlers by implementing an rt_init_allocators() function, which can be as + * a wrapper around the rt_install_default_allocators() function if you want + * no special handling for memory management and error reporting. + * + **/ + +/****************************************************************************** +* Some rules for *.(c|h) files in rt_core +* +* All functions in rt_core that receive a band index parameter +* must be 0-based +* +* Variables and functions internal for a public function should be prefixed +* with _rti_, e.g. _rti_iterator_arg. +******************************************************************************/ + +#ifndef LIBRTCORE_H_INCLUDED +#define LIBRTCORE_H_INCLUDED /* define the systems */ #if defined(__linux__) /* (predefined) */ @@ -90,43 +118,23 @@ #endif #endif +#include /* for printf, sprintf */ #include /* For size_t, srand and rand */ #include /* For C99 int types */ +#include /* for memcpy, strlen, etc */ #include /* for FLT_EPSILON, DBL_EPSILON and float type limits */ #include /* for integer type limits */ -#include -#include "lwgeom_geos.h" #include "liblwgeom.h" #include "gdal_alg.h" #include "gdal_frmts.h" #include "gdal.h" #include "gdalwarper.h" -#include "ogr_api.h" -#include "ogr_srs_api.h" #include "cpl_vsi.h" #include "cpl_conv.h" -#include "../../postgis_config.h" -#include "../raster_config.h" - -/** - * @file rt_api.h - * - * This library is the generic raster handling section of PostGIS. The raster - * objects, constructors, destructors, and a set of spatial processing functions - * are implemented here. - * - * The library is designed for use in non-PostGIS applications if necessary. The - * units tests at test/core (and the future loader/dumper programs) are examples - * of non-PostGIS applications using rt_core. - * - * Programs using this library should set up the default memory managers and error - * handlers by implementing an rt_init_allocators() function, which can be as - * a wrapper around the rt_install_default_allocators() function if you want - * no special handling for memory management and error reporting. - * - **/ +#include "ogr_api.h" +#include "ogr_srs_api.h" /** * Types definitions @@ -2414,4 +2422,4 @@ struct rt_colormap_t { rt_colormap_entry entry; }; -#endif /* RT_API_H_INCLUDED */ +#endif /* LIBRTCORE_H_INCLUDED */ diff --git a/raster/rt_core/librtcore_internal.h b/raster/rt_core/librtcore_internal.h new file mode 100644 index 000000000..e7a2f083a --- /dev/null +++ b/raster/rt_core/librtcore_internal.h @@ -0,0 +1,43 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef LIBRTCORE_INTERNAL_H_INCLUDED +#define LIBRTCORE_INTERNAL_H_INCLUDED + +#include + +#include "../../postgis_config.h" +#include "../raster_config.h" + +#include "lwgeom_geos.h" + +#include "librtcore.h" + +#endif /* LIBRTCORE_INTERNAL_H_INCLUDED */ diff --git a/raster/rt_core/rt_api.c b/raster/rt_core/rt_api.c deleted file mode 100644 index 80fe4cfbc..000000000 --- a/raster/rt_core/rt_api.c +++ /dev/null @@ -1,15248 +0,0 @@ -/* - * $Id$ - * - * WKTRaster - Raster Types for PostGIS - * http://trac.osgeo.org/postgis/wiki/WKTRaster - * - * Copyright (C) 2011-2013 Regents of the University of California - * - * Copyright (C) 2010-2011 Jorge Arevalo - * Copyright (C) 2010-2011 David Zwarg - * Copyright (C) 2009-2011 Pierre Racine - * Copyright (C) 2009-2011 Mateusz Loskot - * Copyright (C) 2008-2009 Sandro Santilli - * - * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include -#include /* for printf (default message handler) */ -#include /* for va_list, va_start etc */ -#include /* for memcpy and strlen */ -#include -#include /* for time */ -#include "rt_api.h" -#include "gdal_vrt.h" - -/****************************************************************************** -* Some rules for *.(c|h) files in rt_core -* -* All functions in rt_core that receive a band index parameter -* must be 0-based -* -* Variables and functions internal for a public function should be prefixed -* with _rti_, e.g. _rti_iterator_arg. -******************************************************************************/ - -/*--- Utilities -------------------------------------------------*/ - -static void -swap_char(uint8_t *a, uint8_t *b) { - uint8_t c = 0; - - assert(NULL != a && NULL != b); - - c = *a; - *a = *b; - *b = c; -} - -static void -flip_endian_16(uint8_t *d) { - assert(NULL != d); - - swap_char(d, d + 1); -} - -static void -flip_endian_32(uint8_t *d) { - assert(NULL != d); - - swap_char(d, d + 3); - swap_char(d + 1, d + 2); -} - -static void -flip_endian_64(uint8_t *d) { - assert(NULL != d); - - swap_char(d + 7, d); - swap_char(d + 6, d + 1); - swap_char(d + 5, d + 2); - swap_char(d + 4, d + 3); -} - -uint8_t -rt_util_clamp_to_1BB(double value) { - return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_1BBMAX); -} - -uint8_t -rt_util_clamp_to_2BUI(double value) { - return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_2BUIMAX); -} - -uint8_t -rt_util_clamp_to_4BUI(double value) { - return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_4BUIMAX); -} - -int8_t -rt_util_clamp_to_8BSI(double value) { - return (int8_t)fmin(fmax((value), SCHAR_MIN), SCHAR_MAX); -} - -uint8_t -rt_util_clamp_to_8BUI(double value) { - return (uint8_t)fmin(fmax((value), 0), UCHAR_MAX); -} - -int16_t -rt_util_clamp_to_16BSI(double value) { - return (int16_t)fmin(fmax((value), SHRT_MIN), SHRT_MAX); -} - -uint16_t -rt_util_clamp_to_16BUI(double value) { - return (uint16_t)fmin(fmax((value), 0), USHRT_MAX); -} - -int32_t -rt_util_clamp_to_32BSI(double value) { - return (int32_t)fmin(fmax((value), INT_MIN), INT_MAX); -} - -uint32_t -rt_util_clamp_to_32BUI(double value) { - return (uint32_t)fmin(fmax((value), 0), UINT_MAX); -} - -float -rt_util_clamp_to_32F(double value) { - return (float)fmin(fmax((value), -FLT_MAX), FLT_MAX); -} - -/* quicksort */ -#define SWAP(x, y) { double t; t = x; x = y; y = t; } -#define ORDER(x, y) if (x > y) SWAP(x, y) - -static double pivot(double *left, double *right) { - double l, m, r, *p; - - l = *left; - m = *(left + (right - left) / 2); - r = *right; - - /* order */ - ORDER(l, m); - ORDER(l, r); - ORDER(m, r); - - /* pivot is higher of two values */ - if (l < m) return m; - if (m < r) return r; - - /* find pivot that isn't left */ - for (p = left + 1; p <= right; ++p) { - if (*p != *left) - return (*p < *left) ? *left : *p; - } - - /* all values are same */ - return -1; -} - -static double *partition(double *left, double *right, double pivot) { - while (left <= right) { - while (*left < pivot) ++left; - while (*right >= pivot) --right; - - if (left < right) { - SWAP(*left, *right); - ++left; - --right; - } - } - - return left; -} - -static void quicksort(double *left, double *right) { - double p = pivot(left, right); - double *pos; - - if (p != -1) { - pos = partition(left, right, p); - quicksort(left, pos - 1); - quicksort(pos, right); - } -} - -/** - * Convert cstring name to GDAL Resample Algorithm - * - * @param algname : cstring name to convert - * - * @return valid GDAL resampling algorithm - */ -GDALResampleAlg -rt_util_gdal_resample_alg(const char *algname) { - assert(algname != NULL && strlen(algname) > 0); - - if (strcmp(algname, "NEARESTNEIGHBOUR") == 0) - return GRA_NearestNeighbour; - else if (strcmp(algname, "NEARESTNEIGHBOR") == 0) - return GRA_NearestNeighbour; - else if (strcmp(algname, "BILINEAR") == 0) - return GRA_Bilinear; - else if (strcmp(algname, "CUBICSPLINE") == 0) - return GRA_CubicSpline; - else if (strcmp(algname, "CUBIC") == 0) - return GRA_Cubic; - else if (strcmp(algname, "LANCZOS") == 0) - return GRA_Lanczos; - - return GRA_NearestNeighbour; -} - -/** - * Convert rt_pixtype to GDALDataType - * - * @param pt : pixeltype to convert - * - * @return valid GDALDataType - */ -GDALDataType -rt_util_pixtype_to_gdal_datatype(rt_pixtype pt) { - switch (pt) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BUI: - return GDT_Byte; - case PT_8BSI: - case PT_16BSI: - return GDT_Int16; - case PT_16BUI: - return GDT_UInt16; - case PT_32BSI: - return GDT_Int32; - case PT_32BUI: - return GDT_UInt32; - case PT_32BF: - return GDT_Float32; - case PT_64BF: - return GDT_Float64; - default: - return GDT_Unknown; - } - - return GDT_Unknown; -} - -/** - * Convert GDALDataType to rt_pixtype - * - * @param gdt : GDAL datatype to convert - * - * @return valid rt_pixtype - */ -rt_pixtype -rt_util_gdal_datatype_to_pixtype(GDALDataType gdt) { - switch (gdt) { - case GDT_Byte: - return PT_8BUI; - case GDT_UInt16: - return PT_16BUI; - case GDT_Int16: - return PT_16BSI; - case GDT_UInt32: - return PT_32BUI; - case GDT_Int32: - return PT_32BSI; - case GDT_Float32: - return PT_32BF; - case GDT_Float64: - return PT_64BF; - default: - return PT_END; - } - - return PT_END; -} - -/* - get GDAL runtime version information -*/ -const char* -rt_util_gdal_version(const char *request) { - if (NULL == request || !strlen(request)) - return GDALVersionInfo("RELEASE_NAME"); - else - return GDALVersionInfo(request); -} - -/* - computed extent type -*/ -rt_extenttype -rt_util_extent_type(const char *name) { - assert(name != NULL && strlen(name) > 0); - - if (strcmp(name, "UNION") == 0) - return ET_UNION; - else if (strcmp(name, "FIRST") == 0) - return ET_FIRST; - else if (strcmp(name, "SECOND") == 0) - return ET_SECOND; - else if (strcmp(name, "LAST") == 0) - return ET_LAST; - else if (strcmp(name, "CUSTOM") == 0) - return ET_CUSTOM; - else - return ET_INTERSECTION; -} - -/* - convert the spatial reference string from a GDAL recognized format to either WKT or Proj4 -*/ -char* -rt_util_gdal_convert_sr(const char *srs, int proj4) { - OGRSpatialReferenceH hsrs; - char *rtn = NULL; - - assert(srs != NULL); - - hsrs = OSRNewSpatialReference(NULL); - if (OSRSetFromUserInput(hsrs, srs) == OGRERR_NONE) { - if (proj4) - OSRExportToProj4(hsrs, &rtn); - else - OSRExportToWkt(hsrs, &rtn); - } - else { - rterror("rt_util_gdal_convert_sr: Could not process the provided srs: %s", srs); - return NULL; - } - - OSRDestroySpatialReference(hsrs); - if (rtn == NULL) { - rterror("rt_util_gdal_convert_sr: Could not process the provided srs: %s", srs); - return NULL; - } - - return rtn; -} - -/* - is the spatial reference string supported by GDAL -*/ -int -rt_util_gdal_supported_sr(const char *srs) { - OGRSpatialReferenceH hsrs; - OGRErr rtn = OGRERR_NONE; - - assert(srs != NULL); - - hsrs = OSRNewSpatialReference(NULL); - rtn = OSRSetFromUserInput(hsrs, srs); - OSRDestroySpatialReference(hsrs); - - if (rtn == OGRERR_NONE) - return 1; - else - return 0; -} - -/** - * Get auth name and code - * - * @param authname: authority organization of code. calling function - * is expected to free the memory allocated for value - * @param authcode: code assigned by authority organization. calling function - * is expected to free the memory allocated for value - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate -rt_util_gdal_sr_auth_info(GDALDatasetH hds, char **authname, char **authcode) { - const char *srs = NULL; - - assert(authname != NULL); - assert(authcode != NULL); - - *authname = NULL; - *authcode = NULL; - - srs = GDALGetProjectionRef(hds); - if (srs != NULL && srs[0] != '\0') { - OGRSpatialReferenceH hSRS = OSRNewSpatialReference(NULL); - - if (OSRSetFromUserInput(hSRS, srs) == OGRERR_NONE) { - const char* pszAuthorityName = OSRGetAuthorityName(hSRS, NULL); - const char* pszAuthorityCode = OSRGetAuthorityCode(hSRS, NULL); - - if (pszAuthorityName != NULL && pszAuthorityCode != NULL) { - *authname = rtalloc(sizeof(char) * (strlen(pszAuthorityName) + 1)); - *authcode = rtalloc(sizeof(char) * (strlen(pszAuthorityCode) + 1)); - - if (*authname == NULL || *authcode == NULL) { - rterror("rt_util_gdal_sr_auth_info: Could not allocate memory for auth name and code"); - if (*authname != NULL) rtdealloc(*authname); - if (*authcode != NULL) rtdealloc(*authcode); - OSRDestroySpatialReference(hSRS); - return ES_ERROR; - } - - strncpy(*authname, pszAuthorityName, strlen(pszAuthorityName) + 1); - strncpy(*authcode, pszAuthorityCode, strlen(pszAuthorityCode) + 1); - } - } - - OSRDestroySpatialReference(hSRS); - } - - return ES_NONE; -} - -/* - is GDAL configured correctly? -*/ -int rt_util_gdal_configured(void) { - - /* set of EPSG codes */ - if (!rt_util_gdal_supported_sr("EPSG:4326")) - return 0; - if (!rt_util_gdal_supported_sr("EPSG:4269")) - return 0; - if (!rt_util_gdal_supported_sr("EPSG:4267")) - return 0; - if (!rt_util_gdal_supported_sr("EPSG:3310")) - return 0; - if (!rt_util_gdal_supported_sr("EPSG:2163")) - return 0; - - return 1; -} - -/* - register all GDAL drivers -*/ -void -rt_util_gdal_register_all(void) { - static int registered = 0; - - if (registered) - return; - - GDALAllRegister(); - registered = 1; -} - -/* - is the driver registered? -*/ -int -rt_util_gdal_driver_registered(const char *drv) { - int count = GDALGetDriverCount(); - int i = 0; - GDALDriverH hdrv = NULL; - - if (drv == NULL || !strlen(drv) || count < 1) - return 0; - - for (i = 0; i < count; i++) { - hdrv = GDALGetDriver(i); - if (hdrv == NULL) continue; - - if (strcmp(drv, GDALGetDriverShortName(hdrv)) == 0) - return 1; - } - - return 0; -} - -void -rt_util_from_ogr_envelope( - OGREnvelope env, - rt_envelope *ext -) { - assert(ext != NULL); - - ext->MinX = env.MinX; - ext->MaxX = env.MaxX; - ext->MinY = env.MinY; - ext->MaxY = env.MaxY; - - ext->UpperLeftX = env.MinX; - ext->UpperLeftY = env.MaxY; -} - -void -rt_util_to_ogr_envelope( - rt_envelope ext, - OGREnvelope *env -) { - assert(env != NULL); - - env->MinX = ext.MinX; - env->MaxX = ext.MaxX; - env->MinY = ext.MinY; - env->MaxY = ext.MaxY; -} - -LWPOLY * -rt_util_envelope_to_lwpoly( - rt_envelope env -) { - LWPOLY *npoly = NULL; - POINTARRAY **rings = NULL; - POINTARRAY *pts = NULL; - POINT4D p4d; - - rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); - if (!rings) { - rterror("rt_util_envelope_to_lwpoly: Out of memory building envelope's geometry"); - return NULL; - } - rings[0] = ptarray_construct(0, 0, 5); - if (!rings[0]) { - rterror("rt_util_envelope_to_lwpoly: Out of memory building envelope's geometry ring"); - return NULL; - } - - pts = rings[0]; - - /* Upper-left corner (first and last points) */ - p4d.x = env.MinX; - p4d.y = env.MaxY; - ptarray_set_point4d(pts, 0, &p4d); - ptarray_set_point4d(pts, 4, &p4d); - - /* Upper-right corner (we go clockwise) */ - p4d.x = env.MaxX; - p4d.y = env.MaxY; - ptarray_set_point4d(pts, 1, &p4d); - - /* Lower-right corner */ - p4d.x = env.MaxX; - p4d.y = env.MinY; - ptarray_set_point4d(pts, 2, &p4d); - - /* Lower-left corner */ - p4d.x = env.MinX; - p4d.y = env.MinY; - ptarray_set_point4d(pts, 3, &p4d); - - npoly = lwpoly_construct(SRID_UNKNOWN, 0, 1, rings); - if (npoly == NULL) { - rterror("rt_util_envelope_to_lwpoly: Could not build envelope's geometry"); - return NULL; - } - - return npoly; -} - -int -rt_util_same_geotransform_matrix(double *gt1, double *gt2) { - int k = 0; - - if (gt1 == NULL || gt2 == NULL) - return FALSE; - - for (k = 0; k < 6; k++) { - if (FLT_NEQ(gt1[k], gt2[k])) - return FALSE; - } - - return TRUE; -} - -/* coordinates in RGB and HSV are floating point values between 0 and 1 */ -rt_errorstate -rt_util_rgb_to_hsv(double rgb[3], double hsv[3]) { - int i; - - double minc; - double maxc; - - double h = 0.; - double s = 0.; - double v = 0.; - - minc = rgb[0]; - maxc = rgb[0]; - - /* get min and max values from RGB */ - for (i = 1; i < 3; i++) { - if (rgb[i] > maxc) - maxc = rgb[i]; - if (rgb[i] < minc) - minc = rgb[i]; - } - v = maxc; - - if (maxc != minc) { - double diff = 0.; - double rc = 0.; - double gc = 0.; - double bc = 0.; - double junk = 0.; - - diff = maxc - minc; - s = diff / maxc; - rc = (maxc - rgb[0]) / diff; - gc = (maxc - rgb[1]) / diff; - bc = (maxc - rgb[2]) / diff; - - if (DBL_EQ(rgb[0], maxc)) - h = bc - gc; - else if (DBL_EQ(rgb[1], maxc)) - h = 2.0 + rc - bc; - else - h = 4.0 + gc - rc; - - h = modf((h / 6.0), &junk); - } - - hsv[0] = h; - hsv[1] = s; - hsv[2] = v; - - return ES_NONE; -} - -/* coordinates in RGB and HSV are floating point values between 0 and 1 */ -rt_errorstate -rt_util_hsv_to_rgb(double hsv[3], double rgb[3]) { - double r = 0; - double g = 0; - double b = 0; - double v = hsv[2]; - - if (DBL_EQ(hsv[1], 0.)) - r = g = b = v; - else { - double i; - double f; - double p; - double q; - double t; - - int a; - - i = floor(hsv[0] * 6.); - f = (hsv[0] * 6.0) - i; - p = v * (1. - hsv[1]); - q = v * (1. - hsv[1] * f); - t = v * (1. - hsv[1] * (1. - f)); - - a = (int) i; - switch (a) { - case 1: - r = q; - g = v; - b = p; - break; - case 2: - r = p; - g = v; - b = t; - break; - case 3: - r = p; - g = q; - b = v; - break; - case 4: - r = t; - g = p; - b = v; - break; - case 5: - r = v; - g = p; - b = q; - break; - case 0: - case 6: - default: - r = v; - g = t; - b = p; - break; - } - } - - rgb[0] = r; - rgb[1] = g; - rgb[2] = b; - - return ES_NONE; -} - -/*- rt_context -------------------------------------------------------*/ - -/* Functions definitions */ -void * init_rt_allocator(size_t size); -void * init_rt_reallocator(void * mem, size_t size); -void init_rt_deallocator(void * mem); -void init_rt_errorreporter(const char * fmt, va_list ap); -void init_rt_warnreporter(const char * fmt, va_list ap); -void init_rt_inforeporter(const char * fmt, va_list ap); - -/* - * Default allocators - * - * We include some default allocators that use malloc/free/realloc - * along with stdout/stderr since this is the most common use case - * - */ -void * -default_rt_allocator(size_t size) -{ - void *mem = malloc(size); - return mem; -} - -void * -default_rt_reallocator(void *mem, size_t size) -{ - void *ret = realloc(mem, size); - return ret; -} - -void -default_rt_deallocator(void *mem) -{ - free(mem); -} - -void -default_rt_error_handler(const char *fmt, va_list ap) { - - static const char *label = "ERROR: "; - char newfmt[1024] = {0}; - snprintf(newfmt, 1024, "%s%s\n", label, fmt); - newfmt[1023] = '\0'; - - vprintf(newfmt, ap); - - va_end(ap); -} - -void -default_rt_warning_handler(const char *fmt, va_list ap) { - - static const char *label = "WARNING: "; - char newfmt[1024] = {0}; - snprintf(newfmt, 1024, "%s%s\n", label, fmt); - newfmt[1023] = '\0'; - - vprintf(newfmt, ap); - - va_end(ap); -} - -void -default_rt_info_handler(const char *fmt, va_list ap) { - - static const char *label = "INFO: "; - char newfmt[1024] = {0}; - snprintf(newfmt, 1024, "%s%s\n", label, fmt); - newfmt[1023] = '\0'; - - vprintf(newfmt, ap); - - va_end(ap); -} - -/** - * Struct definition here - */ -struct rt_context_t { - rt_allocator alloc; - rt_reallocator realloc; - rt_deallocator dealloc; - rt_message_handler err; - rt_message_handler warn; - rt_message_handler info; -}; - -/* Static variable, to be used for all rt_core functions */ -static struct rt_context_t ctx_t = { - .alloc = init_rt_allocator, - .realloc = init_rt_reallocator, - .dealloc = init_rt_deallocator, - .err = init_rt_errorreporter, - .warn = init_rt_warnreporter, - .info = init_rt_inforeporter -}; - - -/** - * This function is normally called by rt_init_allocators when no special memory - * management is needed. Useful in raster core testing and in the (future) - * loader, when we need to use raster core functions but we don't have - * PostgreSQL backend behind. We must take care of memory by ourselves in those - * situations - */ -void -rt_install_default_allocators(void) -{ - ctx_t.alloc = default_rt_allocator; - ctx_t.realloc = default_rt_reallocator; - ctx_t.dealloc = default_rt_deallocator; - ctx_t.err = default_rt_error_handler; - ctx_t.info = default_rt_info_handler; - ctx_t.warn = default_rt_warning_handler; -} - - -/** - * This function is called by rt_init_allocators when the PostgreSQL backend is - * taking care of the memory and we want to use palloc family - */ -void -rt_set_handlers(rt_allocator allocator, rt_reallocator reallocator, - rt_deallocator deallocator, rt_message_handler error_handler, - rt_message_handler info_handler, rt_message_handler warning_handler) { - - ctx_t.alloc = allocator; - ctx_t.realloc = reallocator; - ctx_t.dealloc = deallocator; - - ctx_t.err = error_handler; - ctx_t.info = info_handler; - ctx_t.warn = warning_handler; -} - - - -/** - * Initialisation allocators - * - * These are used the first time any of the allocators are called to enable - * executables/libraries that link into raster to be able to set up their own - * allocators. This is mainly useful for older PostgreSQL versions that don't - * have functions that are called upon startup. - **/ -void * -init_rt_allocator(size_t size) -{ - rt_init_allocators(); - - return ctx_t.alloc(size); -} - -void -init_rt_deallocator(void *mem) -{ - rt_init_allocators(); - - ctx_t.dealloc(mem); -} - - -void * -init_rt_reallocator(void *mem, size_t size) -{ - rt_init_allocators(); - - return ctx_t.realloc(mem, size); -} - -void -init_rt_inforeporter(const char *fmt, va_list ap) -{ - rt_init_allocators(); - - (*ctx_t.info)(fmt, ap); -} - -void -init_rt_warnreporter(const char *fmt, va_list ap) -{ - rt_init_allocators(); - - (*ctx_t.warn)(fmt, ap); -} - - -void -init_rt_errorreporter(const char *fmt, va_list ap) -{ - rt_init_allocators(); - - (*ctx_t.err)(fmt, ap); - -} - - - -/** - * Raster core memory management functions. - * - * They use the functions defined by the caller. - */ -void * -rtalloc(size_t size) { - void * mem = ctx_t.alloc(size); - RASTER_DEBUGF(5, "rtalloc called: %d@%p", size, mem); - return mem; -} - - -void * -rtrealloc(void * mem, size_t size) { - void * result = ctx_t.realloc(mem, size); - RASTER_DEBUGF(5, "rtrealloc called: %d@%p", size, result); - return result; -} - -void -rtdealloc(void * mem) { - ctx_t.dealloc(mem); - RASTER_DEBUG(5, "rtdealloc called"); -} - -/** - * Raster core error and info handlers - * - * Since variadic functions cannot pass their parameters directly, we need - * wrappers for these functions to convert the arguments into a va_list - * structure. - */ -void -rterror(const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - - /* Call the supplied function */ - (*ctx_t.err)(fmt, ap); - - va_end(ap); -} - -void -rtinfo(const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - - /* Call the supplied function */ - (*ctx_t.info)(fmt, ap); - - va_end(ap); -} - - -void -rtwarn(const char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - - /* Call the supplied function */ - (*ctx_t.warn)(fmt, ap); - - va_end(ap); -} - - - -int -rt_util_dbl_trunc_warning( - double initialvalue, - int32_t checkvalint, uint32_t checkvaluint, - float checkvalfloat, double checkvaldouble, - rt_pixtype pixtype -) { - int result = 0; - - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BSI: - case PT_8BUI: - case PT_16BSI: - case PT_16BUI: - case PT_32BSI: { - if (fabs(checkvalint - initialvalue) >= 1) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got clamped from %f to %d", - rt_pixtype_name(pixtype), - initialvalue, checkvalint - ); -#endif - result = 1; - } - else if (FLT_NEQ(checkvalint, initialvalue)) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got truncated from %f to %d", - rt_pixtype_name(pixtype), - initialvalue, checkvalint - ); -#endif - result = 1; - } - break; - } - case PT_32BUI: { - if (fabs(checkvaluint - initialvalue) >= 1) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got clamped from %f to %u", - rt_pixtype_name(pixtype), - initialvalue, checkvaluint - ); -#endif - result = 1; - } - else if (FLT_NEQ(checkvaluint, initialvalue)) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got truncated from %f to %u", - rt_pixtype_name(pixtype), - initialvalue, checkvaluint - ); -#endif - result = 1; - } - break; - } - case PT_32BF: { - /* - For float, because the initial value is a double, - there is very often a difference between the desired value and the obtained one - */ - if (FLT_NEQ(checkvalfloat, initialvalue)) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got converted from %f to %f", - rt_pixtype_name(pixtype), - initialvalue, checkvalfloat - ); -#endif - result = 1; - } - break; - } - case PT_64BF: { - if (FLT_NEQ(checkvaldouble, initialvalue)) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value set for %s band got converted from %f to %f", - rt_pixtype_name(pixtype), - initialvalue, checkvaldouble - ); -#endif - result = 1; - } - break; - } - case PT_END: - break; - } - - return result; -} - -/*--- Debug and Testing Utilities --------------------------------------------*/ - -#if POSTGIS_DEBUG_LEVEL > 2 - -static char* -d_binary_to_hex(const uint8_t * const raw, uint32_t size, uint32_t *hexsize) { - char* hex = NULL; - uint32_t i = 0; - - - assert(NULL != raw); - assert(NULL != hexsize); - - - *hexsize = size * 2; /* hex is 2 times bytes */ - hex = (char*) rtalloc((*hexsize) + 1); - if (!hex) { - rterror("d_binary_to_hex: Out of memory hexifying raw binary"); - return NULL; - } - hex[*hexsize] = '\0'; /* Null-terminate */ - - for (i = 0; i < size; ++i) { - deparse_hex(raw[i], &(hex[2 * i])); - } - - assert(NULL != hex); - assert(0 == strlen(hex) % 2); - return hex; -} - -static void -d_print_binary_hex(const char* msg, const uint8_t * const raw, uint32_t size) { - char* hex = NULL; - uint32_t hexsize = 0; - - - assert(NULL != msg); - assert(NULL != raw); - - - hex = d_binary_to_hex(raw, size, &hexsize); - if (NULL != hex) { - rtinfo("%s\t%s", msg, hex); - rtdealloc(hex); - } -} - -static size_t -d_binptr_to_pos(const uint8_t * const ptr, const uint8_t * const end, size_t size) { - assert(NULL != ptr && NULL != end); - - return (size - (end - ptr)); -} - -#define CHECK_BINPTR_POSITION(ptr, end, size, pos) \ - { if (pos != d_binptr_to_pos(ptr, end, size)) { \ - fprintf(stderr, "Check of binary stream pointer position failed on line %d\n", __LINE__); \ - fprintf(stderr, "\tactual = %lu, expected = %lu\n", (long unsigned)d_binptr_to_pos(ptr, end, size), (long unsigned)pos); \ - } } - -#else - -#define CHECK_BINPTR_POSITION(ptr, end, size, pos) ((void)0); - -#endif /* ifndef POSTGIS_DEBUG_LEVEL > 3 */ - -/*- rt_pixeltype -----------------------------------------------------*/ - -int -rt_pixtype_size(rt_pixtype pixtype) { - int pixbytes = -1; - - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BSI: - case PT_8BUI: - pixbytes = 1; - break; - case PT_16BSI: - case PT_16BUI: - pixbytes = 2; - break; - case PT_32BSI: - case PT_32BUI: - case PT_32BF: - pixbytes = 4; - break; - case PT_64BF: - pixbytes = 8; - break; - default: - rterror("rt_pixtype_size: Unknown pixeltype %d", pixtype); - pixbytes = -1; - break; - } - - RASTER_DEBUGF(3, "Pixel type = %s and size = %d bytes", - rt_pixtype_name(pixtype), pixbytes); - - return pixbytes; -} - -int -rt_pixtype_alignment(rt_pixtype pixtype) { - return rt_pixtype_size(pixtype); -} - -rt_pixtype -rt_pixtype_index_from_name(const char* pixname) { - assert(pixname && strlen(pixname) > 0); - - if (strcmp(pixname, "1BB") == 0) - return PT_1BB; - else if (strcmp(pixname, "2BUI") == 0) - return PT_2BUI; - else if (strcmp(pixname, "4BUI") == 0) - return PT_4BUI; - else if (strcmp(pixname, "8BSI") == 0) - return PT_8BSI; - else if (strcmp(pixname, "8BUI") == 0) - return PT_8BUI; - else if (strcmp(pixname, "16BSI") == 0) - return PT_16BSI; - else if (strcmp(pixname, "16BUI") == 0) - return PT_16BUI; - else if (strcmp(pixname, "32BSI") == 0) - return PT_32BSI; - else if (strcmp(pixname, "32BUI") == 0) - return PT_32BUI; - else if (strcmp(pixname, "32BF") == 0) - return PT_32BF; - else if (strcmp(pixname, "64BF") == 0) - return PT_64BF; - - return PT_END; -} - -const char* -rt_pixtype_name(rt_pixtype pixtype) { - switch (pixtype) { - case PT_1BB: - return "1BB"; - case PT_2BUI: - return "2BUI"; - case PT_4BUI: - return "4BUI"; - case PT_8BSI: - return "8BSI"; - case PT_8BUI: - return "8BUI"; - case PT_16BSI: - return "16BSI"; - case PT_16BUI: - return "16BUI"; - case PT_32BSI: - return "32BSI"; - case PT_32BUI: - return "32BUI"; - case PT_32BF: - return "32BF"; - case PT_64BF: - return "64BF"; - default: - rterror("rt_pixtype_name: Unknown pixeltype %d", pixtype); - return "Unknown"; - } -} - -/** - * Return minimum value possible for pixel type - * - * @param pixtype : the pixel type to get minimum possible value for - * - * @return the minimum possible value for the pixel type. - */ -double -rt_pixtype_get_min_value(rt_pixtype pixtype) { - switch (pixtype) { - case PT_1BB: { - return (double) rt_util_clamp_to_1BB((double) CHAR_MIN); - } - case PT_2BUI: { - return (double) rt_util_clamp_to_2BUI((double) CHAR_MIN); - } - case PT_4BUI: { - return (double) rt_util_clamp_to_4BUI((double) CHAR_MIN); - } - case PT_8BUI: { - return (double) rt_util_clamp_to_8BUI((double) CHAR_MIN); - } - case PT_8BSI: { - return (double) rt_util_clamp_to_8BSI((double) SCHAR_MIN); - } - case PT_16BSI: { - return (double) rt_util_clamp_to_16BSI((double) SHRT_MIN); - } - case PT_16BUI: { - return (double) rt_util_clamp_to_16BUI((double) SHRT_MIN); - } - case PT_32BSI: { - return (double) rt_util_clamp_to_32BSI((double) INT_MIN); - } - case PT_32BUI: { - return (double) rt_util_clamp_to_32BUI((double) INT_MIN); - } - case PT_32BF: { - return (double) -FLT_MAX; - } - case PT_64BF: { - return (double) -DBL_MAX; - } - default: { - rterror("rt_pixtype_get_min_value: Unknown pixeltype %d", pixtype); - return (double) rt_util_clamp_to_8BUI((double) CHAR_MIN); - } - } -} - -/** - * Returns 1 if clamped values are equal, 0 if not equal, -1 if error - * - * @param pixtype : the pixel type to clamp the provided values - * @param val : value to compare to reference value - * @param refval : reference value to be compared with - * @param isequal : non-zero if clamped values are equal, 0 otherwise - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate rt_pixtype_compare_clamped_values( - rt_pixtype pixtype, - double val, double refval, - int *isequal -) { - assert(isequal != NULL); - *isequal = 0; - - switch (pixtype) { - case PT_1BB: - if (rt_util_clamp_to_1BB(val) == rt_util_clamp_to_1BB(refval)) - *isequal = 1; - break; - case PT_2BUI: - if (rt_util_clamp_to_2BUI(val) == rt_util_clamp_to_2BUI(refval)) - *isequal = 1; - break; - case PT_4BUI: - if (rt_util_clamp_to_4BUI(val) == rt_util_clamp_to_4BUI(refval)) - *isequal = 1; - break; - case PT_8BSI: - if (rt_util_clamp_to_8BSI(val) == rt_util_clamp_to_8BSI(refval)) - *isequal = 1; - break; - case PT_8BUI: - if (rt_util_clamp_to_8BUI(val) == rt_util_clamp_to_8BUI(refval)) - *isequal = 1; - break; - case PT_16BSI: - if (rt_util_clamp_to_16BSI(val) == rt_util_clamp_to_16BSI(refval)) - *isequal = 1; - break; - case PT_16BUI: - if (rt_util_clamp_to_16BUI(val) == rt_util_clamp_to_16BUI(refval)) - *isequal = 1; - break; - case PT_32BSI: - if (rt_util_clamp_to_32BSI(val) == rt_util_clamp_to_32BSI(refval)) - *isequal = 1; - break; - case PT_32BUI: - if (rt_util_clamp_to_32BUI(val) == rt_util_clamp_to_32BUI(refval)) - *isequal = 1; - break; - case PT_32BF: - if (FLT_EQ(rt_util_clamp_to_32F(val), rt_util_clamp_to_32F(refval))) - *isequal = 1; - break; - case PT_64BF: - if (FLT_EQ(val, refval)) - *isequal = 1; - break; - default: - rterror("rt_pixtype_compare_clamped_values: Unknown pixeltype %d", pixtype); - return ES_ERROR; - } - - return ES_NONE; -} - -/*- rt_pixel ----------------------------------------------------------*/ - -/* - * Convert an array of rt_pixel objects to two 2D arrays of value and NODATA. - * The dimensions of the returned 2D array are [Y][X], going by row Y and - * then column X. - * - * @param npixel : array of rt_pixel objects - * @param count : number of elements in npixel - * @param x : the column of the center pixel (0-based) - * @param y : the line of the center pixel (0-based) - * @param distancex : the number of pixels around the specified pixel - * along the X axis - * @param distancey : the number of pixels around the specified pixel - * along the Y axis - * @param value : pointer to pointer for 2D value array - * @param nodata : pointer to pointer for 2D NODATA array - * @param dimx : size of value and nodata along the X axis - * @param dimy : size of value and nodata along the Y axis - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate rt_pixel_set_to_array( - rt_pixel npixel, int count, - int x, int y, - uint16_t distancex, uint16_t distancey, - double ***value, - int ***nodata, - int *dimx, int *dimy -) { - uint32_t i; - uint32_t j; - uint32_t dim[2] = {0}; - double **values = NULL; - int **nodatas = NULL; - int zero[2] = {0}; - int _x; - int _y; - - assert(npixel != NULL && count > 0); - assert(value != NULL); - assert(nodata != NULL); - - /* dimensions */ - dim[0] = distancex * 2 + 1; - dim[1] = distancey * 2 + 1; - RASTER_DEBUGF(4, "dimensions = %d x %d", dim[0], dim[1]); - - /* establish 2D arrays (Y axis) */ - values = rtalloc(sizeof(double *) * dim[1]); - nodatas = rtalloc(sizeof(int *) * dim[1]); - - if (values == NULL || nodatas == NULL) { - rterror("rt_pixel_set_to_array: Could not allocate memory for 2D array"); - return ES_ERROR; - } - - /* initialize X axis */ - for (i = 0; i < dim[1]; i++) { - values[i] = rtalloc(sizeof(double) * dim[0]); - nodatas[i] = rtalloc(sizeof(int) * dim[0]); - - if (values[i] == NULL || nodatas[i] == NULL) { - rterror("rt_pixel_set_to_array: Could not allocate memory for dimension of 2D array"); - - if (values[i] == NULL) { - for (j = 0; j < i; j++) { - rtdealloc(values[j]); - rtdealloc(nodatas[j]); - } - } - else { - for (j = 0; j <= i; j++) { - rtdealloc(values[j]); - if (j < i) - rtdealloc(nodatas[j]); - } - } - - rtdealloc(values); - rtdealloc(nodatas); - - return ES_ERROR; - } - - /* set values to 0 */ - memset(values[i], 0, sizeof(double) * dim[0]); - - /* set nodatas to 1 */ - for (j = 0; j < dim[0]; j++) - nodatas[i][j] = 1; - } - - /* get 0,0 of grid */ - zero[0] = x - distancex; - zero[1] = y - distancey; - - /* populate 2D arrays */ - for (i = 0; i < count; i++) { - if (npixel[i].nodata) - continue; - - _x = npixel[i].x - zero[0]; - _y = npixel[i].y - zero[1]; - - RASTER_DEBUGF(4, "absolute x,y: %d x %d", npixel[i].x, npixel[i].y); - RASTER_DEBUGF(4, "relative x,y: %d x %d", _x, _y); - - values[_y][_x] = npixel[i].value; - nodatas[_y][_x] = 0; - - RASTER_DEBUGF(4, "(x, y, nodata, value) = (%d, %d, %d, %f)", _x, _y, nodatas[_y][_x], values[_y][_x]); - } - - *value = &(*values); - *nodata = &(*nodatas); - if (dimx != NULL) - *dimx = dim[0]; - if (dimy != NULL) - *dimy = dim[1]; - - return ES_NONE; -} - -/*- rt_band ----------------------------------------------------------*/ - -/** - * Create an in-db rt_band with no data - * - * @param width : number of pixel columns - * @param height : number of pixel rows - * @param pixtype : pixel type for the band - * @param hasnodata : indicates if the band has nodata value - * @param nodataval : the nodata value, will be appropriately - * truncated to fit the pixtype size. - * @param data : pointer to actual band data, required to - * be aligned accordingly to - * rt_pixtype_aligment(pixtype) and big enough - * to hold raster width*height values. - * Data will NOT be copied, ownership is left - * to caller which is responsible to keep it - * allocated for the whole lifetime of the returned - * rt_band. - * - * @return an rt_band, or 0 on failure - */ -rt_band -rt_band_new_inline( - uint16_t width, uint16_t height, - rt_pixtype pixtype, - uint32_t hasnodata, double nodataval, - uint8_t* data -) { - rt_band band = NULL; - - assert(NULL != data); - - band = rtalloc(sizeof(struct rt_band_t)); - if (band == NULL) { - rterror("rt_band_new_inline: Out of memory allocating rt_band"); - return NULL; - } - - RASTER_DEBUGF(3, "Created rt_band @ %p with pixtype %s", band, rt_pixtype_name(pixtype)); - - band->pixtype = pixtype; - band->offline = 0; - band->width = width; - band->height = height; - band->hasnodata = hasnodata ? 1 : 0; - band->isnodata = FALSE; /* we don't know what is in data, so must be FALSE */ - band->nodataval = 0; - band->data.mem = data; - band->ownsdata = 0; /* we do NOT own this data!!! */ - band->raster = NULL; - - RASTER_DEBUGF(3, "Created rt_band with dimensions %d x %d", band->width, band->height); - - /* properly set nodataval as it may need to be constrained to the data type */ - if (hasnodata && rt_band_set_nodata(band, nodataval, NULL) != ES_NONE) { - rterror("rt_band_new_inline: Could not set NODATA value"); - rt_band_destroy(band); - return NULL; - } - - return band; -} - -/** - * Create an out-db rt_band - * - * @param width : number of pixel columns - * @param height : number of pixel rows - * @param pixtype : pixel type for the band - * @param hasnodata : indicates if the band has nodata value - * @param nodataval : the nodata value, will be appropriately - * truncated to fit the pixtype size. - * @param bandNum : 0-based band number in the external file - * to associate this band with. - * @param path : NULL-terminated path string pointing to the file - * containing band data. The string will NOT be - * copied, ownership is left to caller which is - * responsible to keep it allocated for the whole - * lifetime of the returned rt_band. - * - * @return an rt_band, or 0 on failure - */ -rt_band -rt_band_new_offline( - uint16_t width, uint16_t height, - rt_pixtype pixtype, - uint32_t hasnodata, double nodataval, - uint8_t bandNum, const char* path -) { - rt_band band = NULL; - int pathlen = 0; - - assert(NULL != path); - - band = rtalloc(sizeof(struct rt_band_t)); - if (band == NULL) { - rterror("rt_band_new_offline: Out of memory allocating rt_band"); - return NULL; - } - - RASTER_DEBUGF(3, "Created rt_band @ %p with pixtype %s", - band, rt_pixtype_name(pixtype) - ); - - band->pixtype = pixtype; - band->offline = 1; - band->width = width; - band->height = height; - band->hasnodata = hasnodata ? 1 : 0; - band->nodataval = 0; - band->isnodata = FALSE; /* we don't know if the offline band is NODATA */ - band->ownsdata = 0; /* offline, flag is useless as all offline data cache is owned internally */ - band->raster = NULL; - - /* properly set nodataval as it may need to be constrained to the data type */ - if (hasnodata && rt_band_set_nodata(band, nodataval, NULL) != ES_NONE) { - rterror("rt_band_new_offline: Could not set NODATA value"); - rt_band_destroy(band); - return NULL; - } - - band->data.offline.bandNum = bandNum; - - /* memory for data.offline.path is managed internally */ - pathlen = strlen(path); - band->data.offline.path = rtalloc(sizeof(char) * (pathlen + 1)); - if (band->data.offline.path == NULL) { - rterror("rt_band_new_offline: Out of memory allocating offline path"); - rt_band_destroy(band); - return NULL; - } - memcpy(band->data.offline.path, path, pathlen); - band->data.offline.path[pathlen] = '\0'; - - band->data.offline.mem = NULL; - - return band; -} - -/** - * Create a new band duplicated from source band. Memory is allocated - * for band path (if band is offline) or band data (if band is online). - * The caller is responsible for freeing the memory when the returned - * rt_band is destroyed. - * - * @param : the band to copy - * - * @return an rt_band or NULL on failure - */ -rt_band -rt_band_duplicate(rt_band band) { - rt_band rtn = NULL; - - assert(band != NULL); - - /* offline */ - if (band->offline) { - rtn = rt_band_new_offline( - band->width, band->height, - band->pixtype, - band->hasnodata, band->nodataval, - band->data.offline.bandNum, (const char *) band->data.offline.path - ); - } - /* online */ - else { - uint8_t *data = NULL; - data = rtalloc(rt_pixtype_size(band->pixtype) * band->width * band->height); - if (data == NULL) { - rterror("rt_band_duplicate: Out of memory allocating online band data"); - return NULL; - } - memcpy(data, band->data.mem, rt_pixtype_size(band->pixtype) * band->width * band->height); - - rtn = rt_band_new_inline( - band->width, band->height, - band->pixtype, - band->hasnodata, band->nodataval, - data - ); - rt_band_set_ownsdata_flag(rtn, 1); /* we DO own this data!!! */ - } - - if (rtn == NULL) { - rterror("rt_band_duplicate: Could not copy band"); - return NULL; - } - - return rtn; -} - -int -rt_band_is_offline(rt_band band) { - - assert(NULL != band); - - - return band->offline ? 1 : 0; -} - -/** - * Destroy a raster band - * - * @param band : the band to destroy - */ -void -rt_band_destroy(rt_band band) { - if (band == NULL) - return; - - RASTER_DEBUGF(3, "Destroying rt_band @ %p", band); - - /* offline band */ - if (band->offline) { - /* memory cache */ - if (band->data.offline.mem != NULL) - rtdealloc(band->data.offline.mem); - /* offline file path */ - if (band->data.offline.path != NULL) - rtdealloc(band->data.offline.path); - } - /* inline band and band owns the data */ - else if (band->data.mem != NULL && band->ownsdata) - rtdealloc(band->data.mem); - - rtdealloc(band); -} - -const char* -rt_band_get_ext_path(rt_band band) { - - assert(NULL != band); - - - if (!band->offline) { - RASTER_DEBUG(3, "rt_band_get_ext_path: Band is not offline"); - return NULL; - } - return band->data.offline.path; -} - -rt_errorstate -rt_band_get_ext_band_num(rt_band band, uint8_t *bandnum) { - assert(NULL != band); - assert(NULL != bandnum); - - *bandnum = 0; - - if (!band->offline) { - RASTER_DEBUG(3, "rt_band_get_ext_band_num: Band is not offline"); - return ES_ERROR; - } - - *bandnum = band->data.offline.bandNum; - - return ES_NONE; -} - -/** - * Get pointer to raster band data - * - * @param band : the band who's data to get - * - * @return pointer to band data or NULL if error - */ -void * -rt_band_get_data(rt_band band) { - assert(NULL != band); - - if (band->offline) { - if (band->data.offline.mem != NULL) - return band->data.offline.mem; - - if (rt_band_load_offline_data(band) != ES_NONE) - return NULL; - else - return band->data.offline.mem; - } - else - return band->data.mem; -} - -/** - * Load offline band's data. Loaded data is internally owned - * and should not be released by the caller. Data will be - * released when band is destroyed with rt_band_destroy(). - * - * @param band : the band who's data to get - * - * @return ES_NONE if success, ES_ERROR if failure - */ -rt_errorstate -rt_band_load_offline_data(rt_band band) { - GDALDatasetH hdsSrc = NULL; - int nband = 0; - VRTDatasetH hdsDst = NULL; - VRTSourcedRasterBandH hbandDst = NULL; - double gt[6] = {0.}; - double ogt[6] = {0}; - double offset[2] = {0}; - - rt_raster _rast = NULL; - rt_band _band = NULL; - int aligned = 0; - int err = ES_NONE; - - assert(band != NULL); - assert(band->raster != NULL); - - if (!band->offline) { - rterror("rt_band_load_offline_data: Band is not offline"); - return ES_ERROR; - } - else if (!strlen(band->data.offline.path)) { - rterror("rt_band_load_offline_data: Offline band does not a have a specified file"); - return ES_ERROR; - } - - rt_util_gdal_register_all(); - hdsSrc = GDALOpenShared(band->data.offline.path, GA_ReadOnly); - if (hdsSrc == NULL) { - rterror("rt_band_load_offline_data: Cannot open offline raster: %s", band->data.offline.path); - return ES_ERROR; - } - - /* # of bands */ - nband = GDALGetRasterCount(hdsSrc); - if (!nband) { - rterror("rt_band_load_offline_data: No bands found in offline raster: %s", band->data.offline.path); - GDALClose(hdsSrc); - return ES_ERROR; - } - /* bandNum is 0-based */ - else if (band->data.offline.bandNum + 1 > nband) { - rterror("rt_band_load_offline_data: Specified band %d not found in offline raster: %s", band->data.offline.bandNum, band->data.offline.path); - GDALClose(hdsSrc); - return ES_ERROR; - } - - /* get raster's geotransform */ - rt_raster_get_geotransform_matrix(band->raster, gt); - RASTER_DEBUGF(3, "Raster geotransform (%f, %f, %f, %f, %f, %f)", - gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); - - /* get offline raster's geotransform */ - if (GDALGetGeoTransform(hdsSrc, ogt) != CE_None) { - RASTER_DEBUG(4, "Using default geotransform matrix (0, 1, 0, 0, 0, -1)"); - ogt[0] = 0; - ogt[1] = 1; - ogt[2] = 0; - ogt[3] = 0; - ogt[4] = 0; - ogt[5] = -1; - } - RASTER_DEBUGF(3, "Offline geotransform (%f, %f, %f, %f, %f, %f)", - ogt[0], ogt[1], ogt[2], ogt[3], ogt[4], ogt[5]); - - /* are rasters aligned? */ - _rast = rt_raster_new(1, 1); - rt_raster_set_geotransform_matrix(_rast, ogt); - rt_raster_set_srid(_rast, band->raster->srid); - err = rt_raster_same_alignment(band->raster, _rast, &aligned, NULL); - rt_raster_destroy(_rast); - - if (err != ES_NONE) { - rterror("rt_band_load_offline_data: Could not test alignment of in-db representation of out-db raster"); - GDALClose(hdsSrc); - return ES_ERROR; - } - else if (!aligned) { - rtwarn("The in-db representation of the out-db raster is not aligned. Band data may be incorrect"); - } - - /* get offsets */ - rt_raster_geopoint_to_cell( - band->raster, - ogt[0], ogt[3], - &(offset[0]), &(offset[1]), - NULL - ); - - RASTER_DEBUGF(4, "offsets: (%f, %f)", offset[0], offset[1]); - - /* create VRT dataset */ - hdsDst = VRTCreate(band->width, band->height); - GDALSetGeoTransform(hdsDst, gt); - /* - GDALSetDescription(hdsDst, "/tmp/offline.vrt"); - */ - - /* add band as simple sources */ - GDALAddBand(hdsDst, rt_util_pixtype_to_gdal_datatype(band->pixtype), NULL); - hbandDst = (VRTSourcedRasterBandH) GDALGetRasterBand(hdsDst, 1); - - if (band->hasnodata) - GDALSetRasterNoDataValue(hbandDst, band->nodataval); - - VRTAddSimpleSource( - hbandDst, GDALGetRasterBand(hdsSrc, band->data.offline.bandNum + 1), - abs(offset[0]), abs(offset[1]), - band->width, band->height, - 0, 0, - band->width, band->height, - "near", VRT_NODATA_UNSET - ); - - /* make sure VRT reflects all changes */ - VRTFlushCache(hdsDst); - - /* convert VRT dataset to rt_raster */ - _rast = rt_raster_from_gdal_dataset(hdsDst); - - GDALClose(hdsDst); - /* XXX: need to find a way to clean up the GDALOpenShared datasets at end of transaction */ - /* GDALClose(hdsSrc); */ - - if (_rast == NULL) { - rterror("rt_band_load_offline_data: Cannot load data from offline raster: %s", band->data.offline.path); - return ES_ERROR; - } - - _band = rt_raster_get_band(_rast, 0); - if (_band == NULL) { - rterror("rt_band_load_offline_data: Cannot load data from offline raster: %s", band->data.offline.path); - rt_raster_destroy(_rast); - return ES_ERROR; - } - - /* band->data.offline.mem not NULL, free first */ - if (band->data.offline.mem != NULL) { - rtdealloc(band->data.offline.mem); - band->data.offline.mem = NULL; - } - - band->data.offline.mem = _band->data.mem; - - rtdealloc(_band); /* cannot use rt_band_destroy */ - rt_raster_destroy(_rast); - - return ES_NONE; -} - -rt_pixtype -rt_band_get_pixtype(rt_band band) { - - assert(NULL != band); - - - return band->pixtype; -} - -uint16_t -rt_band_get_width(rt_band band) { - - assert(NULL != band); - - - return band->width; -} - -uint16_t -rt_band_get_height(rt_band band) { - - assert(NULL != band); - - - return band->height; -} - -/* Get ownsdata flag */ -int -rt_band_get_ownsdata_flag(rt_band band) { - assert(NULL != band); - - return band->ownsdata ? 1 : 0; -} - -/* set ownsdata flag */ -void -rt_band_set_ownsdata_flag(rt_band band, int flag) { - assert(NULL != band); - - band->ownsdata = flag ? 1 : 0; -} - -#ifdef OPTIMIZE_SPACE - -/* - * Set given number of bits of the given byte, - * starting from given bitOffset (from the first) - * to the given value. - * - * Examples: - * char ch; - * ch=0; setBits(&ch, 1, 1, 0) -> ch==8 - * ch=0; setBits(&ch, 3, 2, 1) -> ch==96 (0x60) - * - * Note that number of bits set must be <= 8-bitOffset - * - */ -static void -setBits(char* ch, double val, int bits, int bitOffset) { - char mask = 0xFF >> (8 - bits); - char ival = val; - - - assert(ch != NULL); - assert(8 - bitOffset >= bits); - - RASTER_DEBUGF(4, "ival:%d bits:%d mask:%hhx bitoffset:%d\n", - ival, bits, mask, bitOffset); - - /* clear all but significant bits from ival */ - ival &= mask; -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - if (ival != val) { - rtwarn("Pixel value for %d-bits band got truncated" - " from %g to %hhu", bits, val, ival); - } -#endif /* POSTGIS_RASTER_WARN_ON_TRUNCATION */ - - RASTER_DEBUGF(4, " cleared ival:%hhx\n", ival); - - - /* Shift ival so the significant bits start at - * the first bit */ - ival <<= (8 - bitOffset - bits); - - RASTER_DEBUGF(4, " ival shifted:%hhx\n", ival); - RASTER_DEBUGF(4, " ch:%hhx\n", *ch); - - /* clear first bits of target */ - *ch &= ~(mask << (8 - bits - bitOffset)); - - RASTER_DEBUGF(4, " ch cleared:%hhx\n", *ch); - - /* Set the first bit of target */ - *ch |= ival; - - RASTER_DEBUGF(4, " ch ored:%hhx\n", *ch); - -} -#endif /* OPTIMIZE_SPACE */ - -int -rt_band_get_hasnodata_flag(rt_band band) { - assert(NULL != band); - - return band->hasnodata ? 1 : 0; -} - -void -rt_band_set_hasnodata_flag(rt_band band, int flag) { - - assert(NULL != band); - - band->hasnodata = (flag) ? 1 : 0; - - /* isnodata depends on hasnodata */ - if (!band->hasnodata && band->isnodata) { - RASTER_DEBUG(3, "Setting isnodata to FALSE as band no longer has NODATA"); - band->isnodata = 0; - } -} - -rt_errorstate -rt_band_set_isnodata_flag(rt_band band, int flag) { - assert(NULL != band); - - if (!band->hasnodata) { - /* silently permit setting isnodata flag to FALSE */ - if (!flag) - band->isnodata = 0; - else { - rterror("rt_band_set_isnodata_flag: Cannot set isnodata flag as band has no NODATA"); - return ES_ERROR; - } - } - else - band->isnodata = (flag) ? 1 : 0; - - return ES_NONE; -} - -int -rt_band_get_isnodata_flag(rt_band band) { - assert(NULL != band); - - if (band->hasnodata) - return band->isnodata ? 1 : 0; - else - return 0; -} - -/** - * Set nodata value - * - * @param band : the band to set nodata value to - * @param val : the nodata value - * @param converted : if non-zero, value was truncated/clamped/coverted - * - * @return ES_NONE or ES_ERROR - */ -rt_errorstate -rt_band_set_nodata(rt_band band, double val, int *converted) { - rt_pixtype pixtype = PT_END; - int32_t checkvalint = 0; - uint32_t checkvaluint = 0; - float checkvalfloat = 0; - double checkvaldouble = 0; - - assert(NULL != band); - - if (converted != NULL) - *converted = 0; - - pixtype = band->pixtype; - - RASTER_DEBUGF(3, "rt_band_set_nodata: setting nodata value %g with band type %s", val, rt_pixtype_name(pixtype)); - - /* return -1 on out of range */ - switch (pixtype) { - case PT_1BB: { - band->nodataval = rt_util_clamp_to_1BB(val); - checkvalint = band->nodataval; - break; - } - case PT_2BUI: { - band->nodataval = rt_util_clamp_to_2BUI(val); - checkvalint = band->nodataval; - break; - } - case PT_4BUI: { - band->nodataval = rt_util_clamp_to_4BUI(val); - checkvalint = band->nodataval; - break; - } - case PT_8BSI: { - band->nodataval = rt_util_clamp_to_8BSI(val); - checkvalint = band->nodataval; - break; - } - case PT_8BUI: { - band->nodataval = rt_util_clamp_to_8BUI(val); - checkvalint = band->nodataval; - break; - } - case PT_16BSI: { - band->nodataval = rt_util_clamp_to_16BSI(val); - checkvalint = band->nodataval; - break; - } - case PT_16BUI: { - band->nodataval = rt_util_clamp_to_16BUI(val); - checkvalint = band->nodataval; - break; - } - case PT_32BSI: { - band->nodataval = rt_util_clamp_to_32BSI(val); - checkvalint = band->nodataval; - break; - } - case PT_32BUI: { - band->nodataval = rt_util_clamp_to_32BUI(val); - checkvaluint = band->nodataval; - break; - } - case PT_32BF: { - band->nodataval = rt_util_clamp_to_32F(val); - checkvalfloat = band->nodataval; - break; - } - case PT_64BF: { - band->nodataval = val; - checkvaldouble = band->nodataval; - break; - } - default: { - rterror("rt_band_set_nodata: Unknown pixeltype %d", pixtype); - band->hasnodata = 0; - return ES_ERROR; - } - } - - RASTER_DEBUGF(3, "rt_band_set_nodata: band->hasnodata = %d", band->hasnodata); - RASTER_DEBUGF(3, "rt_band_set_nodata: band->nodataval = %f", band->nodataval); - /* the nodata value was just set, so this band has NODATA */ - band->hasnodata = 1; - - /* also set isnodata flag to false */ - band->isnodata = 0; - - if (rt_util_dbl_trunc_warning( - val, - checkvalint, checkvaluint, - checkvalfloat, checkvaldouble, - pixtype - ) && converted != NULL) { - *converted = 1; - } - - return ES_NONE; -} - -/** - * Set values of multiple pixels. Unlike rt_band_set_pixel, - * values in vals are expected to be of the band's pixel type - * as this function uses memcpy. - * - * It is important to be careful when using this function as - * the number of values being set may exceed a pixel "row". - * Remember that the band values are stored in a stream (1-D array) - * regardless of what the raster's width and height might be. - * So, setting a number of values may cross multiple pixel "rows". - * - * @param band : the band to set value to - * @param x : X coordinate (0-based) - * @param y : Y coordinate (0-based) - * @param vals : the pixel values to apply - * @param len : # of elements in vals - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate -rt_band_set_pixel_line( - rt_band band, - int x, int y, - void *vals, uint32_t len -) { - rt_pixtype pixtype = PT_END; - int size = 0; - uint8_t *data = NULL; - uint32_t offset = 0; - - assert(NULL != band); - assert(vals != NULL && len > 0); - - RASTER_DEBUGF(3, "length of values = %d", len); - - if (band->offline) { - rterror("rt_band_set_pixel_line not implemented yet for OFFDB bands"); - return ES_ERROR; - } - - pixtype = band->pixtype; - size = rt_pixtype_size(pixtype); - - if ( - x < 0 || x >= band->width || - y < 0 || y >= band->height - ) { - rterror("rt_band_set_pixel_line: Coordinates out of range (%d, %d) vs (%d, %d)", x, y, band->width, band->height); - return ES_ERROR; - } - - data = rt_band_get_data(band); - offset = x + (y * band->width); - RASTER_DEBUGF(4, "offset = %d", offset); - - /* make sure len of values to copy don't exceed end of data */ - if (len > (band->width * band->height) - offset) { - rterror("rt_band_set_pixel_line: Could not apply pixels as values length exceeds end of data"); - return ES_ERROR; - } - - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BUI: - case PT_8BSI: { - uint8_t *ptr = data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_16BUI: { - uint16_t *ptr = (uint16_t *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_16BSI: { - int16_t *ptr = (int16_t *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_32BUI: { - uint32_t *ptr = (uint32_t *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_32BSI: { - int32_t *ptr = (int32_t *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_32BF: { - float *ptr = (float *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - case PT_64BF: { - double *ptr = (double *) data; - ptr += offset; - memcpy(ptr, vals, size * len); - break; - } - default: { - rterror("rt_band_set_pixel_line: Unknown pixeltype %d", pixtype); - return ES_ERROR; - } - } - -#if POSTGIS_DEBUG_LEVEL > 0 - { - double value; - rt_band_get_pixel(band, x, y, &value, NULL); - RASTER_DEBUGF(4, "pixel at (%d, %d) = %f", x, y, value); - } -#endif - - /* set band's isnodata flag to FALSE */ - if (rt_band_get_hasnodata_flag(band)) - rt_band_set_isnodata_flag(band, 0); - - return ES_NONE; -} - -/** - * Set single pixel's value - * - * @param band : the band to set value to - * @param x : x ordinate (0-based) - * @param y : y ordinate (0-based) - * @param val : the pixel value - * @param converted : (optional) non-zero if value truncated/clamped/converted - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate -rt_band_set_pixel( - rt_band band, - int x, int y, - double val, - int *converted -) { - rt_pixtype pixtype = PT_END; - unsigned char* data = NULL; - uint32_t offset = 0; - - int32_t checkvalint = 0; - uint32_t checkvaluint = 0; - float checkvalfloat = 0; - double checkvaldouble = 0; - - assert(NULL != band); - - if (converted != NULL) - *converted = 0; - - if (band->offline) { - rterror("rt_band_set_pixel not implemented yet for OFFDB bands"); - return ES_ERROR; - } - - pixtype = band->pixtype; - - if ( - x < 0 || x >= band->width || - y < 0 || y >= band->height - ) { - rterror("rt_band_set_pixel: Coordinates out of range"); - return ES_ERROR; - } - - /* check that clamped value isn't clamped NODATA */ - if (band->hasnodata && pixtype != PT_64BF) { - double newval; - int corrected; - - rt_band_corrected_clamped_value(band, val, &newval, &corrected); - - if (corrected) { -#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 - rtwarn("Value for pixel %d x %d has been corrected as clamped value becomes NODATA", x, y); -#endif - val = newval; - - if (converted != NULL) - *converted = 1; - } - } - - data = rt_band_get_data(band); - offset = x + (y * band->width); - - switch (pixtype) { - case PT_1BB: { - data[offset] = rt_util_clamp_to_1BB(val); - checkvalint = data[offset]; - break; - } - case PT_2BUI: { - data[offset] = rt_util_clamp_to_2BUI(val); - checkvalint = data[offset]; - break; - } - case PT_4BUI: { - data[offset] = rt_util_clamp_to_4BUI(val); - checkvalint = data[offset]; - break; - } - case PT_8BSI: { - data[offset] = rt_util_clamp_to_8BSI(val); - checkvalint = (int8_t) data[offset]; - break; - } - case PT_8BUI: { - data[offset] = rt_util_clamp_to_8BUI(val); - checkvalint = data[offset]; - break; - } - case PT_16BSI: { - int16_t *ptr = (int16_t*) data; /* we assume correct alignment */ - ptr[offset] = rt_util_clamp_to_16BSI(val); - checkvalint = (int16_t) ptr[offset]; - break; - } - case PT_16BUI: { - uint16_t *ptr = (uint16_t*) data; /* we assume correct alignment */ - ptr[offset] = rt_util_clamp_to_16BUI(val); - checkvalint = ptr[offset]; - break; - } - case PT_32BSI: { - int32_t *ptr = (int32_t*) data; /* we assume correct alignment */ - ptr[offset] = rt_util_clamp_to_32BSI(val); - checkvalint = (int32_t) ptr[offset]; - break; - } - case PT_32BUI: { - uint32_t *ptr = (uint32_t*) data; /* we assume correct alignment */ - ptr[offset] = rt_util_clamp_to_32BUI(val); - checkvaluint = ptr[offset]; - break; - } - case PT_32BF: { - float *ptr = (float*) data; /* we assume correct alignment */ - ptr[offset] = rt_util_clamp_to_32F(val); - checkvalfloat = ptr[offset]; - break; - } - case PT_64BF: { - double *ptr = (double*) data; /* we assume correct alignment */ - ptr[offset] = val; - checkvaldouble = ptr[offset]; - break; - } - default: { - rterror("rt_band_set_pixel: Unknown pixeltype %d", pixtype); - return ES_ERROR; - } - } - - /* If the stored value is not NODATA, reset the isnodata flag */ - if (!rt_band_clamped_value_is_nodata(band, val)) { - RASTER_DEBUG(3, "Band has a value that is not NODATA. Setting isnodata to FALSE"); - band->isnodata = FALSE; - } - - /* Overflow checking */ - if (rt_util_dbl_trunc_warning( - val, - checkvalint, checkvaluint, - checkvalfloat, checkvaldouble, - pixtype - ) && converted != NULL) { - *converted = 1; - } - - return ES_NONE; -} - -/** - * Get values of multiple pixels. Unlike rt_band_get_pixel, - * values in vals are of the band's pixel type so cannot be - * assumed to be double. Function uses memcpy. - * - * It is important to be careful when using this function as - * the number of values being fetched may exceed a pixel "row". - * Remember that the band values are stored in a stream (1-D array) - * regardless of what the raster's width and height might be. - * So, getting a number of values may cross multiple pixel "rows". - * - * @param band : the band to get pixel value from - * @param x : pixel column (0-based) - * @param y : pixel row (0-based) - * @param len : the number of pixels to get - * @param **vals : the pixel values - * @param *nvals : the number of pixel values being returned - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate rt_band_get_pixel_line( - rt_band band, - int x, int y, - uint16_t len, - void **vals, uint16_t *nvals -) { - uint8_t *_vals = NULL; - int pixsize = 0; - uint8_t *data = NULL; - uint32_t offset = 0; - uint16_t _nvals = 0; - int maxlen = 0; - uint8_t *ptr = NULL; - - assert(NULL != band); - assert(vals != NULL && nvals != NULL); - - /* initialize to no values */ - *nvals = 0; - - if ( - x < 0 || x >= band->width || - y < 0 || y >= band->height - ) { - rtwarn("Attempting to get pixel values with out of range raster coordinates: (%d, %d)", x, y); - return ES_ERROR; - } - - if (len < 1) - return ES_NONE; - - data = rt_band_get_data(band); - if (data == NULL) { - rterror("rt_band_get_pixel_line: Cannot get band data"); - return ES_ERROR; - } - - /* +1 for the nodata value */ - offset = x + (y * band->width); - RASTER_DEBUGF(4, "offset = %d", offset); - - pixsize = rt_pixtype_size(band->pixtype); - RASTER_DEBUGF(4, "pixsize = %d", pixsize); - - /* cap _nvals so that it doesn't overflow */ - _nvals = len; - maxlen = band->width * band->height; - - if (((int) (offset + _nvals)) > maxlen) { - _nvals = maxlen - offset; - rtwarn("Limiting returning number values to %d", _nvals); - } - RASTER_DEBUGF(4, "_nvals = %d", _nvals); - - ptr = data + (offset * pixsize); - - _vals = rtalloc(_nvals * pixsize); - if (_vals == NULL) { - rterror("rt_band_get_pixel_line: Could not allocate memory for pixel values"); - return ES_ERROR; - } - - /* copy pixels */ - memcpy(_vals, ptr, _nvals * pixsize); - - *vals = _vals; - *nvals = _nvals; - - return ES_NONE; -} - -/** - * Get pixel value. If band's isnodata flag is TRUE, value returned - * will be the band's NODATA value - * - * @param band : the band to set nodata value to - * @param x : x ordinate (0-based) - * @param y : x ordinate (0-based) - * @param *value : pixel value - * @param *nodata : 0 if pixel is not NODATA - * - * @return 0 on success, -1 on error (value out of valid range). - */ -rt_errorstate -rt_band_get_pixel( - rt_band band, - int x, int y, - double *value, - int *nodata -) { - rt_pixtype pixtype = PT_END; - uint8_t* data = NULL; - uint32_t offset = 0; - - assert(NULL != band); - assert(NULL != value); - - /* set nodata to 0 */ - if (nodata != NULL) - *nodata = 0; - - if ( - x < 0 || x >= band->width || - y < 0 || y >= band->height - ) { - rtwarn("Attempting to get pixel value with out of range raster coordinates: (%d, %d)", x, y); - return ES_ERROR; - } - - /* band is NODATA */ - if (band->isnodata) { - RASTER_DEBUG(3, "Band's isnodata flag is TRUE. Returning NODATA value"); - *value = band->nodataval; - if (nodata != NULL) *nodata = 1; - return ES_NONE; - } - - data = rt_band_get_data(band); - if (data == NULL) { - rterror("rt_band_get_pixel: Cannot get band data"); - return ES_ERROR; - } - - /* +1 for the nodata value */ - offset = x + (y * band->width); - - pixtype = band->pixtype; - - switch (pixtype) { - case PT_1BB: -#ifdef OPTIMIZE_SPACE - { - int byteOffset = offset / 8; - int bitOffset = offset % 8; - data += byteOffset; - - /* Bit to set is bitOffset into data */ - *value = getBits(data, val, 1, bitOffset); - break; - } -#endif - case PT_2BUI: -#ifdef OPTIMIZE_SPACE - { - int byteOffset = offset / 4; - int bitOffset = offset % 4; - data += byteOffset; - - /* Bits to set start at bitOffset into data */ - *value = getBits(data, val, 2, bitOffset); - break; - } -#endif - case PT_4BUI: -#ifdef OPTIMIZE_SPACE - { - int byteOffset = offset / 2; - int bitOffset = offset % 2; - data += byteOffset; - - /* Bits to set start at bitOffset into data */ - *value = getBits(data, val, 2, bitOffset); - break; - } -#endif - case PT_8BSI: { - int8_t val = data[offset]; - *value = val; - break; - } - case PT_8BUI: { - uint8_t val = data[offset]; - *value = val; - break; - } - case PT_16BSI: { - int16_t *ptr = (int16_t*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - case PT_16BUI: { - uint16_t *ptr = (uint16_t*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - case PT_32BSI: { - int32_t *ptr = (int32_t*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - case PT_32BUI: { - uint32_t *ptr = (uint32_t*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - case PT_32BF: { - float *ptr = (float*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - case PT_64BF: { - double *ptr = (double*) data; /* we assume correct alignment */ - *value = ptr[offset]; - break; - } - default: { - rterror("rt_band_get_pixel: Unknown pixeltype %d", pixtype); - return ES_ERROR; - } - } - - /* set NODATA flag */ - if (band->hasnodata && nodata != NULL) { - if (rt_band_clamped_value_is_nodata(band, *value)) - *nodata = 1; - } - - return ES_NONE; -} - -/** - * Get nearest pixel(s) with value (not NODATA) to specified pixel - * - * @param band : the band to get nearest pixel(s) from - * @param x : the column of the pixel (0-based) - * @param y : the line of the pixel (0-based) - * @param distancex : the number of pixels around the specified pixel - * along the X axis - * @param distancey : the number of pixels around the specified pixel - * along the Y axis - * @param exclude_nodata_value : if non-zero, ignore nodata values - * to check for pixels with value - * @param npixels : return set of rt_pixel object or NULL - * - * @return -1 on error, otherwise the number of rt_pixel objects - * in npixels - */ -int rt_band_get_nearest_pixel( - rt_band band, - int x, int y, - uint16_t distancex, uint16_t distancey, - int exclude_nodata_value, - rt_pixel *npixels -) { - rt_pixel npixel = NULL; - int extent[4] = {0}; - int max_extent[4] = {0}; - int d0 = 0; - int distance[2] = {0}; - uint32_t _d[2] = {0}; - uint32_t i = 0; - uint32_t j = 0; - uint32_t k = 0; - int _max = 0; - int _x = 0; - int _y = 0; - int *_min = NULL; - double pixval = 0; - double minval = 0; - uint32_t count = 0; - int isnodata = 0; - - int inextent = 0; - - assert(NULL != band); - assert(NULL != npixels); - - RASTER_DEBUG(3, "Starting"); - - /* process distance */ - distance[0] = distancex; - distance[1] = distancey; - - /* no distance, means get nearest pixels and return */ - if (!distance[0] && !distance[1]) - d0 = 1; - - RASTER_DEBUGF(4, "Selected pixel: %d x %d", x, y); - RASTER_DEBUGF(4, "Distances: %d x %d", distance[0], distance[1]); - - /* shortcuts if outside band extent */ - if ( - exclude_nodata_value && ( - (x < 0 || x > band->width) || - (y < 0 || y > band->height) - ) - ) { - /* no distances specified, jump to pixel close to extent */ - if (d0) { - if (x < 0) - x = -1; - else if (x > band->width) - x = band->width; - - if (y < 0) - y = -1; - else if (y > band->height) - y = band->height; - - RASTER_DEBUGF(4, "Moved selected pixel: %d x %d", x, y); - } - /* - distances specified - if distances won't capture extent of band, return 0 - */ - else if ( - ((x < 0 && abs(x) > distance[0]) || (x - band->width >= distance[0])) || - ((y < 0 && abs(y) > distance[1]) || (y - band->height >= distance[1])) - ) { - RASTER_DEBUG(4, "No nearest pixels possible for provided pixel and distances"); - return 0; - } - } - - /* no NODATA, exclude is FALSE */ - if (!band->hasnodata) - exclude_nodata_value = FALSE; - /* band is NODATA and excluding NODATA */ - else if (exclude_nodata_value && band->isnodata) { - RASTER_DEBUG(4, "No nearest pixels possible as band is NODATA and excluding NODATA values"); - return 0; - } - - /* determine the maximum distance to prevent an infinite loop */ - if (d0) { - int a, b; - - /* X axis */ - a = abs(x); - b = abs(x - band->width); - - if (a > b) - distance[0] = a; - else - distance[0] = b; - - /* Y axis */ - a = abs(y); - b = abs(y - band->height); - if (a > b) - distance[1] = a; - else - distance[1] = b; - - RASTER_DEBUGF(4, "Maximum distances: %d x %d", distance[0], distance[1]); - } - - /* minimum possible value for pixel type */ - minval = rt_pixtype_get_min_value(band->pixtype); - RASTER_DEBUGF(4, "pixtype: %s", rt_pixtype_name(band->pixtype)); - RASTER_DEBUGF(4, "minval: %f", minval); - - /* set variables */ - count = 0; - *npixels = NULL; - - /* maximum extent */ - max_extent[0] = x - distance[0]; /* min X */ - max_extent[1] = y - distance[1]; /* min Y */ - max_extent[2] = x + distance[0]; /* max X */ - max_extent[3] = y + distance[1]; /* max Y */ - RASTER_DEBUGF(4, "Maximum Extent: (%d, %d, %d, %d)", - max_extent[0], max_extent[1], max_extent[2], max_extent[3]); - - _d[0] = 0; - _d[1] = 0; - do { - _d[0]++; - _d[1]++; - - extent[0] = x - _d[0]; /* min x */ - extent[1] = y - _d[1]; /* min y */ - extent[2] = x + _d[0]; /* max x */ - extent[3] = y + _d[1]; /* max y */ - - RASTER_DEBUGF(4, "Processing distances: %d x %d", _d[0], _d[1]); - RASTER_DEBUGF(4, "Extent: (%d, %d, %d, %d)", - extent[0], extent[1], extent[2], extent[3]); - - for (i = 0; i < 2; i++) { - - /* by row */ - if (i < 1) - _max = extent[2] - extent[0] + 1; - /* by column */ - else - _max = extent[3] - extent[1] + 1; - _max = abs(_max); - - for (j = 0; j < 2; j++) { - /* by row */ - if (i < 1) { - _x = extent[0]; - _min = &_x; - - /* top row */ - if (j < 1) - _y = extent[1]; - /* bottom row */ - else - _y = extent[3]; - } - /* by column */ - else { - _y = extent[1] + 1; - _min = &_y; - - /* left column */ - if (j < 1) { - _x = extent[0]; - _max -= 2; - } - /* right column */ - else - _x = extent[2]; - } - - RASTER_DEBUGF(4, "_min, _max: %d, %d", *_min, _max); - for (k = 0; k < _max; k++) { - /* check that _x and _y are not outside max extent */ - if ( - _x < max_extent[0] || _x > max_extent[2] || - _y < max_extent[1] || _y > max_extent[3] - ) { - (*_min)++; - continue; - } - - /* outside band extent, set to NODATA */ - if ( - (_x < 0 || _x >= band->width) || - (_y < 0 || _y >= band->height) - ) { - /* no NODATA, set to minimum possible value */ - if (!band->hasnodata) - pixval = minval; - /* has NODATA, use NODATA */ - else - pixval = band->nodataval; - RASTER_DEBUGF(4, "NODATA pixel outside band extent: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); - inextent = 0; - isnodata = 1; - } - else { - if (rt_band_get_pixel( - band, - _x, _y, - &pixval, - &isnodata - ) != ES_NONE) { - rterror("rt_band_get_nearest_pixel: Could not get pixel value"); - if (count) rtdealloc(*npixels); - return -1; - } - RASTER_DEBUGF(4, "Pixel: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); - inextent = 1; - } - - /* use pixval? */ - if (!exclude_nodata_value || (exclude_nodata_value && !isnodata)) { - /* add pixel to result set */ - RASTER_DEBUGF(4, "Adding pixel to set of nearest pixels: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); - count++; - - if (*npixels == NULL) - *npixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t) * count); - else - *npixels = (rt_pixel) rtrealloc(*npixels, sizeof(struct rt_pixel_t) * count); - if (*npixels == NULL) { - rterror("rt_band_get_nearest_pixel: Could not allocate memory for nearest pixel(s)"); - return -1; - } - - npixel = &((*npixels)[count - 1]); - npixel->x = _x; - npixel->y = _y; - npixel->value = pixval; - - /* special case for when outside band extent */ - if (!inextent && !band->hasnodata) - npixel->nodata = 1; - else - npixel->nodata = 0; - } - - (*_min)++; - } - } - } - - /* distance threshholds met */ - if (_d[0] >= distance[0] && _d[1] >= distance[1]) - break; - else if (d0 && count) - break; - } - while (1); - - RASTER_DEBUGF(3, "Nearest pixels in return: %d", count); - - return count; -} - -/** - * Search band for pixel(s) with search values - * - * @param band : the band to query for minimum and maximum pixel values - * @param exclude_nodata_value : if non-zero, ignore nodata values - * @param searchset : array of values to count - * @param searchcount : the number of search values - * @param pixels : pixels with the search value - * - * @return -1 on error, otherwise number of pixels - */ -int -rt_band_get_pixel_of_value( - rt_band band, int exclude_nodata_value, - double *searchset, int searchcount, - rt_pixel *pixels -) { - int x; - int y; - int i; - double pixval; - int err; - int count = 0; - int isnodata = 0; - int isequal = 0; - - rt_pixel pixel = NULL; - - assert(NULL != band); - assert(NULL != pixels); - assert(NULL != searchset && searchcount > 0); - - if (!band->hasnodata) - exclude_nodata_value = FALSE; - /* band is NODATA and exclude_nodata_value = TRUE, nothing to search */ - else if (exclude_nodata_value && band->isnodata) { - RASTER_DEBUG(4, "Pixels cannot be searched as band is NODATA and excluding NODATA values"); - return 0; - } - - for (x = 0; x < band->width; x++) { - for (y = 0; y < band->height; y++) { - err = rt_band_get_pixel(band, x, y, &pixval, &isnodata); - if (err != ES_NONE) { - rterror("rt_band_get_pixel_of_value: Cannot get band pixel"); - return -1; - } - else if (exclude_nodata_value && isnodata) - continue; - - for (i = 0; i < searchcount; i++) { - if (rt_pixtype_compare_clamped_values(band->pixtype, searchset[i], pixval, &isequal) != ES_NONE) { - continue; - } - - if (FLT_NEQ(pixval, searchset[i]) || !isequal) - continue; - - /* match found */ - count++; - if (*pixels == NULL) - *pixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t) * count); - else - *pixels = (rt_pixel) rtrealloc(*pixels, sizeof(struct rt_pixel_t) * count); - if (*pixels == NULL) { - rterror("rt_band_get_pixel_of_value: Could not allocate memory for pixel(s)"); - return -1; - } - - pixel = &((*pixels)[count - 1]); - pixel->x = x; - pixel->y = y; - pixel->nodata = 0; - pixel->value = pixval; - } - } - } - - return count; -} - -/** - * Get NODATA value - * - * @param band : the band whose NODATA value will be returned - * @param nodata : the band's NODATA value - * - * @return ES_NONE or ES_ERROR - */ -rt_errorstate -rt_band_get_nodata(rt_band band, double *nodata) { - assert(NULL != band); - assert(NULL != nodata); - - *nodata = band->nodataval; - - if (!band->hasnodata) { - rterror("rt_band_get_nodata: Band has no NODATA value"); - return ES_ERROR; - } - - return ES_NONE; -} - -double -rt_band_get_min_value(rt_band band) { - assert(NULL != band); - - return rt_pixtype_get_min_value(band->pixtype); -} - -int -rt_band_check_is_nodata(rt_band band) { - int i, j, err; - double pxValue; - int isnodata = 0; - - assert(NULL != band); - - /* Check if band has nodata value */ - if (!band->hasnodata) { - RASTER_DEBUG(3, "Band has no NODATA value"); - band->isnodata = FALSE; - return FALSE; - } - - pxValue = band->nodataval; - - /* Check all pixels */ - for (i = 0; i < band->width; i++) { - for (j = 0; j < band->height; j++) { - err = rt_band_get_pixel(band, i, j, &pxValue, &isnodata); - if (err != ES_NONE) { - rterror("rt_band_check_is_nodata: Cannot get band pixel"); - return FALSE; - } - else if (!isnodata) { - band->isnodata = FALSE; - return FALSE; - } - } - } - - band->isnodata = TRUE; - return TRUE; -} - -/** - * Compare clamped value to band's clamped NODATA value. - * - * @param band : the band whose NODATA value will be used for comparison - * @param val : the value to compare to the NODATA value - * - * @return 2 if unclamped value is unclamped NODATA - * 1 if clamped value is clamped NODATA - * 0 if clamped value is NOT clamped NODATA - */ -int -rt_band_clamped_value_is_nodata(rt_band band, double val) { - int isequal = 0; - - assert(NULL != band); - - /* no NODATA, so never equal */ - if (!band->hasnodata) - return 0; - - /* value is exactly NODATA */ - if (FLT_EQ(val, band->nodataval)) - return 2; - - /* ignore error from rt_pixtype_compare_clamped_values */ - rt_pixtype_compare_clamped_values( - band->pixtype, - val, band->nodataval, - &isequal - ); - - return isequal ? 1 : 0; -} - -/** - * Correct value when clamped value is equal to clamped NODATA value. - * Correction does NOT occur if unclamped value is exactly unclamped - * NODATA value. - * - * @param band : the band whose NODATA value will be used for comparison - * @param val : the value to compare to the NODATA value and correct - * @param *newval : pointer to corrected value - * @param *corrected : (optional) non-zero if val was corrected - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_band_corrected_clamped_value( - rt_band band, - double val, - double *newval, int *corrected -) { - double minval = 0.; - - assert(NULL != band); - assert(NULL != newval); - - if (corrected != NULL) - *corrected = 0; - - /* no need to correct if clamped values IS NOT clamped NODATA */ - if (rt_band_clamped_value_is_nodata(band, val) != 1) { - *newval = val; - return ES_NONE; - } - - minval = rt_pixtype_get_min_value(band->pixtype); - *newval = val; - - switch (band->pixtype) { - case PT_1BB: - *newval = !band->nodataval; - break; - case PT_2BUI: - if (rt_util_clamp_to_2BUI(val) == rt_util_clamp_to_2BUI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_4BUI: - if (rt_util_clamp_to_4BUI(val) == rt_util_clamp_to_4BUI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_8BSI: - if (rt_util_clamp_to_8BSI(val) == rt_util_clamp_to_8BSI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_8BUI: - if (rt_util_clamp_to_8BUI(val) == rt_util_clamp_to_8BUI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_16BSI: - if (rt_util_clamp_to_16BSI(val) == rt_util_clamp_to_16BSI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_16BUI: - if (rt_util_clamp_to_16BUI(val) == rt_util_clamp_to_16BUI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_32BSI: - if (rt_util_clamp_to_32BSI(val) == rt_util_clamp_to_32BSI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_32BUI: - if (rt_util_clamp_to_32BUI(val) == rt_util_clamp_to_32BUI(minval)) - (*newval)++; - else - (*newval)--; - break; - case PT_32BF: - if (FLT_EQ(rt_util_clamp_to_32F(val), rt_util_clamp_to_32F(minval))) - *newval += FLT_EPSILON; - else - *newval -= FLT_EPSILON; - break; - case PT_64BF: - break; - default: - rterror("rt_band_corrected_clamped_value: Unknown pixeltype %d", band->pixtype); - return ES_ERROR; - } - - if (corrected != NULL) - *corrected = 1; - - return ES_NONE; -} - -/** - * Compute summary statistics for a band - * - * @param band : the band to query for summary stats - * @param exclude_nodata_value : if non-zero, ignore nodata values - * @param sample : percentage of pixels to sample - * @param inc_vals : flag to include values in return struct - * @param cK : number of pixels counted thus far in coverage - * @param cM : M component of 1-pass stddev for coverage - * @param cQ : Q component of 1-pass stddev for coverage - * - * @return the summary statistics for a band or NULL - */ -rt_bandstats -rt_band_get_summary_stats( - rt_band band, - int exclude_nodata_value, double sample, int inc_vals, - uint64_t *cK, double *cM, double *cQ -) { - uint32_t x = 0; - uint32_t y = 0; - uint32_t z = 0; - uint32_t offset = 0; - uint32_t diff = 0; - int rtn; - int hasnodata = FALSE; - double nodata = 0; - double *values = NULL; - double value; - int isnodata = 0; - rt_bandstats stats = NULL; - - uint32_t do_sample = 0; - uint32_t sample_size = 0; - uint32_t sample_per = 0; - uint32_t sample_int = 0; - uint32_t i = 0; - uint32_t j = 0; - double sum = 0; - uint32_t k = 0; - double M = 0; - double Q = 0; - -#if POSTGIS_DEBUG_LEVEL > 0 - clock_t start, stop; - double elapsed = 0; -#endif - - RASTER_DEBUG(3, "starting"); -#if POSTGIS_DEBUG_LEVEL > 0 - start = clock(); -#endif - - assert(NULL != band); - - /* band is empty (width < 1 || height < 1) */ - if (band->width < 1 || band->height < 1) { - stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); - if (NULL == stats) { - rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); - return NULL; - } - - rtwarn("Band is empty as width and/or height is 0"); - - stats->sample = 1; - stats->sorted = 0; - stats->values = NULL; - stats->count = 0; - stats->min = stats->max = 0; - stats->sum = 0; - stats->mean = 0; - stats->stddev = -1; - - return stats; - } - - hasnodata = rt_band_get_hasnodata_flag(band); - if (hasnodata != FALSE) - rt_band_get_nodata(band, &nodata); - else - exclude_nodata_value = 0; - - RASTER_DEBUGF(3, "nodata = %f", nodata); - RASTER_DEBUGF(3, "hasnodata = %d", hasnodata); - RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); - - /* entire band is nodata */ - if (rt_band_get_isnodata_flag(band) != FALSE) { - stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); - if (NULL == stats) { - rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); - return NULL; - } - - stats->sample = 1; - stats->sorted = 0; - stats->values = NULL; - - if (exclude_nodata_value) { - rtwarn("All pixels of band have the NODATA value"); - - stats->count = 0; - stats->min = stats->max = 0; - stats->sum = 0; - stats->mean = 0; - stats->stddev = -1; - } - else { - stats->count = band->width * band->height; - stats->min = stats->max = nodata; - stats->sum = stats->count * nodata; - stats->mean = nodata; - stats->stddev = 0; - } - - return stats; - } - - /* clamp percentage */ - if ( - (sample < 0 || FLT_EQ(sample, 0.0)) || - (sample > 1 || FLT_EQ(sample, 1.0)) - ) { - do_sample = 0; - sample = 1; - } - else - do_sample = 1; - RASTER_DEBUGF(3, "do_sample = %d", do_sample); - - /* sample all pixels */ - if (!do_sample) { - sample_size = band->width * band->height; - sample_per = band->height; - } - /* - randomly sample a percentage of available pixels - sampling method is known as - "systematic random sample without replacement" - */ - else { - sample_size = round((band->width * band->height) * sample); - sample_per = round(sample_size / band->width); - if (sample_per < 1) - sample_per = 1; - sample_int = round(band->height / sample_per); - srand(time(NULL)); - } - - RASTER_DEBUGF(3, "sampling %d of %d available pixels w/ %d per set" - , sample_size, (band->width * band->height), sample_per); - - if (inc_vals) { - values = rtalloc(sizeof(double) * sample_size); - if (NULL == values) { - rtwarn("Could not allocate memory for values"); - inc_vals = 0; - } - } - - /* initialize stats */ - stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); - if (NULL == stats) { - rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); - return NULL; - } - stats->sample = sample; - stats->count = 0; - stats->sum = 0; - stats->mean = 0; - stats->stddev = -1; - stats->min = stats->max = 0; - stats->values = NULL; - stats->sorted = 0; - - for (x = 0, j = 0, k = 0; x < band->width; x++) { - y = -1; - diff = 0; - - for (i = 0, z = 0; i < sample_per; i++) { - if (!do_sample) - y = i; - else { - offset = (rand() % sample_int) + 1; - y += diff + offset; - diff = sample_int - offset; - } - RASTER_DEBUGF(5, "(x, y, z) = (%d, %d, %d)", x, y, z); - if (y >= band->height || z > sample_per) break; - - rtn = rt_band_get_pixel(band, x, y, &value, &isnodata); - - j++; - if (rtn == ES_NONE && (!exclude_nodata_value || (exclude_nodata_value && !isnodata))) { - - /* inc_vals set, collect pixel values */ - if (inc_vals) values[k] = value; - - /* average */ - k++; - sum += value; - - /* - one-pass standard deviation - http://www.eecs.berkeley.edu/~mhoemmen/cs194/Tutorials/variance.pdf - */ - if (k == 1) { - Q = 0; - M = value; - } - else { - Q += (((k - 1) * pow(value - M, 2)) / k); - M += ((value - M ) / k); - } - - /* coverage one-pass standard deviation */ - if (NULL != cK) { - (*cK)++; - if (*cK == 1) { - *cQ = 0; - *cM = value; - } - else { - *cQ += (((*cK - 1) * pow(value - *cM, 2)) / *cK); - *cM += ((value - *cM ) / *cK); - } - } - - /* min/max */ - if (stats->count < 1) { - stats->count = 1; - stats->min = stats->max = value; - } - else { - if (value < stats->min) - stats->min = value; - if (value > stats->max) - stats->max = value; - } - - } - - z++; - } - } - - RASTER_DEBUG(3, "sampling complete"); - - stats->count = k; - if (k > 0) { - if (inc_vals) { - /* free unused memory */ - if (sample_size != k) { - values = rtrealloc(values, k * sizeof(double)); - } - - stats->values = values; - } - - stats->sum = sum; - stats->mean = sum / k; - - /* standard deviation */ - if (!do_sample) - stats->stddev = sqrt(Q / k); - /* sample deviation */ - else { - if (k < 2) - stats->stddev = -1; - else - stats->stddev = sqrt(Q / (k - 1)); - } - } - /* inc_vals thus values allocated but not used */ - else if (inc_vals) - rtdealloc(values); - - /* if do_sample is one */ - if (do_sample && k < 1) - rtwarn("All sampled pixels of band have the NODATA value"); - -#if POSTGIS_DEBUG_LEVEL > 0 - stop = clock(); - elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; - RASTER_DEBUGF(3, "(time, count, mean, stddev, min, max) = (%0.4f, %d, %f, %f, %f, %f)", - elapsed, stats->count, stats->mean, stats->stddev, stats->min, stats->max); -#endif - - RASTER_DEBUG(3, "done"); - return stats; -} - -/** - * Count the distribution of data - * - * @param stats : a populated stats struct for processing - * @param bin_count : the number of bins to group the data by - * @param bin_width : the width of each bin as an array - * @param bin_width_count : number of values in bin_width - * @param right : evaluate bins by (a,b] rather than default [a,b) - * @param min : user-defined minimum value of the histogram - * a value less than the minimum value is not counted in any bins - * if min = max, min and max are not used - * @param max : user-defined maximum value of the histogram - * a value greater than the max value is not counted in any bins - * if min = max, min and max are not used - * @param rtn_count : set to the number of bins being returned - * - * @return the histogram of the data or NULL - */ -rt_histogram -rt_band_get_histogram( - rt_bandstats stats, - int bin_count, double *bin_width, int bin_width_count, - int right, double min, double max, - uint32_t *rtn_count -) { - rt_histogram bins = NULL; - int init_width = 0; - int i; - int j; - double tmp; - double value; - int sum = 0; - double qmin; - double qmax; - -#if POSTGIS_DEBUG_LEVEL > 0 - clock_t start, stop; - double elapsed = 0; -#endif - - RASTER_DEBUG(3, "starting"); -#if POSTGIS_DEBUG_LEVEL > 0 - start = clock(); -#endif - - assert(NULL != stats); - assert(NULL != rtn_count); - - if (stats->count < 1 || NULL == stats->values) { - rterror("rt_util_get_histogram: rt_bandstats object has no value"); - return NULL; - } - - /* bin width must be positive numbers and not zero */ - if (NULL != bin_width && bin_width_count > 0) { - for (i = 0; i < bin_width_count; i++) { - if (bin_width[i] < 0 || FLT_EQ(bin_width[i], 0.0)) { - rterror("rt_util_get_histogram: bin_width element is less than or equal to zero"); - return NULL; - } - } - } - - /* ignore min and max parameters */ - if (FLT_EQ(max, min)) { - qmin = stats->min; - qmax = stats->max; - } - else { - qmin = min; - qmax = max; - if (qmin > qmax) { - qmin = max; - qmax = min; - } - } - - /* # of bins not provided */ - if (bin_count <= 0) { - /* - determine # of bins - http://en.wikipedia.org/wiki/Histogram - - all computed bins are assumed to have equal width - */ - /* Square-root choice for stats->count < 30 */ - if (stats->count < 30) - bin_count = ceil(sqrt(stats->count)); - /* Sturges' formula for stats->count >= 30 */ - else - bin_count = ceil(log2((double) stats->count) + 1.); - - /* bin_width_count provided and bin_width has value */ - if (bin_width_count > 0 && NULL != bin_width) { - /* user has defined something specific */ - if (bin_width_count > bin_count) - bin_count = bin_width_count; - else if (bin_width_count > 1) { - tmp = 0; - for (i = 0; i < bin_width_count; i++) tmp += bin_width[i]; - bin_count = ceil((qmax - qmin) / tmp) * bin_width_count; - } - else - bin_count = ceil((qmax - qmin) / bin_width[0]); - } - /* set bin width count to zero so that one can be calculated */ - else { - bin_width_count = 0; - } - } - - /* min and max the same */ - if (FLT_EQ(qmax, qmin)) bin_count = 1; - - RASTER_DEBUGF(3, "bin_count = %d", bin_count); - - /* bin count = 1, all values are in one bin */ - if (bin_count < 2) { - bins = rtalloc(sizeof(struct rt_histogram_t)); - if (NULL == bins) { - rterror("rt_util_get_histogram: Could not allocate memory for histogram"); - return NULL; - } - - bins->count = stats->count; - bins->percent = -1; - bins->min = qmin; - bins->max = qmax; - bins->inc_min = bins->inc_max = 1; - - *rtn_count = bin_count; - return bins; - } - - /* establish bin width */ - if (bin_width_count == 0) { - bin_width_count = 1; - - /* bin_width unallocated */ - if (NULL == bin_width) { - bin_width = rtalloc(sizeof(double)); - if (NULL == bin_width) { - rterror("rt_util_get_histogram: Could not allocate memory for bin widths"); - return NULL; - } - init_width = 1; - } - - bin_width[0] = (qmax - qmin) / bin_count; - } - - /* initialize bins */ - bins = rtalloc(bin_count * sizeof(struct rt_histogram_t)); - if (NULL == bins) { - rterror("rt_util_get_histogram: Could not allocate memory for histogram"); - if (init_width) rtdealloc(bin_width); - return NULL; - } - if (!right) - tmp = qmin; - else - tmp = qmax; - for (i = 0; i < bin_count;) { - for (j = 0; j < bin_width_count; j++) { - bins[i].count = 0; - bins->percent = -1; - - if (!right) { - bins[i].min = tmp; - tmp += bin_width[j]; - bins[i].max = tmp; - - bins[i].inc_min = 1; - bins[i].inc_max = 0; - } - else { - bins[i].max = tmp; - tmp -= bin_width[j]; - bins[i].min = tmp; - - bins[i].inc_min = 0; - bins[i].inc_max = 1; - } - - i++; - } - } - if (!right) { - bins[bin_count - 1].inc_max = 1; - - /* align last bin to the max value */ - if (bins[bin_count - 1].max < qmax) - bins[bin_count - 1].max = qmax; - } - else { - bins[bin_count - 1].inc_min = 1; - - /* align first bin to the min value */ - if (bins[bin_count - 1].min > qmin) - bins[bin_count - 1].min = qmin; - } - - /* process the values */ - for (i = 0; i < stats->count; i++) { - value = stats->values[i]; - - /* default, [a, b) */ - if (!right) { - for (j = 0; j < bin_count; j++) { - if ( - (!bins[j].inc_max && value < bins[j].max) || ( - bins[j].inc_max && ( - (value < bins[j].max) || - FLT_EQ(value, bins[j].max) - ) - ) - ) { - bins[j].count++; - sum++; - break; - } - } - } - /* (a, b] */ - else { - for (j = 0; j < bin_count; j++) { - if ( - (!bins[j].inc_min && value > bins[j].min) || ( - bins[j].inc_min && ( - (value > bins[j].min) || - FLT_EQ(value, bins[j].min) - ) - ) - ) { - bins[j].count++; - sum++; - break; - } - } - } - } - - for (i = 0; i < bin_count; i++) { - bins[i].percent = ((double) bins[i].count) / sum; - } - -#if POSTGIS_DEBUG_LEVEL > 0 - stop = clock(); - elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; - RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); - - for (j = 0; j < bin_count; j++) { - RASTER_DEBUGF(5, "(min, max, inc_min, inc_max, count, sum, percent) = (%f, %f, %d, %d, %d, %d, %f)", - bins[j].min, bins[j].max, bins[j].inc_min, bins[j].inc_max, bins[j].count, sum, bins[j].percent); - } -#endif - - if (init_width) rtdealloc(bin_width); - *rtn_count = bin_count; - RASTER_DEBUG(3, "done"); - return bins; -} - -/** - * Compute the default set of or requested quantiles for a set of data - * the quantile formula used is same as Excel and R default method - * - * @param stats : a populated stats struct for processing - * @param quantiles : the quantiles to be computed - * @param quantiles_count : the number of quantiles to be computed - * @param rtn_count : set to the number of quantiles being returned - * - * @return the default set of or requested quantiles for a band or NULL - */ -rt_quantile -rt_band_get_quantiles( - rt_bandstats stats, - double *quantiles, int quantiles_count, - uint32_t *rtn_count -) { - rt_quantile rtn; - int init_quantiles = 0; - int i = 0; - double h; - int hl; - -#if POSTGIS_DEBUG_LEVEL > 0 - clock_t start, stop; - double elapsed = 0; -#endif - - RASTER_DEBUG(3, "starting"); -#if POSTGIS_DEBUG_LEVEL > 0 - start = clock(); -#endif - - assert(NULL != stats); - assert(NULL != rtn_count); - - if (stats->count < 1 || NULL == stats->values) { - rterror("rt_band_get_quantiles: rt_bandstats object has no value"); - return NULL; - } - - /* quantiles not provided */ - if (NULL == quantiles) { - /* quantile count not specified, default to quartiles */ - if (quantiles_count < 2) - quantiles_count = 5; - - quantiles = rtalloc(sizeof(double) * quantiles_count); - init_quantiles = 1; - if (NULL == quantiles) { - rterror("rt_band_get_quantiles: Could not allocate memory for quantile input"); - return NULL; - } - - quantiles_count--; - for (i = 0; i <= quantiles_count; i++) - quantiles[i] = ((double) i) / quantiles_count; - quantiles_count++; - } - - /* check quantiles */ - for (i = 0; i < quantiles_count; i++) { - if (quantiles[i] < 0. || quantiles[i] > 1.) { - rterror("rt_band_get_quantiles: Quantile value not between 0 and 1"); - if (init_quantiles) rtdealloc(quantiles); - return NULL; - } - } - quicksort(quantiles, quantiles + quantiles_count - 1); - - /* initialize rt_quantile */ - rtn = rtalloc(sizeof(struct rt_quantile_t) * quantiles_count); - if (NULL == rtn) { - rterror("rt_band_get_quantiles: Could not allocate memory for quantile output"); - if (init_quantiles) rtdealloc(quantiles); - return NULL; - } - - /* sort values */ - if (!stats->sorted) { - quicksort(stats->values, stats->values + stats->count - 1); - stats->sorted = 1; - } - - /* - make quantiles - - formula is that used in R (method 7) and Excel from - http://en.wikipedia.org/wiki/Quantile - */ - for (i = 0; i < quantiles_count; i++) { - rtn[i].quantile = quantiles[i]; - - h = ((stats->count - 1.) * quantiles[i]) + 1.; - hl = floor(h); - - /* h greater than hl, do full equation */ - if (h > hl) - rtn[i].value = stats->values[hl - 1] + ((h - hl) * (stats->values[hl] - stats->values[hl - 1])); - /* shortcut as second part of equation is zero */ - else - rtn[i].value = stats->values[hl - 1]; - } - -#if POSTGIS_DEBUG_LEVEL > 0 - stop = clock(); - elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; - RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); -#endif - - *rtn_count = quantiles_count; - if (init_quantiles) rtdealloc(quantiles); - RASTER_DEBUG(3, "done"); - return rtn; -} - -static struct quantile_llist_element *quantile_llist_search( - struct quantile_llist_element *element, - double needle -) { - if (NULL == element) - return NULL; - else if (FLT_NEQ(needle, element->value)) { - if (NULL != element->next) - return quantile_llist_search(element->next, needle); - else - return NULL; - } - else - return element; -} - -static struct quantile_llist_element *quantile_llist_insert( - struct quantile_llist_element *element, - double value, - uint32_t *idx -) { - struct quantile_llist_element *qle = NULL; - - if (NULL == element) { - qle = rtalloc(sizeof(struct quantile_llist_element)); - RASTER_DEBUGF(4, "qle @ %p is only element in list", qle); - if (NULL == qle) return NULL; - - qle->value = value; - qle->count = 1; - - qle->prev = NULL; - qle->next = NULL; - - if (NULL != idx) *idx = 0; - return qle; - } - else if (value > element->value) { - if (NULL != idx) *idx += 1; - if (NULL != element->next) - return quantile_llist_insert(element->next, value, idx); - /* insert as last element in list */ - else { - qle = rtalloc(sizeof(struct quantile_llist_element)); - RASTER_DEBUGF(4, "insert qle @ %p as last element", qle); - if (NULL == qle) return NULL; - - qle->value = value; - qle->count = 1; - - qle->prev = element; - qle->next = NULL; - element->next = qle; - - return qle; - } - } - /* insert before current element */ - else { - qle = rtalloc(sizeof(struct quantile_llist_element)); - RASTER_DEBUGF(4, "insert qle @ %p before current element", qle); - if (NULL == qle) return NULL; - - qle->value = value; - qle->count = 1; - - if (NULL != element->prev) element->prev->next = qle; - qle->next = element; - qle->prev = element->prev; - element->prev = qle; - - return qle; - } -} - -static int quantile_llist_delete(struct quantile_llist_element *element) { - if (NULL == element) return 0; - - /* beginning of list */ - if (NULL == element->prev && NULL != element->next) { - element->next->prev = NULL; - } - /* end of list */ - else if (NULL != element->prev && NULL == element->next) { - element->prev->next = NULL; - } - /* within list */ - else if (NULL != element->prev && NULL != element->next) { - element->prev->next = element->next; - element->next->prev = element->prev; - } - - RASTER_DEBUGF(4, "qle @ %p destroyed", element); - rtdealloc(element); - - return 1; -} - -int quantile_llist_destroy(struct quantile_llist **list, uint32_t list_count) { - struct quantile_llist_element *element = NULL; - uint32_t i; - - if (NULL == *list) return 0; - - for (i = 0; i < list_count; i++) { - element = (*list)[i].head; - while (NULL != element->next) { - quantile_llist_delete(element->next); - } - quantile_llist_delete(element); - - rtdealloc((*list)[i].index); - } - - rtdealloc(*list); - return 1; -} - -static void quantile_llist_index_update(struct quantile_llist *qll, struct quantile_llist_element *qle, uint32_t idx) { - uint32_t anchor = (uint32_t) floor(idx / 100); - - if (qll->tail == qle) return; - - if ( - (anchor != 0) && ( - NULL == qll->index[anchor].element || - idx <= qll->index[anchor].index - ) - ) { - qll->index[anchor].index = idx; - qll->index[anchor].element = qle; - } - - if (anchor != 0 && NULL == qll->index[0].element) { - qll->index[0].index = 0; - qll->index[0].element = qll->head; - } -} - -static void quantile_llist_index_delete(struct quantile_llist *qll, struct quantile_llist_element *qle) { - uint32_t i = 0; - - for (i = 0; i < qll->index_max; i++) { - if ( - NULL == qll->index[i].element || - (qll->index[i].element) != qle - ) { - continue; - } - - RASTER_DEBUGF(5, "deleting index: %d => %f", i, qle->value); - qll->index[i].index = -1; - qll->index[i].element = NULL; - } -} - -static struct quantile_llist_element *quantile_llist_index_search( - struct quantile_llist *qll, - double value, - uint32_t *index -) { - uint32_t i = 0, j = 0; - RASTER_DEBUGF(5, "searching index for value %f", value); - - for (i = 0; i < qll->index_max; i++) { - if (NULL == qll->index[i].element) { - if (i < 1) break; - continue; - } - if (value > (qll->index[i]).element->value) continue; - - if (FLT_EQ(value, qll->index[i].element->value)) { - RASTER_DEBUGF(5, "using index value at %d = %f", i, qll->index[i].element->value); - *index = i * 100; - return qll->index[i].element; - } - else if (i > 0) { - for (j = 1; j < i; j++) { - if (NULL != qll->index[i - j].element) { - RASTER_DEBUGF(5, "using index value at %d = %f", i - j, qll->index[i - j].element->value); - *index = (i - j) * 100; - return qll->index[i - j].element; - } - } - } - } - - *index = 0; - return qll->head; -} - -static void quantile_llist_index_reset(struct quantile_llist *qll) { - uint32_t i = 0; - - RASTER_DEBUG(5, "resetting index"); - for (i = 0; i < qll->index_max; i++) { - qll->index[i].index = -1; - qll->index[i].element = NULL; - } -} - -/** - * Compute the default set of or requested quantiles for a coverage - * - * This function is based upon the algorithm described in: - * - * A One-Pass Space-Efficient Algorithm for Finding Quantiles (1995) - * by Rakesh Agrawal, Arun Swami - * in Proc. 7th Intl. Conf. Management of Data (COMAD-95) - * - * http://www.almaden.ibm.com/cs/projects/iis/hdb/Publications/papers/comad95.pdf - * - * In the future, it may be worth exploring algorithms that don't - * require the size of the coverage - * - * @param band : the band to include in the quantile search - * @param exclude_nodata_value : if non-zero, ignore nodata values - * @param sample : percentage of pixels to sample - * @param cov_count : number of values in coverage - * @param qlls : set of quantile_llist structures - * @param qlls_count : the number of quantile_llist structures - * @param quantiles : the quantiles to be computed - * if bot qlls and quantiles provided, qlls is used - * @param quantiles_count : the number of quantiles to be computed - * @param rtn_count : the number of quantiles being returned - * - * @return the default set of or requested quantiles for a band or NULL - */ -rt_quantile -rt_band_get_quantiles_stream( - rt_band band, - int exclude_nodata_value, double sample, - uint64_t cov_count, - struct quantile_llist **qlls, uint32_t *qlls_count, - double *quantiles, int quantiles_count, - uint32_t *rtn_count -) { - rt_quantile rtn = NULL; - int init_quantiles = 0; - - struct quantile_llist *qll = NULL; - struct quantile_llist_element *qle = NULL; - struct quantile_llist_element *qls = NULL; - const uint32_t MAX_VALUES = 750; - - uint8_t *data = NULL; - double value; - int isnodata = 0; - - uint32_t a = 0; - uint32_t i = 0; - uint32_t j = 0; - uint32_t k = 0; - uint32_t x = 0; - uint32_t y = 0; - uint32_t z = 0; - uint32_t idx = 0; - uint32_t offset = 0; - uint32_t diff = 0; - uint8_t exists = 0; - - uint32_t do_sample = 0; - uint32_t sample_size = 0; - uint32_t sample_per = 0; - uint32_t sample_int = 0; - int status; - - RASTER_DEBUG(3, "starting"); - - assert(NULL != band); - assert(cov_count > 1); - assert(NULL != rtn_count); - RASTER_DEBUGF(3, "cov_count = %d", cov_count); - - data = rt_band_get_data(band); - if (data == NULL) { - rterror("rt_band_get_summary_stats: Cannot get band data"); - return NULL; - } - - if (!rt_band_get_hasnodata_flag(band)) - exclude_nodata_value = 0; - RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); - - /* quantile_llist not provided */ - if (NULL == *qlls) { - /* quantiles not provided */ - if (NULL == quantiles) { - /* quantile count not specified, default to quartiles */ - if (quantiles_count < 2) - quantiles_count = 5; - - quantiles = rtalloc(sizeof(double) * quantiles_count); - init_quantiles = 1; - if (NULL == quantiles) { - rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile input"); - return NULL; - } - - quantiles_count--; - for (i = 0; i <= quantiles_count; i++) - quantiles[i] = ((double) i) / quantiles_count; - quantiles_count++; - } - - /* check quantiles */ - for (i = 0; i < quantiles_count; i++) { - if (quantiles[i] < 0. || quantiles[i] > 1.) { - rterror("rt_band_get_quantiles_stream: Quantile value not between 0 and 1"); - if (init_quantiles) rtdealloc(quantiles); - return NULL; - } - } - quicksort(quantiles, quantiles + quantiles_count - 1); - - /* initialize linked-list set */ - *qlls_count = quantiles_count * 2; - RASTER_DEBUGF(4, "qlls_count = %d", *qlls_count); - *qlls = rtalloc(sizeof(struct quantile_llist) * *qlls_count); - if (NULL == *qlls) { - rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile output"); - if (init_quantiles) rtdealloc(quantiles); - return NULL; - } - - j = (uint32_t) floor(MAX_VALUES / 100.) + 1; - for (i = 0; i < *qlls_count; i++) { - qll = &((*qlls)[i]); - qll->quantile = quantiles[(i * quantiles_count) / *qlls_count]; - qll->count = 0; - qll->sum1 = 0; - qll->sum2 = 0; - qll->head = NULL; - qll->tail = NULL; - - /* initialize index */ - qll->index = rtalloc(sizeof(struct quantile_llist_index) * j); - if (NULL == qll->index) { - rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile output"); - if (init_quantiles) rtdealloc(quantiles); - return NULL; - } - qll->index_max = j; - quantile_llist_index_reset(qll); - - /* AL-GEQ */ - if (!(i % 2)) { - qll->algeq = 1; - qll->tau = (uint64_t) ROUND(cov_count - (cov_count * qll->quantile), 0); - if (qll->tau < 1) qll->tau = 1; - } - /* AL-GT */ - else { - qll->algeq = 0; - qll->tau = cov_count - (*qlls)[i - 1].tau + 1; - } - - RASTER_DEBUGF(4, "qll init: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", - qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); - RASTER_DEBUGF(4, "qll init: (head, tail) = (%p, %p)", qll->head, qll->tail); - } - - if (init_quantiles) rtdealloc(quantiles); - } - - /* clamp percentage */ - if ( - (sample < 0 || FLT_EQ(sample, 0.0)) || - (sample > 1 || FLT_EQ(sample, 1.0)) - ) { - do_sample = 0; - sample = 1; - } - else - do_sample = 1; - RASTER_DEBUGF(3, "do_sample = %d", do_sample); - - /* sample all pixels */ - if (!do_sample) { - sample_size = band->width * band->height; - sample_per = band->height; - } - /* - randomly sample a percentage of available pixels - sampling method is known as - "systematic random sample without replacement" - */ - else { - sample_size = round((band->width * band->height) * sample); - sample_per = round(sample_size / band->width); - sample_int = round(band->height / sample_per); - srand(time(NULL)); - } - RASTER_DEBUGF(3, "sampling %d of %d available pixels w/ %d per set" - , sample_size, (band->width * band->height), sample_per); - - for (x = 0, j = 0, k = 0; x < band->width; x++) { - y = -1; - diff = 0; - - /* exclude_nodata_value = TRUE and band is NODATA */ - if (exclude_nodata_value && rt_band_get_isnodata_flag(band)) { - RASTER_DEBUG(3, "Skipping quantile calcuation as band is NODATA"); - break; - } - - for (i = 0, z = 0; i < sample_per; i++) { - if (do_sample != 1) - y = i; - else { - offset = (rand() % sample_int) + 1; - y += diff + offset; - diff = sample_int - offset; - } - RASTER_DEBUGF(5, "(x, y, z) = (%d, %d, %d)", x, y, z); - if (y >= band->height || z > sample_per) break; - - status = rt_band_get_pixel(band, x, y, &value, &isnodata); - - j++; - if (status == ES_NONE && (!exclude_nodata_value || (exclude_nodata_value && !isnodata))) { - - /* process each quantile */ - for (a = 0; a < *qlls_count; a++) { - qll = &((*qlls)[a]); - qls = NULL; - RASTER_DEBUGF(4, "%d of %d (%f)", a + 1, *qlls_count, qll->quantile); - RASTER_DEBUGF(5, "qll before: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", - qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); - RASTER_DEBUGF(5, "qll before: (head, tail) = (%p, %p)", qll->head, qll->tail); - - /* OPTIMIZATION: shortcuts for quantiles of zero or one */ - if (FLT_EQ(qll->quantile, 0.)) { - if (NULL != qll->head) { - if (value < qll->head->value) - qll->head->value = value; - } - else { - qle = quantile_llist_insert(qll->head, value, NULL); - qll->head = qle; - qll->tail = qle; - qll->count = 1; - } - - RASTER_DEBUGF(4, "quantile shortcut for %f\n\n", qll->quantile); - continue; - } - else if (FLT_EQ(qll->quantile, 1.)) { - if (NULL != qll->head) { - if (value > qll->head->value) - qll->head->value = value; - } - else { - qle = quantile_llist_insert(qll->head, value, NULL); - qll->head = qle; - qll->tail = qle; - qll->count = 1; - } - - RASTER_DEBUGF(4, "quantile shortcut for %f\n\n", qll->quantile); - continue; - } - - /* value exists in list */ - /* OPTIMIZATION: check to see if value exceeds last value */ - if (NULL != qll->tail && value > qll->tail->value) - qle = NULL; - /* OPTIMIZATION: check to see if value equals last value */ - else if (NULL != qll->tail && FLT_EQ(value, qll->tail->value)) - qle = qll->tail; - /* OPTIMIZATION: use index if possible */ - else { - qls = quantile_llist_index_search(qll, value, &idx); - qle = quantile_llist_search(qls, value); - } - - /* value found */ - if (NULL != qle) { - RASTER_DEBUGF(4, "%f found in list", value); - RASTER_DEBUGF(5, "qle before: (value, count) = (%f, %d)", qle->value, qle->count); - - qle->count++; - qll->sum1++; - - if (qll->algeq) - qll->sum2 = qll->sum1 - qll->head->count; - else - qll->sum2 = qll->sum1 - qll->tail->count; - - RASTER_DEBUGF(4, "qle after: (value, count) = (%f, %d)", qle->value, qle->count); - } - /* can still add element */ - else if (qll->count < MAX_VALUES) { - RASTER_DEBUGF(4, "Adding %f to list", value); - - /* insert */ - /* OPTIMIZATION: check to see if value exceeds last value */ - if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { - idx = qll->count; - qle = quantile_llist_insert(qll->tail, value, &idx); - } - /* OPTIMIZATION: use index if possible */ - else - qle = quantile_llist_insert(qls, value, &idx); - if (NULL == qle) return NULL; - RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); - qll->count++; - qll->sum1++; - - /* first element */ - if (NULL == qle->prev) - qll->head = qle; - /* last element */ - if (NULL == qle->next) - qll->tail = qle; - - if (qll->algeq) - qll->sum2 = qll->sum1 - qll->head->count; - else - qll->sum2 = qll->sum1 - qll->tail->count; - - /* index is only needed if there are at least 100 values */ - quantile_llist_index_update(qll, qle, idx); - - RASTER_DEBUGF(5, "qle, prev, next, head, tail = %p, %p, %p, %p, %p", qle, qle->prev, qle->next, qll->head, qll->tail); - } - /* AL-GEQ */ - else if (qll->algeq) { - RASTER_DEBUGF(4, "value, head->value = %f, %f", value, qll->head->value); - - if (value < qll->head->value) { - /* ignore value if test isn't true */ - if (qll->sum1 >= qll->tau) { - RASTER_DEBUGF(4, "skipping %f", value); - } - else { - - /* delete last element */ - RASTER_DEBUGF(4, "deleting %f from list", qll->tail->value); - qle = qll->tail->prev; - RASTER_DEBUGF(5, "to-be tail is %f with count %d", qle->value, qle->count); - qle->count += qll->tail->count; - quantile_llist_index_delete(qll, qll->tail); - quantile_llist_delete(qll->tail); - qll->tail = qle; - qll->count--; - RASTER_DEBUGF(5, "tail is %f with count %d", qll->tail->value, qll->tail->count); - - /* insert value */ - RASTER_DEBUGF(4, "adding %f to list", value); - /* OPTIMIZATION: check to see if value exceeds last value */ - if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { - idx = qll->count; - qle = quantile_llist_insert(qll->tail, value, &idx); - } - /* OPTIMIZATION: use index if possible */ - else { - qls = quantile_llist_index_search(qll, value, &idx); - qle = quantile_llist_insert(qls, value, &idx); - } - if (NULL == qle) return NULL; - RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); - qll->count++; - qll->sum1++; - - /* first element */ - if (NULL == qle->prev) - qll->head = qle; - /* last element */ - if (NULL == qle->next) - qll->tail = qle; - - qll->sum2 = qll->sum1 - qll->head->count; - - quantile_llist_index_update(qll, qle, idx); - - RASTER_DEBUGF(5, "qle, head, tail = %p, %p, %p", qle, qll->head, qll->tail); - - } - } - else { - qle = qll->tail; - while (NULL != qle) { - if (qle->value < value) { - qle->count++; - qll->sum1++; - qll->sum2 = qll->sum1 - qll->head->count; - RASTER_DEBUGF(4, "incremented count of %f by 1 to %d", qle->value, qle->count); - break; - } - - qle = qle->prev; - } - } - } - /* AL-GT */ - else { - RASTER_DEBUGF(4, "value, tail->value = %f, %f", value, qll->tail->value); - - if (value > qll->tail->value) { - /* ignore value if test isn't true */ - if (qll->sum1 >= qll->tau) { - RASTER_DEBUGF(4, "skipping %f", value); - } - else { - - /* delete last element */ - RASTER_DEBUGF(4, "deleting %f from list", qll->head->value); - qle = qll->head->next; - RASTER_DEBUGF(5, "to-be tail is %f with count %d", qle->value, qle->count); - qle->count += qll->head->count; - quantile_llist_index_delete(qll, qll->head); - quantile_llist_delete(qll->head); - qll->head = qle; - qll->count--; - quantile_llist_index_update(qll, NULL, 0); - RASTER_DEBUGF(5, "tail is %f with count %d", qll->head->value, qll->head->count); - - /* insert value */ - RASTER_DEBUGF(4, "adding %f to list", value); - /* OPTIMIZATION: check to see if value exceeds last value */ - if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { - idx = qll->count; - qle = quantile_llist_insert(qll->tail, value, &idx); - } - /* OPTIMIZATION: use index if possible */ - else { - qls = quantile_llist_index_search(qll, value, &idx); - qle = quantile_llist_insert(qls, value, &idx); - } - if (NULL == qle) return NULL; - RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); - qll->count++; - qll->sum1++; - - /* first element */ - if (NULL == qle->prev) - qll->head = qle; - /* last element */ - if (NULL == qle->next) - qll->tail = qle; - - qll->sum2 = qll->sum1 - qll->tail->count; - - quantile_llist_index_update(qll, qle, idx); - - RASTER_DEBUGF(5, "qle, head, tail = %p, %p, %p", qle, qll->head, qll->tail); - - } - } - else { - qle = qll->head; - while (NULL != qle) { - if (qle->value > value) { - qle->count++; - qll->sum1++; - qll->sum2 = qll->sum1 - qll->tail->count; - RASTER_DEBUGF(4, "incremented count of %f by 1 to %d", qle->value, qle->count); - break; - } - - qle = qle->next; - } - } - } - - RASTER_DEBUGF(5, "sum2, tau = %d, %d", qll->sum2, qll->tau); - if (qll->sum2 >= qll->tau) { - /* AL-GEQ */ - if (qll->algeq) { - RASTER_DEBUGF(4, "deleting first element %f from list", qll->head->value); - - if (NULL != qll->head->next) { - qle = qll->head->next; - qll->sum1 -= qll->head->count; - qll->sum2 = qll->sum1 - qle->count; - quantile_llist_index_delete(qll, qll->head); - quantile_llist_delete(qll->head); - qll->head = qle; - qll->count--; - - quantile_llist_index_update(qll, NULL, 0); - } - else { - quantile_llist_delete(qll->head); - qll->head = NULL; - qll->tail = NULL; - qll->sum1 = 0; - qll->sum2 = 0; - qll->count = 0; - - quantile_llist_index_reset(qll); - } - } - /* AL-GT */ - else { - RASTER_DEBUGF(4, "deleting first element %f from list", qll->tail->value); - - if (NULL != qll->tail->prev) { - qle = qll->tail->prev; - qll->sum1 -= qll->tail->count; - qll->sum2 = qll->sum1 - qle->count; - quantile_llist_index_delete(qll, qll->tail); - quantile_llist_delete(qll->tail); - qll->tail = qle; - qll->count--; - } - else { - quantile_llist_delete(qll->tail); - qll->head = NULL; - qll->tail = NULL; - qll->sum1 = 0; - qll->sum2 = 0; - qll->count = 0; - - quantile_llist_index_reset(qll); - } - } - } - - RASTER_DEBUGF(5, "qll after: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", - qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); - RASTER_DEBUGF(5, "qll after: (head, tail) = (%p, %p)\n\n", qll->head, qll->tail); - } - - } - else { - RASTER_DEBUGF(5, "skipping value at (x, y) = (%d, %d)", x, y); - } - - z++; - } - } - - /* process quantiles */ - *rtn_count = *qlls_count / 2; - rtn = rtalloc(sizeof(struct rt_quantile_t) * *rtn_count); - if (NULL == rtn) return NULL; - - RASTER_DEBUGF(3, "returning %d quantiles", *rtn_count); - for (i = 0, k = 0; i < *qlls_count; i++) { - qll = &((*qlls)[i]); - - exists = 0; - for (x = 0; x < k; x++) { - if (FLT_EQ(qll->quantile, rtn[x].quantile)) { - exists = 1; - break; - } - } - if (exists) continue; - - RASTER_DEBUGF(5, "qll: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", - qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); - RASTER_DEBUGF(5, "qll: (head, tail) = (%p, %p)", qll->head, qll->tail); - - rtn[k].quantile = qll->quantile; - rtn[k].has_value = 0; - - /* check that qll->head and qll->tail have value */ - if (qll->head == NULL || qll->tail == NULL) - continue; - - /* AL-GEQ */ - if (qll->algeq) - qle = qll->head; - /* AM-GT */ - else - qle = qll->tail; - - exists = 0; - for (j = i + 1; j < *qlls_count; j++) { - if (FLT_EQ((*qlls)[j].quantile, qll->quantile)) { - - RASTER_DEBUGF(5, "qlls[%d]: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", - j, (*qlls)[j].algeq, (*qlls)[j].quantile, (*qlls)[j].count, (*qlls)[j].tau, (*qlls)[j].sum1, (*qlls)[j].sum2); - RASTER_DEBUGF(5, "qlls[%d]: (head, tail) = (%p, %p)", j, (*qlls)[j].head, (*qlls)[j].tail); - - exists = 1; - break; - } - } - - /* weighted average for quantile */ - if (exists) { - if ((*qlls)[j].algeq) { - rtn[k].value = ((qle->value * qle->count) + ((*qlls)[j].head->value * (*qlls)[j].head->count)) / (qle->count + (*qlls)[j].head->count); - RASTER_DEBUGF(5, "qlls[%d].head: (value, count) = (%f, %d)", j, (*qlls)[j].head->value, (*qlls)[j].head->count); - } - else { - rtn[k].value = ((qle->value * qle->count) + ((*qlls)[j].tail->value * (*qlls)[j].tail->count)) / (qle->count + (*qlls)[j].tail->count); - RASTER_DEBUGF(5, "qlls[%d].tail: (value, count) = (%f, %d)", j, (*qlls)[j].tail->value, (*qlls)[j].tail->count); - } - } - /* straight value for quantile */ - else { - rtn[k].value = qle->value; - } - rtn[k].has_value = 1; - RASTER_DEBUGF(3, "(quantile, value) = (%f, %f)\n\n", rtn[k].quantile, rtn[k].value); - - k++; - } - - RASTER_DEBUG(3, "done"); - return rtn; -} - -/** - * Count the number of times provided value(s) occur in - * the band - * - * @param band : the band to query for minimum and maximum pixel values - * @param exclude_nodata_value : if non-zero, ignore nodata values - * @param search_values : array of values to count - * @param search_values_count : the number of search values - * @param roundto : the decimal place to round the values to - * @param rtn_total : the number of pixels examined in the band - * @param rtn_count : the number of value counts being returned - * - * @return the number of times the provide value(s) occur or NULL - */ -rt_valuecount -rt_band_get_value_count( - rt_band band, int exclude_nodata_value, - double *search_values, uint32_t search_values_count, double roundto, - uint32_t *rtn_total, uint32_t *rtn_count -) { - rt_valuecount vcnts = NULL; - rt_pixtype pixtype = PT_END; - uint8_t *data = NULL; - double nodata = 0; - - int scale = 0; - int doround = 0; - double tmpd = 0; - int i = 0; - - uint32_t x = 0; - uint32_t y = 0; - int rtn; - double pxlval; - int isnodata = 0; - double rpxlval; - uint32_t total = 0; - int vcnts_count = 0; - int new_valuecount = 0; - -#if POSTGIS_DEBUG_LEVEL > 0 - clock_t start, stop; - double elapsed = 0; -#endif - - RASTER_DEBUG(3, "starting"); -#if POSTGIS_DEBUG_LEVEL > 0 - start = clock(); -#endif - - assert(NULL != band); - assert(NULL != rtn_count); - - data = rt_band_get_data(band); - if (data == NULL) { - rterror("rt_band_get_summary_stats: Cannot get band data"); - return NULL; - } - - pixtype = band->pixtype; - - if (rt_band_get_hasnodata_flag(band)) { - rt_band_get_nodata(band, &nodata); - RASTER_DEBUGF(3, "hasnodata, nodataval = 1, %f", nodata); - } - else { - exclude_nodata_value = 0; - RASTER_DEBUG(3, "hasnodata, nodataval = 0, 0"); - } - - RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); - - /* process roundto */ - if (roundto < 0 || FLT_EQ(roundto, 0.0)) { - roundto = 0; - scale = 0; - } - /* tenths, hundredths, thousandths, etc */ - else if (roundto < 1) { - switch (pixtype) { - /* integer band types don't have digits after the decimal place */ - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BSI: - case PT_8BUI: - case PT_16BSI: - case PT_16BUI: - case PT_32BSI: - case PT_32BUI: - roundto = 0; - break; - /* floating points, check the rounding */ - case PT_32BF: - case PT_64BF: - for (scale = 0; scale <= 20; scale++) { - tmpd = roundto * pow(10, scale); - if (FLT_EQ((tmpd - ((int) tmpd)), 0.0)) break; - } - break; - case PT_END: - break; - } - } - /* ones, tens, hundreds, etc */ - else { - for (scale = 0; scale >= -20; scale--) { - tmpd = roundto * pow(10, scale); - if (tmpd < 1 || FLT_EQ(tmpd, 1.0)) { - if (scale == 0) doround = 1; - break; - } - } - } - - if (scale != 0 || doround) - doround = 1; - else - doround = 0; - RASTER_DEBUGF(3, "scale = %d", scale); - RASTER_DEBUGF(3, "doround = %d", doround); - - /* process search_values */ - if (search_values_count > 0 && NULL != search_values) { - vcnts = (rt_valuecount) rtalloc(sizeof(struct rt_valuecount_t) * search_values_count); - if (NULL == vcnts) { - rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); - *rtn_count = 0; - return NULL; - } - - for (i = 0; i < search_values_count; i++) { - vcnts[i].count = 0; - vcnts[i].percent = 0; - if (!doround) - vcnts[i].value = search_values[i]; - else - vcnts[i].value = ROUND(search_values[i], scale); - } - vcnts_count = i; - } - else - search_values_count = 0; - RASTER_DEBUGF(3, "search_values_count = %d", search_values_count); - - /* entire band is nodata */ - if (rt_band_get_isnodata_flag(band) != FALSE) { - if (exclude_nodata_value) { - rtwarn("All pixels of band have the NODATA value"); - return NULL; - } - else { - if (search_values_count > 0) { - /* check for nodata match */ - for (i = 0; i < search_values_count; i++) { - if (!doround) - tmpd = nodata; - else - tmpd = ROUND(nodata, scale); - - if (FLT_NEQ(tmpd, vcnts[i].value)) - continue; - - vcnts[i].count = band->width * band->height; - if (NULL != rtn_total) *rtn_total = vcnts[i].count; - vcnts->percent = 1.0; - } - - *rtn_count = vcnts_count; - } - /* no defined search values */ - else { - vcnts = (rt_valuecount) rtalloc(sizeof(struct rt_valuecount_t)); - if (NULL == vcnts) { - rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); - *rtn_count = 0; - return NULL; - } - - vcnts->value = nodata; - vcnts->count = band->width * band->height; - if (NULL != rtn_total) *rtn_total = vcnts[i].count; - vcnts->percent = 1.0; - - *rtn_count = 1; - } - - return vcnts; - } - } - - for (x = 0; x < band->width; x++) { - for (y = 0; y < band->height; y++) { - rtn = rt_band_get_pixel(band, x, y, &pxlval, &isnodata); - - /* error getting value, continue */ - if (rtn != ES_NONE) - continue; - - if (!exclude_nodata_value || (exclude_nodata_value && !isnodata)) { - total++; - if (doround) { - rpxlval = ROUND(pxlval, scale); - } - else - rpxlval = pxlval; - RASTER_DEBUGF(5, "(pxlval, rpxlval) => (%0.6f, %0.6f)", pxlval, rpxlval); - - new_valuecount = 1; - /* search for match in existing valuecounts */ - for (i = 0; i < vcnts_count; i++) { - /* match found */ - if (FLT_EQ(vcnts[i].value, rpxlval)) { - vcnts[i].count++; - new_valuecount = 0; - RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[i].value, vcnts[i].count); - break; - } - } - - /* - don't add new valuecount either because - - no need for new one - - user-defined search values - */ - if (!new_valuecount || search_values_count > 0) continue; - - /* add new valuecount */ - vcnts = rtrealloc(vcnts, sizeof(struct rt_valuecount_t) * (vcnts_count + 1)); - if (NULL == vcnts) { - rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); - *rtn_count = 0; - return NULL; - } - - vcnts[vcnts_count].value = rpxlval; - vcnts[vcnts_count].count = 1; - vcnts[vcnts_count].percent = 0; - RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[vcnts_count].value, vcnts[vcnts_count].count); - vcnts_count++; - } - } - } - -#if POSTGIS_DEBUG_LEVEL > 0 - stop = clock(); - elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; - RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); -#endif - - for (i = 0; i < vcnts_count; i++) { - vcnts[i].percent = (double) vcnts[i].count / total; - RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[i].value, vcnts[i].count); - } - - RASTER_DEBUG(3, "done"); - if (NULL != rtn_total) *rtn_total = total; - *rtn_count = vcnts_count; - return vcnts; -} - -/** - * Returns new band with values reclassified - * - * @param srcband : the band who's values will be reclassified - * @param pixtype : pixel type of the new band - * @param hasnodata : indicates if the band has a nodata value - * @param nodataval : nodata value for the new band - * @param exprset : array of rt_reclassexpr structs - * @param exprcount : number of elements in expr - * - * @return a new rt_band or NULL on error - */ -rt_band -rt_band_reclass( - rt_band srcband, rt_pixtype pixtype, - uint32_t hasnodata, double nodataval, - rt_reclassexpr *exprset, int exprcount -) { - rt_band band = NULL; - uint32_t width = 0; - uint32_t height = 0; - int numval = 0; - int memsize = 0; - void *mem = NULL; - uint32_t src_hasnodata = 0; - double src_nodataval = 0.0; - int isnodata = 0; - - int rtn; - uint32_t x; - uint32_t y; - int i; - double or = 0; - double ov = 0; - double nr = 0; - double nv = 0; - int do_nv = 0; - rt_reclassexpr expr = NULL; - - assert(NULL != srcband); - assert(NULL != exprset && exprcount > 0); - RASTER_DEBUGF(4, "exprcount = %d", exprcount); - RASTER_DEBUGF(4, "exprset @ %p", exprset); - - /* source nodata */ - src_hasnodata = rt_band_get_hasnodata_flag(srcband); - if (src_hasnodata) - rt_band_get_nodata(srcband, &src_nodataval); - - /* size of memory block to allocate */ - width = rt_band_get_width(srcband); - height = rt_band_get_height(srcband); - numval = width * height; - memsize = rt_pixtype_size(pixtype) * numval; - mem = (int *) rtalloc(memsize); - if (!mem) { - rterror("rt_band_reclass: Could not allocate memory for band"); - return 0; - } - - /* initialize to zero */ - if (!hasnodata) { - memset(mem, 0, memsize); - } - /* initialize to nodataval */ - else { - int32_t checkvalint = 0; - uint32_t checkvaluint = 0; - double checkvaldouble = 0; - float checkvalfloat = 0; - - switch (pixtype) { - case PT_1BB: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_1BB(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_2BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_2BUI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_4BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_4BUI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_8BSI: - { - int8_t *ptr = mem; - int8_t clamped_initval = rt_util_clamp_to_8BSI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_8BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_8BUI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_16BSI: - { - int16_t *ptr = mem; - int16_t clamped_initval = rt_util_clamp_to_16BSI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_16BUI: - { - uint16_t *ptr = mem; - uint16_t clamped_initval = rt_util_clamp_to_16BUI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_32BSI: - { - int32_t *ptr = mem; - int32_t clamped_initval = rt_util_clamp_to_32BSI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_32BUI: - { - uint32_t *ptr = mem; - uint32_t clamped_initval = rt_util_clamp_to_32BUI(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvaluint = ptr[0]; - break; - } - case PT_32BF: - { - float *ptr = mem; - float clamped_initval = rt_util_clamp_to_32F(nodataval); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalfloat = ptr[0]; - break; - } - case PT_64BF: - { - double *ptr = mem; - for (i = 0; i < numval; i++) - ptr[i] = nodataval; - checkvaldouble = ptr[0]; - break; - } - default: - { - rterror("rt_band_reclass: Unknown pixeltype %d", pixtype); - rtdealloc(mem); - return 0; - } - } - - /* Overflow checking */ - rt_util_dbl_trunc_warning( - nodataval, - checkvalint, checkvaluint, - checkvalfloat, checkvaldouble, - pixtype - ); - } - RASTER_DEBUGF(3, "rt_band_reclass: width = %d height = %d", width, height); - - band = rt_band_new_inline(width, height, pixtype, hasnodata, nodataval, mem); - if (!band) { - rterror("rt_band_reclass: Could not create new band"); - rtdealloc(mem); - return 0; - } - rt_band_set_ownsdata_flag(band, 1); /* we DO own this data!!! */ - RASTER_DEBUGF(3, "rt_band_reclass: new band @ %p", band); - - for (x = 0; x < width; x++) { - for (y = 0; y < height; y++) { - rtn = rt_band_get_pixel(srcband, x, y, &ov, &isnodata); - - /* error getting value, skip */ - if (rtn != ES_NONE) { - RASTER_DEBUGF(3, "Cannot get value at %d, %d", x, y); - continue; - } - - do { - do_nv = 0; - - /* no data*/ - if (hasnodata && isnodata) { - do_nv = 1; - break; - } - - for (i = 0; i < exprcount; i++) { - expr = exprset[i]; - - /* ov matches min and max*/ - if ( - FLT_EQ(expr->src.min, ov) && - FLT_EQ(expr->src.max, ov) - ) { - do_nv = 1; - break; - } - - /* process min */ - if (( - expr->src.exc_min && ( - expr->src.min > ov || - FLT_EQ(expr->src.min, ov) - )) || ( - expr->src.inc_min && ( - expr->src.min < ov || - FLT_EQ(expr->src.min, ov) - )) || ( - expr->src.min < ov - )) { - /* process max */ - if (( - expr->src.exc_max && ( - ov > expr->src.max || - FLT_EQ(expr->src.max, ov) - )) || ( - expr->src.inc_max && ( - ov < expr->src.max || - FLT_EQ(expr->src.max, ov) - )) || ( - ov < expr->src.max - )) { - do_nv = 1; - break; - } - } - } - } - while (0); - - /* no expression matched, do not continue */ - if (!do_nv) continue; - RASTER_DEBUGF(3, "Using exprset[%d] unless NODATA", i); - - /* converting a value from one range to another range - OldRange = (OldMax - OldMin) - NewRange = (NewMax - NewMin) - NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin - */ - - /* NODATA */ - if (hasnodata && isnodata) { - nv = nodataval; - } - /* - "src" min and max is the same, prevent division by zero - set nv to "dst" min, which should be the same as "dst" max - */ - else if (FLT_EQ(expr->src.max, expr->src.min)) { - nv = expr->dst.min; - } - else { - or = expr->src.max - expr->src.min; - nr = expr->dst.max - expr->dst.min; - nv = (((ov - expr->src.min) * nr) / or) + expr->dst.min; - - /* if dst range is from high to low */ - if (expr->dst.min > expr->dst.max) { - if (nv > expr->dst.min) - nv = expr->dst.min; - else if (nv < expr->dst.max) - nv = expr->dst.max; - } - /* if dst range is from low to high */ - else { - if (nv < expr->dst.min) - nv = expr->dst.min; - else if (nv > expr->dst.max) - nv = expr->dst.max; - } - } - - /* round the value for integers */ - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BSI: - case PT_8BUI: - case PT_16BSI: - case PT_16BUI: - case PT_32BSI: - case PT_32BUI: - nv = round(nv); - break; - default: - break; - } - - RASTER_DEBUGF(4, "(%d, %d) ov: %f or: %f - %f nr: %f - %f nv: %f" - , x - , y - , ov - , (NULL != expr) ? expr->src.min : 0 - , (NULL != expr) ? expr->src.max : 0 - , (NULL != expr) ? expr->dst.min : 0 - , (NULL != expr) ? expr->dst.max : 0 - , nv - ); - if (rt_band_set_pixel(band, x, y, nv, NULL) != ES_NONE) { - rterror("rt_band_reclass: Could not assign value to new band"); - rt_band_destroy(band); - rtdealloc(mem); - return 0; - } - - expr = NULL; - } - } - - return band; -} - -/*- rt_raster --------------------------------------------------------*/ - -/** - * Construct a raster with given dimensions. - * - * Transform will be set to identity. - * Will contain no bands. - * - * @param width : number of pixel columns - * @param height : number of pixel rows - * - * @return an rt_raster or NULL if out of memory - */ -rt_raster -rt_raster_new(uint32_t width, uint32_t height) { - rt_raster ret = NULL; - - ret = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); - if (!ret) { - rterror("rt_raster_new: Out of virtual memory creating an rt_raster"); - return NULL; - } - - RASTER_DEBUGF(3, "Created rt_raster @ %p", ret); - - if (width > 65535 || height > 65535) { - rterror("rt_raster_new: Dimensions requested exceed the maximum (65535 x 65535) permitted for a raster"); - rt_raster_destroy(ret); - return NULL; - } - - ret->width = width; - ret->height = height; - ret->scaleX = 1; - ret->scaleY = -1; - ret->ipX = 0.0; - ret->ipY = 0.0; - ret->skewX = 0.0; - ret->skewY = 0.0; - ret->srid = SRID_UNKNOWN; - - ret->numBands = 0; - ret->bands = NULL; - - return ret; -} - -void -rt_raster_destroy(rt_raster raster) { - if (raster == NULL) - return; - - RASTER_DEBUGF(3, "Destroying rt_raster @ %p", raster); - - if (raster->bands) - rtdealloc(raster->bands); - - rtdealloc(raster); -} - -static void -_rt_raster_geotransform_warn_offline_band(rt_raster raster) { - int numband = 0; - int i = 0; - rt_band band = NULL; - - if (raster == NULL) - return; - - numband = rt_raster_get_num_bands(raster); - if (numband < 1) - return; - - for (i = 0; i < numband; i++) { - band = rt_raster_get_band(raster, i); - if (NULL == band) - continue; - - if (!rt_band_is_offline(band)) - continue; - - rtwarn("Changes made to raster geotransform matrix may affect out-db band data. Returned band data may be incorrect"); - break; - } -} - -uint16_t -rt_raster_get_width(rt_raster raster) { - - assert(NULL != raster); - - return raster->width; -} - -uint16_t -rt_raster_get_height(rt_raster raster) { - - assert(NULL != raster); - - return raster->height; -} - -void -rt_raster_set_scale( - rt_raster raster, - double scaleX, double scaleY -) { - assert(NULL != raster); - - raster->scaleX = scaleX; - raster->scaleY = scaleY; - - _rt_raster_geotransform_warn_offline_band(raster); -} - -double -rt_raster_get_x_scale(rt_raster raster) { - - - assert(NULL != raster); - - return raster->scaleX; -} - -double -rt_raster_get_y_scale(rt_raster raster) { - - - assert(NULL != raster); - - return raster->scaleY; -} - -void -rt_raster_set_skews( - rt_raster raster, - double skewX, double skewY -) { - assert(NULL != raster); - - raster->skewX = skewX; - raster->skewY = skewY; - - _rt_raster_geotransform_warn_offline_band(raster); -} - -double -rt_raster_get_x_skew(rt_raster raster) { - - - assert(NULL != raster); - - return raster->skewX; -} - -double -rt_raster_get_y_skew(rt_raster raster) { - - - assert(NULL != raster); - - return raster->skewY; -} - -void -rt_raster_set_offsets( - rt_raster raster, - double x, double y -) { - - assert(NULL != raster); - - raster->ipX = x; - raster->ipY = y; - - _rt_raster_geotransform_warn_offline_band(raster); -} - -double -rt_raster_get_x_offset(rt_raster raster) { - - - assert(NULL != raster); - - return raster->ipX; -} - -double -rt_raster_get_y_offset(rt_raster raster) { - - - assert(NULL != raster); - - return raster->ipY; -} - -void -rt_raster_get_phys_params(rt_raster rast, - double *i_mag, double *j_mag, double *theta_i, double *theta_ij) -{ - double o11, o12, o21, o22 ; /* geotransform coefficients */ - - if (rast == NULL) return ; - if ( (i_mag==NULL) || (j_mag==NULL) || (theta_i==NULL) || (theta_ij==NULL)) - return ; - - /* retrieve coefficients from raster */ - o11 = rt_raster_get_x_scale(rast) ; - o12 = rt_raster_get_x_skew(rast) ; - o21 = rt_raster_get_y_skew(rast) ; - o22 = rt_raster_get_y_scale(rast) ; - - rt_raster_calc_phys_params(o11, o12, o21, o22, i_mag, j_mag, theta_i, theta_ij); -} - -void -rt_raster_calc_phys_params(double xscale, double xskew, double yskew, double yscale, - double *i_mag, double *j_mag, double *theta_i, double *theta_ij) - -{ - double theta_test ; - - if ( (i_mag==NULL) || (j_mag==NULL) || (theta_i==NULL) || (theta_ij==NULL)) - return ; - - /* pixel size in the i direction */ - *i_mag = sqrt(xscale*xscale + yskew*yskew) ; - - /* pixel size in the j direction */ - *j_mag = sqrt(xskew*xskew + yscale*yscale) ; - - /* Rotation - * ======== - * Two steps: - * 1] calculate the magnitude of the angle between the x axis and - * the i basis vector. - * 2] Calculate the sign of theta_i based on the angle between the y axis - * and the i basis vector. - */ - *theta_i = acos(xscale/(*i_mag)) ; /* magnitude */ - theta_test = acos(yskew/(*i_mag)) ; /* sign */ - if (theta_test < M_PI_2){ - *theta_i = -(*theta_i) ; - } - - - /* Angular separation of basis vectors - * =================================== - * Two steps: - * 1] calculate the magnitude of the angle between the j basis vector and - * the i basis vector. - * 2] Calculate the sign of theta_ij based on the angle between the - * perpendicular of the i basis vector and the j basis vector. - */ - *theta_ij = acos(((xscale*xskew) + (yskew*yscale))/((*i_mag)*(*j_mag))) ; - theta_test = acos( ((-yskew*xskew)+(xscale*yscale)) / - ((*i_mag)*(*j_mag))); - if (theta_test > M_PI_2) { - *theta_ij = -(*theta_ij) ; - } -} - -void -rt_raster_set_phys_params(rt_raster rast,double i_mag, double j_mag, double theta_i, double theta_ij) -{ - double o11, o12, o21, o22 ; /* calculated geotransform coefficients */ - int success ; - - if (rast == NULL) return ; - - success = rt_raster_calc_gt_coeff(i_mag, j_mag, theta_i, theta_ij, - &o11, &o12, &o21, &o22) ; - - if (success) { - rt_raster_set_scale(rast, o11, o22) ; - rt_raster_set_skews(rast, o12, o21) ; - } -} - -int -rt_raster_calc_gt_coeff(double i_mag, double j_mag, double theta_i, double theta_ij, - double *xscale, double *xskew, double *yskew, double *yscale) -{ - double f ; /* reflection flag 1.0 or -1.0 */ - double k_i ; /* shearing coefficient */ - double s_i, s_j ; /* scaling coefficients */ - double cos_theta_i, sin_theta_i ; - - if ( (xscale==NULL) || (xskew==NULL) || (yskew==NULL) || (yscale==NULL)) { - return 0; - } - - if ( (theta_ij == 0.0) || (theta_ij == M_PI)) { - return 0; - } - - /* Reflection across the i axis */ - f=1.0 ; - if (theta_ij < 0) { - f = -1.0; - } - - /* scaling along i axis */ - s_i = i_mag ; - - /* shearing parallel to i axis */ - k_i = tan(f*M_PI_2 - theta_ij) ; - - /* scaling along j axis */ - s_j = j_mag / (sqrt(k_i*k_i + 1)) ; - - /* putting it altogether */ - cos_theta_i = cos(theta_i) ; - sin_theta_i = sin(theta_i) ; - *xscale = s_i * cos_theta_i ; - *xskew = k_i * s_j * f * cos_theta_i + s_j * f * sin_theta_i ; - *yskew = -s_i * sin_theta_i ; - *yscale = -k_i * s_j * f * sin_theta_i + s_j * f * cos_theta_i ; - return 1; -} - -int32_t -rt_raster_get_srid(rt_raster raster) { - assert(NULL != raster); - - return clamp_srid(raster->srid); -} - -void -rt_raster_set_srid(rt_raster raster, int32_t srid) { - assert(NULL != raster); - - raster->srid = clamp_srid(srid); - - _rt_raster_geotransform_warn_offline_band(raster); -} - -int -rt_raster_get_num_bands(rt_raster raster) { - - - assert(NULL != raster); - - return raster->numBands; -} - -rt_band -rt_raster_get_band(rt_raster raster, int n) { - assert(NULL != raster); - - if (n >= raster->numBands || n < 0) - return NULL; - - return raster->bands[n]; -} - -/** - * Add band data to a raster. - * - * @param raster : the raster to add a band to - * @param band : the band to add, ownership left to caller. - * Band dimensions are required to match with raster ones. - * @param index : the position where to insert the new band (0 based) - * - * @return identifier (position) for the just-added raster, or -1 on error - */ -int -rt_raster_add_band(rt_raster raster, rt_band band, int index) { - rt_band *oldbands = NULL; - rt_band oldband = NULL; - rt_band tmpband = NULL; - uint16_t i = 0; - - - - assert(NULL != raster); - assert(NULL != band); - - RASTER_DEBUGF(3, "Adding band %p to raster %p", band, raster); - - if (band->width != raster->width || band->height != raster->height) { - rterror("rt_raster_add_band: Can't add a %dx%d band to a %dx%d raster", - band->width, band->height, raster->width, raster->height); - return -1; - } - - if (index > raster->numBands) - index = raster->numBands; - - if (index < 0) - index = 0; - - oldbands = raster->bands; - - RASTER_DEBUGF(3, "Oldbands at %p", oldbands); - - raster->bands = (rt_band*) rtrealloc(raster->bands, - sizeof (rt_band)*(raster->numBands + 1) - ); - - RASTER_DEBUG(3, "Checking bands"); - - if (NULL == raster->bands) { - rterror("rt_raster_add_band: Out of virtual memory " - "reallocating band pointers"); - raster->bands = oldbands; - return -1; - } - - RASTER_DEBUGF(4, "realloc returned %p", raster->bands); - - for (i = 0; i <= raster->numBands; ++i) { - if (i == index) { - oldband = raster->bands[i]; - raster->bands[i] = band; - } else if (i > index) { - tmpband = raster->bands[i]; - raster->bands[i] = oldband; - oldband = tmpband; - } - } - - band->raster = raster; - - raster->numBands++; - - RASTER_DEBUGF(4, "Raster now has %d bands", raster->numBands); - - return index; -} - -/** - * Generate a new inline band and add it to a raster. - * Memory is allocated in this function for band data. - * - * @param raster : the raster to add a band to - * @param pixtype : the pixel type for the new band - * @param initialvalue : initial value for pixels - * @param hasnodata : indicates if the band has a nodata value - * @param nodatavalue : nodata value for the new band - * @param index : position to add the new band in the raster - * - * @return identifier (position) for the just-added raster, or -1 on error - */ -int -rt_raster_generate_new_band( - rt_raster raster, rt_pixtype pixtype, - double initialvalue, uint32_t hasnodata, double nodatavalue, - int index -) { - rt_band band = NULL; - int width = 0; - int height = 0; - int numval = 0; - int datasize = 0; - int oldnumbands = 0; - int numbands = 0; - void * mem = NULL; - int32_t checkvalint = 0; - uint32_t checkvaluint = 0; - double checkvaldouble = 0; - float checkvalfloat = 0; - int i; - - - assert(NULL != raster); - - /* Make sure index is in a valid range */ - oldnumbands = rt_raster_get_num_bands(raster); - if (index < 0) - index = 0; - else if (index > oldnumbands + 1) - index = oldnumbands + 1; - - /* Determine size of memory block to allocate and allocate it */ - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - numval = width * height; - datasize = rt_pixtype_size(pixtype) * numval; - - mem = (int *)rtalloc(datasize); - if (!mem) { - rterror("rt_raster_generate_new_band: Could not allocate memory for band"); - return -1; - } - - if (FLT_EQ(initialvalue, 0.0)) - memset(mem, 0, datasize); - else { - switch (pixtype) - { - case PT_1BB: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_1BB(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_2BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_2BUI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_4BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_4BUI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_8BSI: - { - int8_t *ptr = mem; - int8_t clamped_initval = rt_util_clamp_to_8BSI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_8BUI: - { - uint8_t *ptr = mem; - uint8_t clamped_initval = rt_util_clamp_to_8BUI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_16BSI: - { - int16_t *ptr = mem; - int16_t clamped_initval = rt_util_clamp_to_16BSI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_16BUI: - { - uint16_t *ptr = mem; - uint16_t clamped_initval = rt_util_clamp_to_16BUI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_32BSI: - { - int32_t *ptr = mem; - int32_t clamped_initval = rt_util_clamp_to_32BSI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalint = ptr[0]; - break; - } - case PT_32BUI: - { - uint32_t *ptr = mem; - uint32_t clamped_initval = rt_util_clamp_to_32BUI(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvaluint = ptr[0]; - break; - } - case PT_32BF: - { - float *ptr = mem; - float clamped_initval = rt_util_clamp_to_32F(initialvalue); - for (i = 0; i < numval; i++) - ptr[i] = clamped_initval; - checkvalfloat = ptr[0]; - break; - } - case PT_64BF: - { - double *ptr = mem; - for (i = 0; i < numval; i++) - ptr[i] = initialvalue; - checkvaldouble = ptr[0]; - break; - } - default: - { - rterror("rt_raster_generate_new_band: Unknown pixeltype %d", pixtype); - rtdealloc(mem); - return -1; - } - } - } - - /* Overflow checking */ - rt_util_dbl_trunc_warning( - initialvalue, - checkvalint, checkvaluint, - checkvalfloat, checkvaldouble, - pixtype - ); - - band = rt_band_new_inline(width, height, pixtype, hasnodata, nodatavalue, mem); - if (! band) { - rterror("rt_raster_generate_new_band: Could not add band to raster. Aborting"); - rtdealloc(mem); - return -1; - } - rt_band_set_ownsdata_flag(band, 1); /* we DO own this data!!! */ - index = rt_raster_add_band(raster, band, index); - numbands = rt_raster_get_num_bands(raster); - if (numbands == oldnumbands || index == -1) { - rterror("rt_raster_generate_new_band: Could not add band to raster. Aborting"); - rt_band_destroy(band); - } - - /* set isnodata if hasnodata = TRUE and initial value = nodatavalue */ - if (hasnodata && FLT_EQ(initialvalue, nodatavalue)) - rt_band_set_isnodata_flag(band, 1); - - return index; -} - -/** - * Get 6-element array of raster inverse geotransform matrix - * - * @param raster : the raster to get matrix of - * @param gt : optional input parameter, 6-element geotransform matrix - * @param igt : output parameter, 6-element inverse geotransform matrix - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_get_inverse_geotransform_matrix( - rt_raster raster, - double *gt, double *igt -) { - double _gt[6] = {0}; - - assert((raster != NULL || gt != NULL)); - assert(igt != NULL); - - if (gt == NULL) - rt_raster_get_geotransform_matrix(raster, _gt); - else - memcpy(_gt, gt, sizeof(double) * 6); - - if (!GDALInvGeoTransform(_gt, igt)) { - rterror("rt_raster_get_inverse_geotransform_matrix: Could not compute inverse geotransform matrix"); - return ES_ERROR; - } - - return ES_NONE; -} - -/** - * Get 6-element array of raster geotransform matrix - * - * @param raster : the raster to get matrix of - * @param gt : output parameter, 6-element geotransform matrix - * - */ -void -rt_raster_get_geotransform_matrix(rt_raster raster, - double *gt) { - assert(NULL != raster); - assert(NULL != gt); - - gt[0] = raster->ipX; - gt[1] = raster->scaleX; - gt[2] = raster->skewX; - gt[3] = raster->ipY; - gt[4] = raster->skewY; - gt[5] = raster->scaleY; -} - -/** - * Set raster's geotransform using 6-element array - * - * @param raster : the raster to set matrix of - * @param gt : intput parameter, 6-element geotransform matrix - * - */ -void -rt_raster_set_geotransform_matrix(rt_raster raster, - double *gt) { - assert(NULL != raster); - assert(NULL != gt); - - raster->ipX = gt[0]; - raster->scaleX = gt[1]; - raster->skewX = gt[2]; - raster->ipY = gt[3]; - raster->skewY = gt[4]; - raster->scaleY = gt[5]; - - _rt_raster_geotransform_warn_offline_band(raster); -} - -/** - * Convert an xr, yr raster point to an xw, yw point on map - * - * @param raster : the raster to get info from - * @param xr : the pixel's column - * @param yr : the pixel's row - * @param xw : output parameter, X ordinate of the geographical point - * @param yw : output parameter, Y ordinate of the geographical point - * @param gt : input/output parameter, 3x2 geotransform matrix - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_cell_to_geopoint( - rt_raster raster, - double xr, double yr, - double *xw, double *yw, - double *gt -) { - double _gt[6] = {0}; - - assert(NULL != raster); - assert(NULL != xw && NULL != yw); - - if (NULL != gt) - memcpy(_gt, gt, sizeof(double) * 6); - - /* scale of matrix is not set */ - if ( - FLT_EQ(_gt[1], 0) || - FLT_EQ(_gt[5], 0) - ) { - rt_raster_get_geotransform_matrix(raster, _gt); - } - - RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", - _gt[0], - _gt[1], - _gt[2], - _gt[3], - _gt[4], - _gt[5] - ); - - GDALApplyGeoTransform(_gt, xr, yr, xw, yw); - RASTER_DEBUGF(4, "GDALApplyGeoTransform (c -> g) for (%f, %f) = (%f, %f)", - xr, yr, *xw, *yw); - - return ES_NONE; -} - -/** - * Convert an xw,yw map point to a xr,yr raster point - * - * @param raster : the raster to get info from - * @param xw : X ordinate of the geographical point - * @param yw : Y ordinate of the geographical point - * @param xr : output parameter, the pixel's column - * @param yr : output parameter, the pixel's row - * @param igt : input/output parameter, inverse geotransform matrix - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_geopoint_to_cell( - rt_raster raster, - double xw, double yw, - double *xr, double *yr, - double *igt -) { - double _igt[6] = {0}; - double rnd = 0; - - assert(NULL != raster); - assert(NULL != xr && NULL != yr); - - if (igt != NULL) - memcpy(_igt, igt, sizeof(double) * 6); - - /* matrix is not set */ - if ( - FLT_EQ(_igt[0], 0.) && - FLT_EQ(_igt[1], 0.) && - FLT_EQ(_igt[2], 0.) && - FLT_EQ(_igt[3], 0.) && - FLT_EQ(_igt[4], 0.) && - FLT_EQ(_igt[5], 0.) - ) { - if (rt_raster_get_inverse_geotransform_matrix(raster, NULL, _igt) != ES_NONE) { - rterror("rt_raster_geopoint_to_cell: Could not get inverse geotransform matrix"); - return ES_ERROR; - } - } - - GDALApplyGeoTransform(_igt, xw, yw, xr, yr); - RASTER_DEBUGF(4, "GDALApplyGeoTransform (g -> c) for (%f, %f) = (%f, %f)", - xw, yw, *xr, *yr); - - rnd = ROUND(*xr, 0); - if (FLT_EQ(rnd, *xr)) - *xr = rnd; - else - *xr = floor(*xr); - - rnd = ROUND(*yr, 0); - if (FLT_EQ(rnd, *yr)) - *yr = rnd; - else - *yr = floor(*yr); - - RASTER_DEBUGF(4, "Corrected GDALApplyGeoTransform (g -> c) for (%f, %f) = (%f, %f)", - xw, yw, *xr, *yr); - - return ES_NONE; -} - -/** - * Returns a set of "geomval" value, one for each group of pixel - * sharing the same value for the provided band. - * - * A "geomval" value is a complex type composed of a geometry - * in LWPOLY representation (one for each group of pixel sharing - * the same value) and the value associated with this geometry. - * - * @param raster : the raster to get info from. - * @param nband : the band to polygonize. 0-based - * @param exclude_nodata_value : if non-zero, ignore nodata values - * to check for pixels with value - * - * @return A set of "geomval" values, one for each group of pixels - * sharing the same value for the provided band. The returned values are - * LWPOLY geometries. - */ -rt_geomval -rt_raster_gdal_polygonize( - rt_raster raster, int nband, - int exclude_nodata_value, - int *pnElements -) { - CPLErr cplerr = CE_None; - char *pszQuery; - long j; - OGRSFDriverH ogr_drv = NULL; - GDALDriverH gdal_drv = NULL; - GDALDatasetH memdataset = NULL; - GDALRasterBandH gdal_band = NULL; - OGRDataSourceH memdatasource = NULL; - rt_geomval pols = NULL; - OGRLayerH hLayer = NULL; - OGRFeatureH hFeature = NULL; - OGRGeometryH hGeom = NULL; - OGRFieldDefnH hFldDfn = NULL; - unsigned char *wkb = NULL; - int wkbsize = 0; - LWGEOM *lwgeom = NULL; - int nFeatureCount = 0; - rt_band band = NULL; - int iPixVal = -1; - double dValue = 0.0; - int iBandHasNodataValue = FALSE; - double dBandNoData = 0.0; - - /* for checking that a geometry is valid */ - GEOSGeometry *ggeom = NULL; - int isValid; - LWGEOM *lwgeomValid = NULL; - - uint32_t bandNums[1] = {nband}; - int excludeNodataValues[1] = {exclude_nodata_value}; - - /* checks */ - assert(NULL != raster); - assert(NULL != pnElements); - - RASTER_DEBUG(2, "In rt_raster_gdal_polygonize"); - - *pnElements = 0; - - /******************************* - * Get band - *******************************/ - band = rt_raster_get_band(raster, nband); - if (NULL == band) { - rterror("rt_raster_gdal_polygonize: Error getting band %d from raster", nband); - return NULL; - } - - if (exclude_nodata_value) { - - /* band is NODATA */ - if (rt_band_get_isnodata_flag(band)) { - RASTER_DEBUG(3, "Band is NODATA. Returning null"); - *pnElements = 0; - return NULL; - } - - iBandHasNodataValue = rt_band_get_hasnodata_flag(band); - if (iBandHasNodataValue) - rt_band_get_nodata(band, &dBandNoData); - else - exclude_nodata_value = FALSE; - } - - /***************************************************** - * Convert raster to GDAL MEM dataset - *****************************************************/ - memdataset = rt_raster_to_gdal_mem(raster, NULL, bandNums, excludeNodataValues, 1, &gdal_drv); - if (NULL == memdataset) { - rterror("rt_raster_gdal_polygonize: Couldn't convert raster to GDAL MEM dataset"); - return NULL; - } - - /***************************** - * Register ogr mem driver - *****************************/ - OGRRegisterAll(); - - RASTER_DEBUG(3, "creating OGR MEM vector"); - - /***************************************************** - * Create an OGR in-memory vector for layers - *****************************************************/ - ogr_drv = OGRGetDriverByName("Memory"); - memdatasource = OGR_Dr_CreateDataSource(ogr_drv, "", NULL); - if (NULL == memdatasource) { - rterror("rt_raster_gdal_polygonize: Couldn't create a OGR Datasource to store pols"); - GDALClose(memdataset); - return NULL; - } - - /* Can MEM driver create new layers? */ - if (!OGR_DS_TestCapability(memdatasource, ODsCCreateLayer)) { - rterror("rt_raster_gdal_polygonize: MEM driver can't create new layers, aborting"); - - /* xxx jorgearevalo: what should we do now? */ - GDALClose(memdataset); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - RASTER_DEBUG(3, "polygonizying GDAL MEM raster band"); - - /***************************** - * Polygonize the raster band - *****************************/ - - /** - * From GDALPolygonize function header: "Polygon features will be - * created on the output layer, with polygon geometries representing - * the polygons". So,the WKB geometry type should be "wkbPolygon" - **/ - hLayer = OGR_DS_CreateLayer(memdatasource, "PolygonizedLayer", NULL, wkbPolygon, NULL); - - if (NULL == hLayer) { - rterror("rt_raster_gdal_polygonize: Couldn't create layer to store polygons"); - - GDALClose(memdataset); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - /** - * Create a new field in the layer, to store the px value - */ - - /* First, create a field definition to create the field */ - hFldDfn = OGR_Fld_Create("PixelValue", OFTReal); - - /* Second, create the field */ - if (OGR_L_CreateField(hLayer, hFldDfn, TRUE) != OGRERR_NONE) { - rtwarn("Couldn't create a field in OGR Layer. The polygons generated won't be able to store the pixel value"); - iPixVal = -1; - } - else { - /* Index to the new field created in the layer */ - iPixVal = 0; - } - - /* Get GDAL raster band */ - gdal_band = GDALGetRasterBand(memdataset, 1); - if (NULL == gdal_band) { - rterror("rt_raster_gdal_polygonize: Couldn't get GDAL band to polygonize"); - - GDALClose(memdataset); - OGR_Fld_Destroy(hFldDfn); - OGR_DS_DeleteLayer(memdatasource, 0); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - /** - * We don't need a raster mask band. Each band has a nodata value. - **/ -#ifdef GDALFPOLYGONIZE - cplerr = GDALFPolygonize(gdal_band, NULL, hLayer, iPixVal, NULL, NULL, NULL); -#else - cplerr = GDALPolygonize(gdal_band, NULL, hLayer, iPixVal, NULL, NULL, NULL); -#endif - - if (cplerr != CE_None) { - rterror("rt_raster_gdal_polygonize: Could not polygonize GDAL band"); - - GDALClose(memdataset); - OGR_Fld_Destroy(hFldDfn); - OGR_DS_DeleteLayer(memdatasource, 0); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - /** - * Optimization: Apply a OGR SQL filter to the layer to select the - * features different from NODATA value. - * - * Thanks to David Zwarg. - **/ - if (iBandHasNodataValue) { - pszQuery = (char *) rtalloc(50 * sizeof (char)); - sprintf(pszQuery, "PixelValue != %f", dBandNoData ); - OGRErr e = OGR_L_SetAttributeFilter(hLayer, pszQuery); - if (e != OGRERR_NONE) { - rtwarn("Error filtering NODATA values for band. All values will be treated as data values"); - } - } - else { - pszQuery = NULL; - } - - /********************************************************************* - * Transform OGR layers to WKB polygons - * XXX jorgearevalo: GDALPolygonize does not set the coordinate system - * on the output layer. Application code should do this when the layer - * is created, presumably matching the raster coordinate system. - *********************************************************************/ - nFeatureCount = OGR_L_GetFeatureCount(hLayer, TRUE); - - /* Allocate memory for pols */ - pols = (rt_geomval) rtalloc(nFeatureCount * sizeof(struct rt_geomval_t)); - - if (NULL == pols) { - rterror("rt_raster_gdal_polygonize: Could not allocate memory for geomval set"); - - GDALClose(memdataset); - OGR_Fld_Destroy(hFldDfn); - OGR_DS_DeleteLayer(memdatasource, 0); - if (NULL != pszQuery) - rtdealloc(pszQuery); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - /* initialize GEOS */ - initGEOS(lwnotice, lwgeom_geos_error); - - RASTER_DEBUGF(3, "storing polygons (%d)", nFeatureCount); - - /* Reset feature reading to start in the first feature */ - OGR_L_ResetReading(hLayer); - - for (j = 0; j < nFeatureCount; j++) { - hFeature = OGR_L_GetNextFeature(hLayer); - dValue = OGR_F_GetFieldAsDouble(hFeature, iPixVal); - - hGeom = OGR_F_GetGeometryRef(hFeature); - wkbsize = OGR_G_WkbSize(hGeom); - - /* allocate wkb buffer */ - wkb = rtalloc(sizeof(unsigned char) * wkbsize); - if (wkb == NULL) { - rterror("rt_raster_gdal_polygonize: Could not allocate memory for WKB buffer"); - - OGR_F_Destroy(hFeature); - GDALClose(memdataset); - OGR_Fld_Destroy(hFldDfn); - OGR_DS_DeleteLayer(memdatasource, 0); - if (NULL != pszQuery) - rtdealloc(pszQuery); - OGRReleaseDataSource(memdatasource); - - return NULL; - } - - /* export WKB with LSB byte order */ - OGR_G_ExportToWkb(hGeom, wkbNDR, wkb); - - /* convert WKB to LWGEOM */ - lwgeom = lwgeom_from_wkb(wkb, wkbsize, LW_PARSER_CHECK_NONE); - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = NULL; - OGR_G_ExportToWkt(hGeom, &wkt); - RASTER_DEBUGF(4, "GDAL wkt = %s", wkt); - CPLFree(wkt); - - d_print_binary_hex("GDAL wkb", wkb, wkbsize); - } -#endif - - /* cleanup unnecessary stuff */ - rtdealloc(wkb); - wkb = NULL; - wkbsize = 0; - - OGR_F_Destroy(hFeature); - - /* specify SRID */ - lwgeom_set_srid(lwgeom, rt_raster_get_srid(raster)); - - /* - is geometry valid? - if not, try to make valid - */ - do { -#if POSTGIS_GEOS_VERSION < 33 - /* nothing can be done if the geometry was invalid if GEOS < 3.3 */ - break; -#endif - - ggeom = (GEOSGeometry *) LWGEOM2GEOS(lwgeom); - if (ggeom == NULL) { - rtwarn("Cannot test geometry for validity"); - break; - } - - isValid = GEOSisValid(ggeom); - - GEOSGeom_destroy(ggeom); - ggeom = NULL; - - /* geometry is valid */ - if (isValid) - break; - - RASTER_DEBUG(3, "fixing invalid geometry"); - - /* make geometry valid */ - lwgeomValid = lwgeom_make_valid(lwgeom); - if (lwgeomValid == NULL) { - rtwarn("Cannot fix invalid geometry"); - break; - } - - lwgeom_free(lwgeom); - lwgeom = lwgeomValid; - } - while (0); - - /* save lwgeom */ - pols[j].geom = lwgeom_as_lwpoly(lwgeom); - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(lwgeom, WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "LWGEOM wkt = %s", wkt); - rtdealloc(wkt); - - size_t lwwkbsize = 0; - uint8_t *lwwkb = lwgeom_to_wkb(lwgeom, WKB_ISO | WKB_NDR, &lwwkbsize); - if (lwwkbsize) { - d_print_binary_hex("LWGEOM wkb", lwwkb, lwwkbsize); - rtdealloc(lwwkb); - } - } -#endif - - /* set pixel value */ - pols[j].val = dValue; - } - - *pnElements = nFeatureCount; - - RASTER_DEBUG(3, "destroying GDAL MEM raster"); - GDALClose(memdataset); - - RASTER_DEBUG(3, "destroying OGR MEM vector"); - OGR_Fld_Destroy(hFldDfn); - OGR_DS_DeleteLayer(memdatasource, 0); - if (NULL != pszQuery) rtdealloc(pszQuery); - OGRReleaseDataSource(memdatasource); - - return pols; -} - -/** - * Get raster's convex hull. - * - * The convex hull is typically a 4 vertices (5 to be closed) - * single ring polygon bearing the raster's rotation and using - * projection coordinates. - * - * @param raster : the raster to get info from - * @param **hull : pointer to convex hull - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_get_convex_hull(rt_raster raster, LWGEOM **hull) { - double gt[6] = {0.0}; - int srid = SRID_UNKNOWN; - - POINTARRAY *pts = NULL; - POINT4D p4d; - - assert(hull != NULL); - *hull = NULL; - - /* raster is NULL, convex hull is NULL */ - if (raster == NULL) - return ES_NONE; - - /* raster metadata */ - srid = rt_raster_get_srid(raster); - rt_raster_get_geotransform_matrix(raster, gt); - - RASTER_DEBUGF(3, "rt_raster_get_convex_hull: raster is %dx%d", raster->width, raster->height); - - /* return point or line since at least one of the two dimensions is 0 */ - if ((!raster->width) || (!raster->height)) { - p4d.x = gt[0]; - p4d.y = gt[3]; - - /* return point */ - if (!raster->width && !raster->height) { - LWPOINT *point = lwpoint_make2d(srid, p4d.x, p4d.y); - *hull = lwpoint_as_lwgeom(point); - } - /* return linestring */ - else { - LWLINE *line = NULL; - pts = ptarray_construct_empty(0, 0, 2); - - /* first point of line */ - ptarray_append_point(pts, &p4d, LW_TRUE); - - /* second point of line */ - if (rt_raster_cell_to_geopoint( - raster, - rt_raster_get_width(raster), rt_raster_get_height(raster), - &p4d.x, &p4d.y, - gt - ) != ES_NONE) { - rterror("rt_raster_get_convex_hull: Could not get second point for linestring"); - return ES_ERROR; - } - ptarray_append_point(pts, &p4d, LW_TRUE); - line = lwline_construct(srid, NULL, pts); - - *hull = lwline_as_lwgeom(line); - } - - return ES_NONE; - } - else { - POINTARRAY **rings = NULL; - LWPOLY* poly = NULL; - - /* only one ring */ - rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); - if (!rings) { - rterror("rt_raster_get_convex_hull: Could not allocate memory for polygon ring"); - return ES_ERROR; - } - rings[0] = ptarray_construct(0, 0, 5); - /* TODO: handle error on ptarray construction */ - /* XXX jorgearevalo: the error conditions aren't managed in ptarray_construct */ - if (!rings[0]) { - rterror("rt_raster_get_convex_hull: Could not construct point array"); - return ES_ERROR; - } - pts = rings[0]; - - /* Upper-left corner (first and last points) */ - p4d.x = gt[0]; - p4d.y = gt[3]; - ptarray_set_point4d(pts, 0, &p4d); - ptarray_set_point4d(pts, 4, &p4d); - - /* Upper-right corner (we go clockwise) */ - rt_raster_cell_to_geopoint( - raster, - raster->width, 0, - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 1, &p4d); - - /* Lower-right corner */ - rt_raster_cell_to_geopoint( - raster, - raster->width, raster->height, - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 2, &p4d); - - /* Lower-left corner */ - rt_raster_cell_to_geopoint( - raster, - 0, raster->height, - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 3, &p4d); - - poly = lwpoly_construct(srid, 0, 1, rings); - *hull = lwpoly_as_lwgeom(poly); - } - - return ES_NONE; -} - -/** - * Get raster's envelope. - * - * The envelope is the minimum bounding rectangle of the raster - * - * @param raster : the raster to get envelope of - * @param env : pointer to rt_envelope - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_get_envelope( - rt_raster raster, - rt_envelope *env -) { - int i; - int rtn; - int set = 0; - double _r[2] = {0.}; - double _w[2] = {0.}; - double _gt[6] = {0.}; - - assert(raster != NULL); - assert(env != NULL); - - rt_raster_get_geotransform_matrix(raster, _gt); - - for (i = 0; i < 4; i++) { - switch (i) { - case 0: - _r[0] = 0; - _r[1] = 0; - break; - case 1: - _r[0] = 0; - _r[1] = raster->height; - break; - case 2: - _r[0] = raster->width; - _r[1] = raster->height; - break; - case 3: - _r[0] = raster->width; - _r[1] = 0; - break; - } - - rtn = rt_raster_cell_to_geopoint( - raster, - _r[0], _r[1], - &(_w[0]), &(_w[1]), - _gt - ); - if (rtn != ES_NONE) { - rterror("rt_raster_get_envelope: Could not compute spatial coordinates for raster pixel"); - return ES_ERROR; - } - - if (!set) { - set = 1; - env->MinX = _w[0]; - env->MaxX = _w[0]; - env->MinY = _w[1]; - env->MaxY = _w[1]; - } - else { - if (_w[0] < env->MinX) - env->MinX = _w[0]; - else if (_w[0] > env->MaxX) - env->MaxX = _w[0]; - - if (_w[1] < env->MinY) - env->MinY = _w[1]; - else if (_w[1] > env->MaxY) - env->MaxY = _w[1]; - } - } - - return ES_NONE; -} - -/* - * Compute skewed extent that covers unskewed extent. - * - * @param envelope : unskewed extent of type rt_envelope - * @param skew : pointer to 2-element array (x, y) of skew - * @param scale : pointer to 2-element array (x, y) of scale - * @param tolerance : value between 0 and 1 where the smaller the tolerance - * results in an extent approaching the "minimum" skewed extent. - * If value <= 0, tolerance = 0.1. If value > 1, tolerance = 1. - * - * @return skewed raster who's extent covers unskewed extent, NULL on error - */ -rt_raster -rt_raster_compute_skewed_raster( - rt_envelope extent, - double *skew, - double *scale, - double tolerance -) { - uint32_t run = 0; - uint32_t max_run = 1; - double dbl_run = 0; - - int rtn; - int covers = 0; - rt_raster raster; - double _gt[6] = {0}; - double _igt[6] = {0}; - int _d[2] = {1, -1}; - int _dlast = 0; - int _dlastpos = 0; - double _w[2] = {0}; - double _r[2] = {0}; - double _xy[2] = {0}; - int i; - int j; - int x; - int y; - - LWGEOM *geom = NULL; - GEOSGeometry *sgeom = NULL; - GEOSGeometry *ngeom = NULL; - - if ( - (tolerance < 0.) || - FLT_EQ(tolerance, 0.) - ) { - tolerance = 0.1; - } - else if (tolerance > 1.) - tolerance = 1; - - dbl_run = tolerance; - while (dbl_run < 10) { - dbl_run *= 10.; - max_run *= 10; - } - - /* scale must be provided */ - if (scale == NULL) - return NULL; - for (i = 0; i < 2; i++) { - if (FLT_EQ(scale[i], 0)) { - rterror("rt_raster_compute_skewed_raster: Scale cannot be zero"); - return 0; - } - - if (i < 1) - _gt[1] = fabs(scale[i] * tolerance); - else - _gt[5] = fabs(scale[i] * tolerance); - } - /* conform scale-y to be negative */ - _gt[5] *= -1; - - /* skew not provided or skew is zero, return raster of correct dim and spatial attributes */ - if ( - (skew == NULL) || ( - FLT_EQ(skew[0], 0) && - FLT_EQ(skew[1], 0) - ) - ) { - int _dim[2] = { - (int) fmax((fabs(extent.MaxX - extent.MinX) + (fabs(scale[0]) / 2.)) / fabs(scale[0]), 1), - (int) fmax((fabs(extent.MaxY - extent.MinY) + (fabs(scale[1]) / 2.)) / fabs(scale[1]), 1) - }; - - raster = rt_raster_new(_dim[0], _dim[1]); - if (raster == NULL) { - rterror("rt_raster_compute_skewed_raster: Could not create output raster"); - return NULL; - } - - rt_raster_set_offsets(raster, extent.MinX, extent.MaxY); - rt_raster_set_scale(raster, fabs(scale[0]), -1 * fabs(scale[1])); - rt_raster_set_skews(raster, skew[0], skew[1]); - - return raster; - } - - /* direction to shift upper-left corner */ - if (skew[0] > 0.) - _d[0] = -1; - if (skew[1] < 0.) - _d[1] = 1; - - /* geotransform */ - _gt[0] = extent.UpperLeftX; - _gt[2] = skew[0] * tolerance; - _gt[3] = extent.UpperLeftY; - _gt[4] = skew[1] * tolerance; - - RASTER_DEBUGF(4, "Initial geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5] - ); - RASTER_DEBUGF(4, "Delta: %d, %d", _d[0], _d[1]); - - /* simple raster */ - if ((raster = rt_raster_new(1, 1)) == NULL) { - rterror("rt_raster_compute_skewed_raster: Out of memory allocating extent raster"); - return NULL; - } - rt_raster_set_geotransform_matrix(raster, _gt); - - /* get inverse geotransform matrix */ - if (!GDALInvGeoTransform(_gt, _igt)) { - rterror("rt_raster_compute_skewed_raster: Could not compute inverse geotransform matrix"); - rt_raster_destroy(raster); - return NULL; - } - RASTER_DEBUGF(4, "Inverse geotransform: %f, %f, %f, %f, %f, %f", - _igt[0], _igt[1], _igt[2], _igt[3], _igt[4], _igt[5] - ); - - /* shift along axis */ - for (i = 0; i < 2; i++) { - covers = 0; - run = 0; - - RASTER_DEBUGF(3, "Shifting along %s axis", i < 1 ? "X" : "Y"); - - do { - - /* prevent possible infinite loop */ - if (run > max_run) { - rterror("rt_raster_compute_skewed_raster: Could not compute skewed extent due to check preventing infinite loop"); - rt_raster_destroy(raster); - return NULL; - } - - /* - check the four corners that they are covered along the specific axis - pixel column should be >= 0 - */ - for (j = 0; j < 4; j++) { - switch (j) { - /* upper-left */ - case 0: - _xy[0] = extent.MinX; - _xy[1] = extent.MaxY; - break; - /* lower-left */ - case 1: - _xy[0] = extent.MinX; - _xy[1] = extent.MinY; - break; - /* lower-right */ - case 2: - _xy[0] = extent.MaxX; - _xy[1] = extent.MinY; - break; - /* upper-right */ - case 3: - _xy[0] = extent.MaxX; - _xy[1] = extent.MaxY; - break; - } - - rtn = rt_raster_geopoint_to_cell( - raster, - _xy[0], _xy[1], - &(_r[0]), &(_r[1]), - _igt - ); - if (rtn != ES_NONE) { - rterror("rt_raster_compute_skewed_raster: Could not compute raster pixel for spatial coordinates"); - rt_raster_destroy(raster); - return NULL; - } - - RASTER_DEBUGF(4, "Point %d at cell %d x %d", j, (int) _r[0], (int) _r[1]); - - /* raster doesn't cover point */ - if ((int) _r[i] < 0) { - RASTER_DEBUGF(4, "Point outside of skewed extent: %d", j); - covers = 0; - - if (_dlastpos != j) { - _dlast = (int) _r[i]; - _dlastpos = j; - } - else if ((int) _r[i] < _dlast) { - RASTER_DEBUG(4, "Point going in wrong direction. Reversing direction"); - _d[i] *= -1; - _dlastpos = -1; - run = 0; - } - - break; - } - - covers++; - } - - if (!covers) { - x = 0; - y = 0; - if (i < 1) - x = _d[i] * fabs(_r[i]); - else - y = _d[i] * fabs(_r[i]); - - rtn = rt_raster_cell_to_geopoint( - raster, - x, y, - &(_w[0]), &(_w[1]), - _gt - ); - if (rtn != ES_NONE) { - rterror("rt_raster_compute_skewed_raster: Could not compute spatial coordinates for raster pixel"); - rt_raster_destroy(raster); - return NULL; - } - - /* adjust ul */ - if (i < 1) - _gt[0] = _w[i]; - else - _gt[3] = _w[i]; - rt_raster_set_geotransform_matrix(raster, _gt); - RASTER_DEBUGF(4, "Shifted geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5] - ); - - /* get inverse geotransform matrix */ - if (!GDALInvGeoTransform(_gt, _igt)) { - rterror("rt_raster_compute_skewed_raster: Could not compute inverse geotransform matrix"); - rt_raster_destroy(raster); - return NULL; - } - RASTER_DEBUGF(4, "Inverse geotransform: %f, %f, %f, %f, %f, %f", - _igt[0], _igt[1], _igt[2], _igt[3], _igt[4], _igt[5] - ); - } - - run++; - } - while (!covers); - } - - /* covers test */ - rtn = rt_raster_geopoint_to_cell( - raster, - extent.MaxX, extent.MinY, - &(_r[0]), &(_r[1]), - _igt - ); - if (rtn != ES_NONE) { - rterror("rt_raster_compute_skewed_raster: Could not compute raster pixel for spatial coordinates"); - rt_raster_destroy(raster); - return NULL; - } - - RASTER_DEBUGF(4, "geopoint %f x %f at cell %d x %d", extent.MaxX, extent.MinY, (int) _r[0], (int) _r[1]); - - raster->width = _r[0]; - raster->height = _r[1]; - - /* initialize GEOS */ - initGEOS(lwnotice, lwgeom_geos_error); - - /* create reference LWPOLY */ - { - LWPOLY *npoly = rt_util_envelope_to_lwpoly(extent); - if (npoly == NULL) { - rterror("rt_raster_compute_skewed_raster: Could not build extent's geometry for covers test"); - rt_raster_destroy(raster); - return NULL; - } - - ngeom = (GEOSGeometry *) LWGEOM2GEOS(lwpoly_as_lwgeom(npoly)); - lwpoly_free(npoly); - } - - do { - covers = 0; - - /* construct sgeom from raster */ - if ((rt_raster_get_convex_hull(raster, &geom) != ES_NONE) || geom == NULL) { - rterror("rt_raster_compute_skewed_raster: Could not build skewed extent's geometry for covers test"); - GEOSGeom_destroy(ngeom); - rt_raster_destroy(raster); - return NULL; - } - - sgeom = (GEOSGeometry *) LWGEOM2GEOS(geom); - lwgeom_free(geom); - - covers = GEOSRelatePattern(sgeom, ngeom, "******FF*"); - GEOSGeom_destroy(sgeom); - - if (covers == 2) { - rterror("rt_raster_compute_skewed_raster: Could not run covers test"); - GEOSGeom_destroy(ngeom); - rt_raster_destroy(raster); - return NULL; - } - - if (covers) - break; - - raster->width++; - raster->height++; - } - while (!covers); - - RASTER_DEBUGF(4, "Skewed extent does cover normal extent with dimensions %d x %d", raster->width, raster->height); - - raster->width = (int) ((((double) raster->width) * fabs(_gt[1]) + fabs(scale[0] / 2.)) / fabs(scale[0])); - raster->height = (int) ((((double) raster->height) * fabs(_gt[5]) + fabs(scale[1] / 2.)) / fabs(scale[1])); - _gt[1] = fabs(scale[0]); - _gt[5] = -1 * fabs(scale[1]); - _gt[2] = skew[0]; - _gt[4] = skew[1]; - rt_raster_set_geotransform_matrix(raster, _gt); - - /* minimize width/height */ - for (i = 0; i < 2; i++) { - covers = 1; - do { - if (i < 1) - raster->width--; - else - raster->height--; - - /* construct sgeom from raster */ - if ((rt_raster_get_convex_hull(raster, &geom) != ES_NONE) || geom == NULL) { - rterror("rt_raster_compute_skewed_raster: Could not build skewed extent's geometry for minimizing dimensions"); - GEOSGeom_destroy(ngeom); - rt_raster_destroy(raster); - return NULL; - } - - sgeom = (GEOSGeometry *) LWGEOM2GEOS(geom); - lwgeom_free(geom); - - covers = GEOSRelatePattern(sgeom, ngeom, "******FF*"); - GEOSGeom_destroy(sgeom); - - if (covers == 2) { - rterror("rt_raster_compute_skewed_raster: Could not run covers test for minimizing dimensions"); - GEOSGeom_destroy(ngeom); - rt_raster_destroy(raster); - return NULL; - } - - if (!covers) { - if (i < 1) - raster->width++; - else - raster->height++; - - break; - } - } - while (covers); - } - - GEOSGeom_destroy(ngeom); - - return raster; -} - -/*--------- WKB I/O ---------------------------------------------------*/ - -static uint8_t -isMachineLittleEndian(void) { - static int endian_check_int = 1; /* dont modify this!!! */ - /* 0=big endian|xdr -- 1=little endian|ndr */ - return *((uint8_t *) & endian_check_int); -} - -static uint8_t -read_uint8(const uint8_t** from) { - assert(NULL != from); - - return *(*from)++; -} - -/* unused up to now -static void -write_uint8(uint8_t** from, uint8_t v) -{ - assert(NULL != from); - - *(*from)++ = v; -} - */ - -static int8_t -read_int8(const uint8_t** from) { - assert(NULL != from); - - return (int8_t) read_uint8(from); -} - -/* unused up to now -static void -write_int8(uint8_t** from, int8_t v) -{ - assert(NULL != from); - - *(*from)++ = v; -} - */ - -static uint16_t -read_uint16(const uint8_t** from, uint8_t littleEndian) { - uint16_t ret = 0; - - assert(NULL != from); - - if (littleEndian) { - ret = (*from)[0] | - (*from)[1] << 8; - } else { - /* big endian */ - ret = (*from)[0] << 8 | - (*from)[1]; - } - *from += 2; - return ret; -} - -static void -write_uint16(uint8_t** to, uint8_t littleEndian, uint16_t v) { - assert(NULL != to); - - if (littleEndian) { - (*to)[0] = v & 0x00FF; - (*to)[1] = v >> 8; - } else { - (*to)[1] = v & 0x00FF; - (*to)[0] = v >> 8; - } - *to += 2; -} - -static int16_t -read_int16(const uint8_t** from, uint8_t littleEndian) { - assert(NULL != from); - - return read_uint16(from, littleEndian); -} - -/* unused up to now -static void -write_int16(uint8_t** to, uint8_t littleEndian, int16_t v) -{ - assert(NULL != to); - - if ( littleEndian ) - { - (*to)[0] = v & 0x00FF; - (*to)[1] = v >> 8; - } - else - { - (*to)[1] = v & 0x00FF; - (*to)[0] = v >> 8; - } - *to += 2; -} - */ - -static uint32_t -read_uint32(const uint8_t** from, uint8_t littleEndian) { - uint32_t ret = 0; - - assert(NULL != from); - - if (littleEndian) { - ret = (uint32_t) ((*from)[0] & 0xff) | - (uint32_t) ((*from)[1] & 0xff) << 8 | - (uint32_t) ((*from)[2] & 0xff) << 16 | - (uint32_t) ((*from)[3] & 0xff) << 24; - } else { - /* big endian */ - ret = (uint32_t) ((*from)[3] & 0xff) | - (uint32_t) ((*from)[2] & 0xff) << 8 | - (uint32_t) ((*from)[1] & 0xff) << 16 | - (uint32_t) ((*from)[0] & 0xff) << 24; - } - - *from += 4; - return ret; -} - -/* unused up to now -static void -write_uint32(uint8_t** to, uint8_t littleEndian, uint32_t v) -{ - assert(NULL != to); - - if ( littleEndian ) - { - (*to)[0] = v & 0x000000FF; - (*to)[1] = ( v & 0x0000FF00 ) >> 8; - (*to)[2] = ( v & 0x00FF0000 ) >> 16; - (*to)[3] = ( v & 0xFF000000 ) >> 24; - } - else - { - (*to)[3] = v & 0x000000FF; - (*to)[2] = ( v & 0x0000FF00 ) >> 8; - (*to)[1] = ( v & 0x00FF0000 ) >> 16; - (*to)[0] = ( v & 0xFF000000 ) >> 24; - } - *to += 4; -} - */ - -static int32_t -read_int32(const uint8_t** from, uint8_t littleEndian) { - assert(NULL != from); - - return read_uint32(from, littleEndian); -} - -/* unused up to now -static void -write_int32(uint8_t** to, uint8_t littleEndian, int32_t v) -{ - assert(NULL != to); - - if ( littleEndian ) - { - (*to)[0] = v & 0x000000FF; - (*to)[1] = ( v & 0x0000FF00 ) >> 8; - (*to)[2] = ( v & 0x00FF0000 ) >> 16; - (*to)[3] = ( v & 0xFF000000 ) >> 24; - } - else - { - (*to)[3] = v & 0x000000FF; - (*to)[2] = ( v & 0x0000FF00 ) >> 8; - (*to)[1] = ( v & 0x00FF0000 ) >> 16; - (*to)[0] = ( v & 0xFF000000 ) >> 24; - } - *to += 4; -} - */ - -static float -read_float32(const uint8_t** from, uint8_t littleEndian) { - - union { - float f; - uint32_t i; - } ret; - - ret.i = read_uint32(from, littleEndian); - - return ret.f; -} - -/* unused up to now -static void -write_float32(uint8_t** from, uint8_t littleEndian, float f) -{ - union { - float f; - uint32_t i; - } u; - - u.f = f; - write_uint32(from, littleEndian, u.i); -} - */ - -static double -read_float64(const uint8_t** from, uint8_t littleEndian) { - - union { - double d; - uint64_t i; - } ret; - - assert(NULL != from); - - if (littleEndian) { - ret.i = (uint64_t) ((*from)[0] & 0xff) | - (uint64_t) ((*from)[1] & 0xff) << 8 | - (uint64_t) ((*from)[2] & 0xff) << 16 | - (uint64_t) ((*from)[3] & 0xff) << 24 | - (uint64_t) ((*from)[4] & 0xff) << 32 | - (uint64_t) ((*from)[5] & 0xff) << 40 | - (uint64_t) ((*from)[6] & 0xff) << 48 | - (uint64_t) ((*from)[7] & 0xff) << 56; - } else { - /* big endian */ - ret.i = (uint64_t) ((*from)[7] & 0xff) | - (uint64_t) ((*from)[6] & 0xff) << 8 | - (uint64_t) ((*from)[5] & 0xff) << 16 | - (uint64_t) ((*from)[4] & 0xff) << 24 | - (uint64_t) ((*from)[3] & 0xff) << 32 | - (uint64_t) ((*from)[2] & 0xff) << 40 | - (uint64_t) ((*from)[1] & 0xff) << 48 | - (uint64_t) ((*from)[0] & 0xff) << 56; - } - - *from += 8; - return ret.d; -} - -/* unused up to now -static void -write_float64(uint8_t** to, uint8_t littleEndian, double v) -{ - union { - double d; - uint64_t i; - } u; - - assert(NULL != to); - - u.d = v; - - if ( littleEndian ) - { - (*to)[0] = u.i & 0x00000000000000FFULL; - (*to)[1] = ( u.i & 0x000000000000FF00ULL ) >> 8; - (*to)[2] = ( u.i & 0x0000000000FF0000ULL ) >> 16; - (*to)[3] = ( u.i & 0x00000000FF000000ULL ) >> 24; - (*to)[4] = ( u.i & 0x000000FF00000000ULL ) >> 32; - (*to)[5] = ( u.i & 0x0000FF0000000000ULL ) >> 40; - (*to)[6] = ( u.i & 0x00FF000000000000ULL ) >> 48; - (*to)[7] = ( u.i & 0xFF00000000000000ULL ) >> 56; - } - else - { - (*to)[7] = u.i & 0x00000000000000FFULL; - (*to)[6] = ( u.i & 0x000000000000FF00ULL ) >> 8; - (*to)[5] = ( u.i & 0x0000000000FF0000ULL ) >> 16; - (*to)[4] = ( u.i & 0x00000000FF000000ULL ) >> 24; - (*to)[3] = ( u.i & 0x000000FF00000000ULL ) >> 32; - (*to)[2] = ( u.i & 0x0000FF0000000000ULL ) >> 40; - (*to)[1] = ( u.i & 0x00FF000000000000ULL ) >> 48; - (*to)[0] = ( u.i & 0xFF00000000000000ULL ) >> 56; - } - *to += 8; -} - */ - -#define BANDTYPE_FLAGS_MASK 0xF0 -#define BANDTYPE_PIXTYPE_MASK 0x0F -#define BANDTYPE_FLAG_OFFDB (1<<7) -#define BANDTYPE_FLAG_HASNODATA (1<<6) -#define BANDTYPE_FLAG_ISNODATA (1<<5) -#define BANDTYPE_FLAG_RESERVED3 (1<<4) - -#define BANDTYPE_PIXTYPE(x) ((x)&BANDTYPE_PIXTYPE_MASK) -#define BANDTYPE_IS_OFFDB(x) ((x)&BANDTYPE_FLAG_OFFDB) -#define BANDTYPE_HAS_NODATA(x) ((x)&BANDTYPE_FLAG_HASNODATA) -#define BANDTYPE_IS_NODATA(x) ((x)&BANDTYPE_FLAG_ISNODATA) - -/* Read band from WKB as at start of band */ -static rt_band -rt_band_from_wkb( - uint16_t width, uint16_t height, - const uint8_t** ptr, const uint8_t* end, - uint8_t littleEndian -) { - rt_band band = NULL; - int pixbytes = 0; - uint8_t type = 0; - unsigned long sz = 0; - uint32_t v = 0; - - assert(NULL != ptr); - assert(NULL != end); - - band = rtalloc(sizeof (struct rt_band_t)); - if (!band) { - rterror("rt_band_from_wkb: Out of memory allocating rt_band during WKB parsing"); - return NULL; - } - band->ownsdata = 0; /* assume we don't own data */ - - if (end - *ptr < 1) { - rterror("rt_band_from_wkb: Premature end of WKB on band reading (%s:%d)", - __FILE__, __LINE__); - rt_band_destroy(band); - return NULL; - } - type = read_uint8(ptr); - - if ((type & BANDTYPE_PIXTYPE_MASK) >= PT_END) { - rterror("rt_band_from_wkb: Invalid pixtype %d", type & BANDTYPE_PIXTYPE_MASK); - rt_band_destroy(band); - return NULL; - } - - band->pixtype = type & BANDTYPE_PIXTYPE_MASK; - band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0; - band->hasnodata = BANDTYPE_HAS_NODATA(type) ? 1 : 0; - band->isnodata = band->hasnodata ? (BANDTYPE_IS_NODATA(type) ? 1 : 0) : 0; - band->width = width; - band->height = height; - - RASTER_DEBUGF(3, " Band pixtype:%s, offline:%d, hasnodata:%d", - rt_pixtype_name(band->pixtype), - band->offline, - band->hasnodata - ); - - /* Check there's enough bytes to read nodata value */ - pixbytes = rt_pixtype_size(band->pixtype); - if (((*ptr) + pixbytes) >= end) { - rterror("rt_band_from_wkb: Premature end of WKB on band novalue reading"); - rt_band_destroy(band); - return NULL; - } - - /* Read nodata value */ - switch (band->pixtype) { - case PT_1BB: { - band->nodataval = ((int) read_uint8(ptr)) & 0x01; - break; - } - case PT_2BUI: { - band->nodataval = ((int) read_uint8(ptr)) & 0x03; - break; - } - case PT_4BUI: { - band->nodataval = ((int) read_uint8(ptr)) & 0x0F; - break; - } - case PT_8BSI: { - band->nodataval = read_int8(ptr); - break; - } - case PT_8BUI: { - band->nodataval = read_uint8(ptr); - break; - } - case PT_16BSI: { - band->nodataval = read_int16(ptr, littleEndian); - break; - } - case PT_16BUI: { - band->nodataval = read_uint16(ptr, littleEndian); - break; - } - case PT_32BSI: { - band->nodataval = read_int32(ptr, littleEndian); - break; - } - case PT_32BUI: { - band->nodataval = read_uint32(ptr, littleEndian); - break; - } - case PT_32BF: { - band->nodataval = read_float32(ptr, littleEndian); - break; - } - case PT_64BF: { - band->nodataval = read_float64(ptr, littleEndian); - break; - } - default: { - rterror("rt_band_from_wkb: Unknown pixeltype %d", band->pixtype); - rt_band_destroy(band); - return NULL; - } - } - - RASTER_DEBUGF(3, " Nodata value: %g, pixbytes: %d, ptr @ %p, end @ %p", - band->nodataval, pixbytes, *ptr, end); - - if (band->offline) { - if (((*ptr) + 1) >= end) { - rterror("rt_band_from_wkb: Premature end of WKB on offline " - "band data bandNum reading (%s:%d)", - __FILE__, __LINE__ - ); - rt_band_destroy(band); - return NULL; - } - - band->data.offline.bandNum = read_int8(ptr); - band->data.offline.mem = NULL; - - { - /* check we have a NULL-termination */ - sz = 0; - while ((*ptr)[sz] && &((*ptr)[sz]) < end) ++sz; - if (&((*ptr)[sz]) >= end) { - rterror("rt_band_from_wkb: Premature end of WKB on band offline path reading"); - rt_band_destroy(band); - return NULL; - } - - /* we never own offline band data */ - band->ownsdata = 0; - - band->data.offline.path = rtalloc(sz + 1); - if (band->data.offline.path == NULL) { - rterror("rt_band_from_wkb: Out of memory allocating for offline path of band"); - rt_band_destroy(band); - return NULL; - } - - memcpy(band->data.offline.path, *ptr, sz); - band->data.offline.path[sz] = '\0'; - - RASTER_DEBUGF(3, "OFFDB band path is %s (size is %d)", - band->data.offline.path, sz); - - *ptr += sz + 1; - - /* TODO: How could we know if the offline band is a nodata band? */ - /* trust in the force */ - /*band->isnodata = FALSE;*/ - } - - return band; - } - - /* This is an on-disk band */ - sz = width * height * pixbytes; - if (((*ptr) + sz) > end) { - rterror("rt_band_from_wkb: Premature end of WKB on band data reading (%s:%d)", - __FILE__, __LINE__); - rt_band_destroy(band); - return NULL; - } - - band->data.mem = rtalloc(sz); - if (!band->data.mem) { - rterror("rt_band_from_wkb: Out of memory during band creation in WKB parser"); - rt_band_destroy(band); - return NULL; - } - - band->ownsdata = 1; /* we DO own this data!!! */ - memcpy(band->data.mem, *ptr, sz); - *ptr += sz; - - /* Should now flip values if > 8bit and - * littleEndian != isMachineLittleEndian */ - if (pixbytes > 1) { - if (isMachineLittleEndian() != littleEndian) { - void (*flipper)(uint8_t*) = 0; - uint8_t *flipme = NULL; - - if (pixbytes == 2) - flipper = flip_endian_16; - else if (pixbytes == 4) - flipper = flip_endian_32; - else if (pixbytes == 8) - flipper = flip_endian_64; - else { - rterror("rt_band_from_wkb: Unexpected pix bytes %d", pixbytes); - rt_band_destroy(band); - return NULL; - } - - flipme = band->data.mem; - sz = width * height; - for (v = 0; v < sz; ++v) { - flipper(flipme); - flipme += pixbytes; - } - } - } - /* And should check for invalid values for < 8bit types */ - else if ( - band->pixtype == PT_1BB || - band->pixtype == PT_2BUI || - band->pixtype == PT_4BUI - ) { - uint8_t maxVal = band->pixtype == PT_1BB ? 1 : (band->pixtype == PT_2BUI ? 3 : 15); - uint8_t val; - - sz = width*height; - for (v = 0; v < sz; ++v) { - val = ((uint8_t*) band->data.mem)[v]; - if (val > maxVal) { - rterror("rt_band_from_wkb: Invalid value %d for pixel of type %s", - val, rt_pixtype_name(band->pixtype)); - rt_band_destroy(band); - return NULL; - } - } - } - - /* And we should check if the band is a nodata band */ - /* TODO: No!! This is too slow */ - /*rt_band_check_is_nodata(band);*/ - - return band; -} - -/* -4 for size, +1 for endian */ -#define RT_WKB_HDR_SZ (sizeof(struct rt_raster_serialized_t)-4+1) - -rt_raster -rt_raster_from_wkb(const uint8_t* wkb, uint32_t wkbsize) { - const uint8_t *ptr = wkb; - const uint8_t *wkbend = NULL; - rt_raster rast = NULL; - uint8_t endian = 0; - uint16_t version = 0; - uint16_t i = 0; - uint16_t j = 0; - - assert(NULL != ptr); - - /* Check that wkbsize is >= sizeof(rt_raster_serialized) */ - if (wkbsize < RT_WKB_HDR_SZ) { - rterror("rt_raster_from_wkb: wkb size (%d) < min size (%d)", - wkbsize, RT_WKB_HDR_SZ); - return NULL; - } - wkbend = wkb + wkbsize; - - RASTER_DEBUGF(3, "Parsing header from wkb position %d (expected 0)", - d_binptr_to_pos(ptr, wkbend, wkbsize)); - - CHECK_BINPTR_POSITION(ptr, wkbend, wkbsize, 0); - - /* Read endianness */ - endian = *ptr; - ptr += 1; - - /* Read version of protocol */ - version = read_uint16(&ptr, endian); - if (version != 0) { - rterror("rt_raster_from_wkb: WKB version %d unsupported", version); - return NULL; - } - - /* Read other components of raster header */ - rast = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); - if (!rast) { - rterror("rt_raster_from_wkb: Out of memory allocating raster for wkb input"); - return NULL; - } - - rast->numBands = read_uint16(&ptr, endian); - rast->scaleX = read_float64(&ptr, endian); - rast->scaleY = read_float64(&ptr, endian); - rast->ipX = read_float64(&ptr, endian); - rast->ipY = read_float64(&ptr, endian); - rast->skewX = read_float64(&ptr, endian); - rast->skewY = read_float64(&ptr, endian); - rast->srid = clamp_srid(read_int32(&ptr, endian)); - rast->width = read_uint16(&ptr, endian); - rast->height = read_uint16(&ptr, endian); - - /* Consistency checking, should have been checked before */ - assert(ptr <= wkbend); - - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster numBands: %d", - rast->numBands); - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster scale: %gx%g", - rast->scaleX, rast->scaleY); - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster ip: %gx%g", - rast->ipX, rast->ipY); - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster skew: %gx%g", - rast->skewX, rast->skewY); - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster srid: %d", - rast->srid); - RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster dims: %dx%d", - rast->width, rast->height); - RASTER_DEBUGF(3, "Parsing raster header finished at wkb position %d (expected 61)", - d_binptr_to_pos(ptr, wkbend, wkbsize)); - - CHECK_BINPTR_POSITION(ptr, wkbend, wkbsize, 61); - - /* Read all bands of raster */ - if (!rast->numBands) { - /* Here ptr should have been left to right after last used byte */ - if (ptr < wkbend) { - rtwarn("%d bytes of WKB remained unparsed", wkbend - ptr); - } - else if (ptr > wkbend) { - /* Easier to get a segfault before I guess */ - rtwarn("We parsed %d bytes more then available!", ptr - wkbend); - } - - rast->bands = NULL; - return rast; - } - - /* Now read the bands */ - rast->bands = (rt_band*) rtalloc(sizeof(rt_band) * rast->numBands); - if (!rast->bands) { - rterror("rt_raster_from_wkb: Out of memory allocating bands for WKB raster decoding"); - rt_raster_destroy(rast); - return NULL; - } - - /* ptr should now point to start of first band */ - /* we should have checked this before */ - assert(ptr <= wkbend); - - for (i = 0; i < rast->numBands; ++i) { - RASTER_DEBUGF(3, "Parsing band %d from wkb position %d", i, - d_binptr_to_pos(ptr, wkbend, wkbsize)); - - rt_band band = rt_band_from_wkb(rast->width, rast->height, - &ptr, wkbend, endian); - if (!band) { - rterror("rt_raster_from_wkb: Error reading WKB form of band %d", i); - for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); - rt_raster_destroy(rast); - return NULL; - } - - band->raster = rast; - rast->bands[i] = band; - } - - /* Here ptr should have been left to right after last used byte */ - if (ptr < wkbend) { - rtwarn("%d bytes of WKB remained unparsed", wkbend - ptr); - } - else if (ptr > wkbend) { - /* Easier to get a segfault before I guess */ - rtwarn("We parsed %d bytes more then available!", ptr - wkbend); - } - - return rast; -} - -rt_raster -rt_raster_from_hexwkb(const char* hexwkb, uint32_t hexwkbsize) { - rt_raster ret = NULL; - uint8_t* wkb = NULL; - uint32_t wkbsize = 0; - uint32_t i = 0; - - assert(NULL != hexwkb); - - RASTER_DEBUGF(3, "input wkb: %s", hexwkb); - RASTER_DEBUGF(3, "input wkbsize: %d", hexwkbsize); - - if (hexwkbsize % 2) { - rterror("rt_raster_from_hexwkb: Raster HEXWKB input must have an even number of characters"); - return NULL; - } - wkbsize = hexwkbsize / 2; - - wkb = rtalloc(wkbsize); - if (!wkb) { - rterror("rt_raster_from_hexwkb: Out of memory allocating memory for decoding HEXWKB"); - return NULL; - } - - /* parse full hex */ - for (i = 0; i < wkbsize; ++i) { - wkb[i] = parse_hex((char*) & (hexwkb[i * 2])); - } - - ret = rt_raster_from_wkb(wkb, wkbsize); - rtdealloc(wkb); /* as long as rt_raster_from_wkb copies memory */ - - return ret; -} - -static uint32_t -rt_raster_wkb_size(rt_raster raster, int outasin) { - uint32_t size = RT_WKB_HDR_SZ; - uint16_t i = 0; - - assert(NULL != raster); - - RASTER_DEBUGF(3, "rt_raster_wkb_size: computing size for %d bands", - raster->numBands); - - for (i = 0; i < raster->numBands; ++i) { - rt_band band = raster->bands[i]; - rt_pixtype pixtype = band->pixtype; - int pixbytes = rt_pixtype_size(pixtype); - - RASTER_DEBUGF(3, "rt_raster_wkb_size: adding size of band %d", i); - - if (pixbytes < 1) { - rterror("rt_raster_wkb_size: Corrupted band: unknown pixtype"); - return 0; - } - - /* Add space for band type */ - size += 1; - - /* Add space for nodata value */ - size += pixbytes; - - if (!outasin && band->offline) { - /* Add space for band number */ - size += 1; - - /* Add space for null-terminated path */ - size += strlen(band->data.offline.path) + 1; - } - else { - /* Add space for actual data */ - size += pixbytes * raster->width * raster->height; - } - } - - return size; -} - -/** - * Return this raster in WKB form - * - * @param raster : the raster - * @param outasin : if TRUE, out-db bands are treated as in-db - * @param wkbsize : will be set to the size of returned wkb form - * - * @return WKB of raster or NULL on error - */ -uint8_t * -rt_raster_to_wkb(rt_raster raster, int outasin, uint32_t *wkbsize) { - -#if POSTGIS_DEBUG_LEVEL > 0 - const uint8_t *wkbend = NULL; -#endif - - uint8_t *wkb = NULL; - uint8_t *ptr = NULL; - uint16_t i = 0; - uint8_t littleEndian = isMachineLittleEndian(); - - assert(NULL != raster); - assert(NULL != wkbsize); - - RASTER_DEBUG(2, "rt_raster_to_wkb: about to call rt_raster_wkb_size"); - - *wkbsize = rt_raster_wkb_size(raster, outasin); - RASTER_DEBUGF(3, "rt_raster_to_wkb: found size: %d", *wkbsize); - - wkb = (uint8_t*) rtalloc(*wkbsize); - if (!wkb) { - rterror("rt_raster_to_wkb: Out of memory allocating WKB for raster"); - return NULL; - } - - ptr = wkb; - -#if POSTGIS_DEBUG_LEVEL > 2 - wkbend = ptr + (*wkbsize); -#endif - RASTER_DEBUGF(3, "Writing raster header to wkb on position %d (expected 0)", - d_binptr_to_pos(ptr, wkbend, *wkbsize)); - - /* Write endianness */ - *ptr = littleEndian; - ptr += 1; - - /* Write version(size - (end - ptr)) */ - write_uint16(&ptr, littleEndian, 0); - - /* Copy header (from numBands up) */ - memcpy(ptr, &(raster->numBands), sizeof (struct rt_raster_serialized_t) - 6); - ptr += sizeof (struct rt_raster_serialized_t) - 6; - - RASTER_DEBUGF(3, "Writing bands header to wkb position %d (expected 61)", - d_binptr_to_pos(ptr, wkbend, *wkbsize)); - - /* Serialize bands now */ - for (i = 0; i < raster->numBands; ++i) { - rt_band band = raster->bands[i]; - rt_pixtype pixtype = band->pixtype; - int pixbytes = rt_pixtype_size(pixtype); - - RASTER_DEBUGF(3, "Writing WKB for band %d", i); - RASTER_DEBUGF(3, "Writing band pixel type to wkb position %d", - d_binptr_to_pos(ptr, wkbend, *wkbsize)); - - if (pixbytes < 1) { - rterror("rt_raster_to_wkb: Corrupted band: unknown pixtype"); - rtdealloc(wkb); - return NULL; - } - - /* Add band type */ - *ptr = band->pixtype; - if (!outasin && band->offline) *ptr |= BANDTYPE_FLAG_OFFDB; - if (band->hasnodata) *ptr |= BANDTYPE_FLAG_HASNODATA; - if (band->isnodata) *ptr |= BANDTYPE_FLAG_ISNODATA; - ptr += 1; - -#if 0 - /* no padding required for WKB */ - /* Add padding (if needed) */ - if (pixbytes > 1) { - memset(ptr, '\0', pixbytes - 1); - ptr += pixbytes - 1; - } - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!(((uint64_t) ptr) % pixbytes)); -#endif - - RASTER_DEBUGF(3, "Writing band nodata to wkb position %d", - d_binptr_to_pos(ptr, wkbend, *wkbsize)); - - /* Add nodata value */ - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BUI: { - uint8_t v = band->nodataval; - *ptr = v; - ptr += 1; - break; - } - case PT_8BSI: { - int8_t v = band->nodataval; - *ptr = v; - ptr += 1; - break; - } - case PT_16BSI: - case PT_16BUI: { - uint16_t v = band->nodataval; - memcpy(ptr, &v, 2); - ptr += 2; - break; - } - case PT_32BSI: - case PT_32BUI: { - uint32_t v = band->nodataval; - memcpy(ptr, &v, 4); - ptr += 4; - break; - } - case PT_32BF: { - float v = band->nodataval; - memcpy(ptr, &v, 4); - ptr += 4; - break; - } - case PT_64BF: { - memcpy(ptr, &band->nodataval, 8); - ptr += 8; - break; - } - default: - rterror("rt_raster_to_wkb: Fatal error caused by unknown pixel type. Aborting."); - rtdealloc(wkb); - abort(); /* shoudn't happen */ - return 0; - } - -#if 0 - /* no padding for WKB */ - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((uint64_t) ptr % pixbytes)); -#endif - - if (!outasin && band->offline) { - /* Write band number */ - *ptr = band->data.offline.bandNum; - ptr += 1; - - /* Write path */ - strcpy((char*) ptr, band->data.offline.path); - ptr += strlen(band->data.offline.path) + 1; - } - else { - /* Write data */ - uint32_t datasize = raster->width * raster->height * pixbytes; - RASTER_DEBUGF(4, "rt_raster_to_wkb: Copying %d bytes", datasize); - - memcpy(ptr, rt_band_get_data(band), datasize); - - ptr += datasize; - } - -#if 0 - /* no padding for WKB */ - /* Pad up to 8-bytes boundary */ - while ((uint64_t) ptr % 8) { - *ptr = 0; - ++ptr; - } - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((uint64_t) ptr % pixbytes)); -#endif - } - - return wkb; -} - -char * -rt_raster_to_hexwkb(rt_raster raster, int outasin, uint32_t *hexwkbsize) { - uint8_t *wkb = NULL; - char* hexwkb = NULL; - uint32_t i = 0; - uint32_t wkbsize = 0; - - assert(NULL != raster); - assert(NULL != hexwkbsize); - - RASTER_DEBUG(2, "rt_raster_to_hexwkb: calling rt_raster_to_wkb"); - - wkb = rt_raster_to_wkb(raster, outasin, &wkbsize); - - RASTER_DEBUG(3, "rt_raster_to_hexwkb: rt_raster_to_wkb returned"); - - *hexwkbsize = wkbsize * 2; /* hex is 2 times bytes */ - hexwkb = (char*) rtalloc((*hexwkbsize) + 1); - if (!hexwkb) { - rterror("rt_raster_to_hexwkb: Out of memory hexifying raster WKB"); - rtdealloc(wkb); - return NULL; - } - hexwkb[*hexwkbsize] = '\0'; /* Null-terminate */ - - for (i = 0; i < wkbsize; ++i) { - deparse_hex(wkb[i], &(hexwkb[2 * i])); - } - - rtdealloc(wkb); /* we don't need this anymore */ - - RASTER_DEBUGF(3, "rt_raster_to_hexwkb: output wkb: %s", hexwkb); - return hexwkb; -} - -/*--------- Serializer/Deserializer --------------------------------------*/ - -static uint32_t -rt_raster_serialized_size(rt_raster raster) { - uint32_t size = sizeof (struct rt_raster_serialized_t); - uint16_t i = 0; - - assert(NULL != raster); - - RASTER_DEBUGF(3, "Serialized size with just header:%d - now adding size of %d bands", - size, raster->numBands); - - for (i = 0; i < raster->numBands; ++i) { - rt_band band = raster->bands[i]; - rt_pixtype pixtype = band->pixtype; - int pixbytes = rt_pixtype_size(pixtype); - - if (pixbytes < 1) { - rterror("rt_raster_serialized_size: Corrupted band: unknown pixtype"); - return 0; - } - - /* Add space for band type, hasnodata flag and data padding */ - size += pixbytes; - - /* Add space for nodata value */ - size += pixbytes; - - if (band->offline) { - /* Add space for band number */ - size += 1; - - /* Add space for null-terminated path */ - size += strlen(band->data.offline.path) + 1; - } - else { - /* Add space for raster band data */ - size += pixbytes * raster->width * raster->height; - } - - RASTER_DEBUGF(3, "Size before alignment is %d", size); - - /* Align size to 8-bytes boundary (trailing padding) */ - /* XXX jorgearevalo: bug here. If the size is actually 8-bytes aligned, - this line will add 8 bytes trailing padding, and it's not necessary */ - /*size += 8 - (size % 8);*/ - if (size % 8) - size += 8 - (size % 8); - - RASTER_DEBUGF(3, "Size after alignment is %d", size); - } - - return size; -} - -/** - * Return this raster in serialized form. - * Memory (band data included) is copied from rt_raster. - * - * Serialized form is documented in doc/RFC1-SerializedFormat. - */ -void* -rt_raster_serialize(rt_raster raster) { - uint32_t size = 0; - uint8_t* ret = NULL; - uint8_t* ptr = NULL; - uint16_t i = 0; - - assert(NULL != raster); - - size = rt_raster_serialized_size(raster); - ret = (uint8_t*) rtalloc(size); - if (!ret) { - rterror("rt_raster_serialize: Out of memory allocating %d bytes for serializing a raster", size); - return NULL; - } - memset(ret, '-', size); - ptr = ret; - - RASTER_DEBUGF(3, "sizeof(struct rt_raster_serialized_t):%u", - sizeof (struct rt_raster_serialized_t)); - RASTER_DEBUGF(3, "sizeof(struct rt_raster_t):%u", - sizeof (struct rt_raster_t)); - RASTER_DEBUGF(3, "serialized size:%lu", (long unsigned) size); - - /* Set size */ - /* NOTE: Value of rt_raster.size may be updated in - * returned object, for instance, by rt_pg layer to - * store value calculated by SET_VARSIZE. - */ - raster->size = size; - - /* Set version */ - raster->version = 0; - - /* Copy header */ - memcpy(ptr, raster, sizeof (struct rt_raster_serialized_t)); - - RASTER_DEBUG(3, "Start hex dump of raster being serialized using 0x2D to mark non-written bytes"); - -#if POSTGIS_DEBUG_LEVEL > 2 - uint8_t* dbg_ptr = ptr; - d_print_binary_hex("HEADER", dbg_ptr, size); -#endif - - ptr += sizeof (struct rt_raster_serialized_t); - - /* Serialize bands now */ - for (i = 0; i < raster->numBands; ++i) { - rt_band band = raster->bands[i]; - assert(NULL != band); - - rt_pixtype pixtype = band->pixtype; - int pixbytes = rt_pixtype_size(pixtype); - if (pixbytes < 1) { - rterror("rt_raster_serialize: Corrupted band: unknown pixtype"); - rtdealloc(ret); - return NULL; - } - - /* Add band type */ - *ptr = band->pixtype; - if (band->offline) { - *ptr |= BANDTYPE_FLAG_OFFDB; - } - if (band->hasnodata) { - *ptr |= BANDTYPE_FLAG_HASNODATA; - } - - if (band->isnodata) { - *ptr |= BANDTYPE_FLAG_ISNODATA; - } - -#if POSTGIS_DEBUG_LEVEL > 2 - d_print_binary_hex("PIXTYPE", dbg_ptr, size); -#endif - - ptr += 1; - - /* Add padding (if needed) */ - if (pixbytes > 1) { - memset(ptr, '\0', pixbytes - 1); - ptr += pixbytes - 1; - } - -#if POSTGIS_DEBUG_LEVEL > 2 - d_print_binary_hex("PADDING", dbg_ptr, size); -#endif - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((ptr - ret) % pixbytes)); - - /* Add nodata value */ - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BUI: { - uint8_t v = band->nodataval; - *ptr = v; - ptr += 1; - break; - } - case PT_8BSI: { - int8_t v = band->nodataval; - *ptr = v; - ptr += 1; - break; - } - case PT_16BSI: - case PT_16BUI: { - uint16_t v = band->nodataval; - memcpy(ptr, &v, 2); - ptr += 2; - break; - } - case PT_32BSI: - case PT_32BUI: { - uint32_t v = band->nodataval; - memcpy(ptr, &v, 4); - ptr += 4; - break; - } - case PT_32BF: { - float v = band->nodataval; - memcpy(ptr, &v, 4); - ptr += 4; - break; - } - case PT_64BF: { - memcpy(ptr, &band->nodataval, 8); - ptr += 8; - break; - } - default: - rterror("rt_raster_serialize: Fatal error caused by unknown pixel type. Aborting."); - rtdealloc(ret); - return NULL; - } - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((ptr - ret) % pixbytes)); - -#if POSTGIS_DEBUG_LEVEL > 2 - d_print_binary_hex("nodata", dbg_ptr, size); -#endif - - if (band->offline) { - /* Write band number */ - *ptr = band->data.offline.bandNum; - ptr += 1; - - /* Write path */ - strcpy((char*) ptr, band->data.offline.path); - ptr += strlen(band->data.offline.path) + 1; - } - else { - /* Write data */ - uint32_t datasize = raster->width * raster->height * pixbytes; - memcpy(ptr, band->data.mem, datasize); - ptr += datasize; - } - -#if POSTGIS_DEBUG_LEVEL > 2 - d_print_binary_hex("BAND", dbg_ptr, size); -#endif - - /* Pad up to 8-bytes boundary */ - while ((uintptr_t) ptr % 8) { - *ptr = 0; - ++ptr; - - RASTER_DEBUGF(3, "PAD at %d", (uintptr_t) ptr % 8); - } - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((ptr - ret) % pixbytes)); - } /* for-loop over bands */ - -#if POSTGIS_DEBUG_LEVEL > 2 - d_print_binary_hex("SERIALIZED RASTER", dbg_ptr, size); -#endif - return ret; -} - -/** - * Return a raster from a serialized form. - * - * Serialized form is documented in doc/RFC1-SerializedFormat. - * - * NOTE: the raster will contain pointer to the serialized - * form (including band data), which must be kept alive. - */ -rt_raster -rt_raster_deserialize(void* serialized, int header_only) { - rt_raster rast = NULL; - const uint8_t *ptr = NULL; - const uint8_t *beg = NULL; - uint16_t i = 0; - uint16_t j = 0; - uint8_t littleEndian = isMachineLittleEndian(); - - assert(NULL != serialized); - - RASTER_DEBUG(2, "rt_raster_deserialize: Entering..."); - - /* NOTE: Value of rt_raster.size may be different - * than actual size of raster data being read. - * See note on SET_VARSIZE in rt_raster_serialize function above. - */ - - /* Allocate memory for deserialized raster header */ - RASTER_DEBUG(3, "rt_raster_deserialize: Allocating memory for deserialized raster header"); - rast = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); - if (!rast) { - rterror("rt_raster_deserialize: Out of memory allocating raster for deserialization"); - return NULL; - } - - /* Deserialize raster header */ - RASTER_DEBUG(3, "rt_raster_deserialize: Deserialize raster header"); - memcpy(rast, serialized, sizeof (struct rt_raster_serialized_t)); - - if (0 == rast->numBands || header_only) { - rast->bands = 0; - return rast; - } - - beg = (const uint8_t*) serialized; - - /* Allocate registry of raster bands */ - RASTER_DEBUG(3, "rt_raster_deserialize: Allocating memory for bands"); - rast->bands = rtalloc(rast->numBands * sizeof (rt_band)); - if (rast->bands == NULL) { - rterror("rt_raster_deserialize: Out of memory allocating bands"); - rtdealloc(rast); - return NULL; - } - - RASTER_DEBUGF(3, "rt_raster_deserialize: %d bands", rast->numBands); - - /* Move to the beginning of first band */ - ptr = beg; - ptr += sizeof (struct rt_raster_serialized_t); - - /* Deserialize bands now */ - for (i = 0; i < rast->numBands; ++i) { - rt_band band = NULL; - uint8_t type = 0; - int pixbytes = 0; - - band = rtalloc(sizeof(struct rt_band_t)); - if (!band) { - rterror("rt_raster_deserialize: Out of memory allocating rt_band during deserialization"); - for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); - rt_raster_destroy(rast); - return NULL; - } - - rast->bands[i] = band; - - type = *ptr; - ptr++; - band->pixtype = type & BANDTYPE_PIXTYPE_MASK; - - RASTER_DEBUGF(3, "rt_raster_deserialize: band %d with pixel type %s", i, rt_pixtype_name(band->pixtype)); - - band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0; - band->hasnodata = BANDTYPE_HAS_NODATA(type) ? 1 : 0; - band->isnodata = band->hasnodata ? (BANDTYPE_IS_NODATA(type) ? 1 : 0) : 0; - band->width = rast->width; - band->height = rast->height; - band->ownsdata = 0; /* we do NOT own this data!!! */ - band->raster = rast; - - /* Advance by data padding */ - pixbytes = rt_pixtype_size(band->pixtype); - ptr += pixbytes - 1; - - /* Read nodata value */ - switch (band->pixtype) { - case PT_1BB: { - band->nodataval = ((int) read_uint8(&ptr)) & 0x01; - break; - } - case PT_2BUI: { - band->nodataval = ((int) read_uint8(&ptr)) & 0x03; - break; - } - case PT_4BUI: { - band->nodataval = ((int) read_uint8(&ptr)) & 0x0F; - break; - } - case PT_8BSI: { - band->nodataval = read_int8(&ptr); - break; - } - case PT_8BUI: { - band->nodataval = read_uint8(&ptr); - break; - } - case PT_16BSI: { - band->nodataval = read_int16(&ptr, littleEndian); - break; - } - case PT_16BUI: { - band->nodataval = read_uint16(&ptr, littleEndian); - break; - } - case PT_32BSI: { - band->nodataval = read_int32(&ptr, littleEndian); - break; - } - case PT_32BUI: { - band->nodataval = read_uint32(&ptr, littleEndian); - break; - } - case PT_32BF: { - band->nodataval = read_float32(&ptr, littleEndian); - break; - } - case PT_64BF: { - band->nodataval = read_float64(&ptr, littleEndian); - break; - } - default: { - rterror("rt_raster_deserialize: Unknown pixeltype %d", band->pixtype); - for (j = 0; j <= i; j++) rt_band_destroy(rast->bands[j]); - rt_raster_destroy(rast); - return NULL; - } - } - - RASTER_DEBUGF(3, "rt_raster_deserialize: has nodata flag %d", band->hasnodata); - RASTER_DEBUGF(3, "rt_raster_deserialize: nodata value %g", band->nodataval); - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((ptr - beg) % pixbytes)); - - if (band->offline) { - int pathlen = 0; - - /* Read band number */ - band->data.offline.bandNum = *ptr; - ptr += 1; - - /* Register path */ - pathlen = strlen((char*) ptr); - band->data.offline.path = rtalloc(sizeof(char) * (pathlen + 1)); - if (band->data.offline.path == NULL) { - rterror("rt_raster_deserialize: Could not allocate momory for offline band path"); - for (j = 0; j <= i; j++) rt_band_destroy(rast->bands[j]); - rt_raster_destroy(rast); - return NULL; - } - - memcpy(band->data.offline.path, ptr, pathlen); - band->data.offline.path[pathlen] = '\0'; - ptr += pathlen + 1; - - band->data.offline.mem = NULL; - } - else { - /* Register data */ - const uint32_t datasize = rast->width * rast->height * pixbytes; - band->data.mem = (uint8_t*) ptr; - ptr += datasize; - } - - /* Skip bytes of padding up to 8-bytes boundary */ -#if POSTGIS_DEBUG_LEVEL > 0 - const uint8_t *padbeg = ptr; -#endif - while (0 != ((ptr - beg) % 8)) { - ++ptr; - } - - RASTER_DEBUGF(3, "rt_raster_deserialize: skip %d bytes of 8-bytes boundary padding", ptr - padbeg); - - /* Consistency checking (ptr is pixbytes-aligned) */ - assert(!((ptr - beg) % pixbytes)); - } - - return rast; -} - -/** - * Return TRUE if the raster is empty. i.e. is NULL, width = 0 or height = 0 - * - * @param raster : the raster to get info from - * - * @return TRUE if the raster is empty, FALSE otherwise - */ -int -rt_raster_is_empty(rt_raster raster) { - return (NULL == raster || raster->height <= 0 || raster->width <= 0); -} - -/** - * Return TRUE if the raster has a band of this number. - * - * @param raster : the raster to get info from - * @param nband : the band number. 0-based - * - * @return TRUE if the raster has a band of this number, FALSE otherwise - */ -int -rt_raster_has_band(rt_raster raster, int nband) { - return !(NULL == raster || nband >= raster->numBands || nband < 0); -} - -/** - * Copy one band from one raster to another. Bands are duplicated from - * fromrast to torast using rt_band_duplicate. The caller will need - * to ensure that the copied band's data or path remains allocated - * for the lifetime of the copied bands. - * - * @param torast : raster to copy band to - * @param fromrast : raster to copy band from - * @param fromindex : index of band in source raster, 0-based - * @param toindex : index of new band in destination raster, 0-based - * - * @return The band index of the second raster where the new band is copied. - * -1 if error - */ -int -rt_raster_copy_band( - rt_raster torast, rt_raster fromrast, - int fromindex, int toindex -) { - rt_band srcband = NULL; - rt_band dstband = NULL; - - assert(NULL != torast); - assert(NULL != fromrast); - - /* Check raster dimensions */ - if (torast->height != fromrast->height || torast->width != fromrast->width) { - rtwarn("rt_raster_copy_band: Attempting to add a band with different width or height"); - return -1; - } - - /* Check bands limits */ - if (fromrast->numBands < 1) { - rtwarn("rt_raster_copy_band: Second raster has no band"); - return -1; - } - else if (fromindex < 0) { - rtwarn("rt_raster_copy_band: Band index for second raster < 0. Defaulted to 0"); - fromindex = 0; - } - else if (fromindex >= fromrast->numBands) { - rtwarn("rt_raster_copy_band: Band index for second raster > number of bands, truncated from %u to %u", fromindex, fromrast->numBands - 1); - fromindex = fromrast->numBands - 1; - } - - if (toindex < 0) { - rtwarn("rt_raster_copy_band: Band index for first raster < 0. Defaulted to 0"); - toindex = 0; - } - else if (toindex > torast->numBands) { - rtwarn("rt_raster_copy_band: Band index for first raster > number of bands, truncated from %u to %u", toindex, torast->numBands); - toindex = torast->numBands; - } - - /* Get band from source raster */ - srcband = rt_raster_get_band(fromrast, fromindex); - - /* duplicate band */ - dstband = rt_band_duplicate(srcband); - - /* Add band to the second raster */ - return rt_raster_add_band(torast, dstband, toindex); -} - -/** - * Construct a new rt_raster from an existing rt_raster and an array - * of band numbers - * - * @param raster : the source raster - * @param bandNums : array of band numbers to extract from source raster - * and add to the new raster (0 based) - * @param count : number of elements in bandNums - * - * @return a new rt_raster or NULL on error - */ -rt_raster -rt_raster_from_band(rt_raster raster, uint32_t *bandNums, int count) { - rt_raster rast = NULL; - int i = 0; - int j = 0; - int idx; - int32_t flag; - double gt[6] = {0.}; - - assert(NULL != raster); - assert(NULL != bandNums); - - RASTER_DEBUGF(3, "rt_raster_from_band: source raster has %d bands", - rt_raster_get_num_bands(raster)); - - /* create new raster */ - rast = rt_raster_new(raster->width, raster->height); - if (NULL == rast) { - rterror("rt_raster_from_band: Out of memory allocating new raster"); - return NULL; - } - - /* copy raster attributes */ - rt_raster_get_geotransform_matrix(raster, gt); - rt_raster_set_geotransform_matrix(rast, gt); - - /* srid */ - rt_raster_set_srid(rast, raster->srid); - - /* copy bands */ - for (i = 0; i < count; i++) { - idx = bandNums[i]; - flag = rt_raster_copy_band(rast, raster, idx, i); - - if (flag < 0) { - rterror("rt_raster_from_band: Could not copy band"); - for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); - rt_raster_destroy(rast); - return NULL; - } - - RASTER_DEBUGF(3, "rt_raster_from_band: band created at index %d", - flag); - } - - RASTER_DEBUGF(3, "rt_raster_from_band: new raster has %d bands", - rt_raster_get_num_bands(rast)); - return rast; -} - -/** - * Replace band at provided index with new band - * - * @param raster: raster of band to be replaced - * @param band : new band to add to raster - * @param index : index of band to replace (0-based) - * - * @return NULL on error or replaced band - */ -rt_band -rt_raster_replace_band(rt_raster raster, rt_band band, int index) { - rt_band oldband = NULL; - assert(NULL != raster); - assert(NULL != band); - - if (band->width != raster->width || band->height != raster->height) { - rterror("rt_raster_replace_band: Band does not match raster's dimensions: %dx%d band to %dx%d raster", - band->width, band->height, raster->width, raster->height); - return 0; - } - - if (index >= raster->numBands || index < 0) { - rterror("rt_raster_replace_band: Band index is not valid"); - return 0; - } - - oldband = rt_raster_get_band(raster, index); - RASTER_DEBUGF(3, "rt_raster_replace_band: old band at %p", oldband); - RASTER_DEBUGF(3, "rt_raster_replace_band: new band at %p", band); - - raster->bands[index] = band; - RASTER_DEBUGF(3, "rt_raster_replace_band: new band at %p", raster->bands[index]); - - band->raster = raster; - oldband->raster = NULL; - - return oldband; -} - -/** - * Clone an existing raster - * - * @param raster : raster to clone - * @param deep : flag indicating if bands should be cloned - * - * @return a new rt_raster or NULL on error - */ -rt_raster -rt_raster_clone(rt_raster raster, uint8_t deep) { - rt_raster rtn = NULL; - double gt[6] = {0}; - - assert(NULL != raster); - - if (deep) { - int numband = rt_raster_get_num_bands(raster); - uint32_t *nband = NULL; - int i = 0; - - nband = rtalloc(sizeof(uint32_t) * numband); - if (nband == NULL) { - rterror("rt_raster_clone: Could not allocate memory for deep clone"); - return NULL; - } - for (i = 0; i < numband; i++) - nband[i] = i; - - rtn = rt_raster_from_band(raster, nband, numband); - rtdealloc(nband); - - return rtn; - } - - rtn = rt_raster_new( - rt_raster_get_width(raster), - rt_raster_get_height(raster) - ); - if (rtn == NULL) { - rterror("rt_raster_clone: Could not create cloned raster"); - return NULL; - } - - rt_raster_get_geotransform_matrix(raster, gt); - rt_raster_set_geotransform_matrix(rtn, gt); - rt_raster_set_srid(rtn, rt_raster_get_srid(raster)); - - return rtn; -} - -/** - * Return formatted GDAL raster from raster - * - * @param raster : the raster to convert - * @param srs : the raster's coordinate system in OGC WKT - * @param format : format to convert to. GDAL driver short name - * @param options : list of format creation options. array of strings - * @param gdalsize : will be set to the size of returned bytea - * - * @return formatted GDAL raster. the calling function is responsible - * for freeing the returned data using CPLFree() - */ -uint8_t* -rt_raster_to_gdal(rt_raster raster, const char *srs, - char *format, char **options, uint64_t *gdalsize) { - GDALDriverH src_drv = NULL; - GDALDatasetH src_ds = NULL; - - vsi_l_offset rtn_lenvsi; - uint64_t rtn_len = 0; - - GDALDriverH rtn_drv = NULL; - GDALDatasetH rtn_ds = NULL; - uint8_t *rtn = NULL; - - assert(NULL != raster); - assert(NULL != gdalsize); - - /* any supported format is possible */ - rt_util_gdal_register_all(); - RASTER_DEBUG(3, "loaded all supported GDAL formats"); - - /* output format not specified */ - if (format == NULL || !strlen(format)) - format = "GTiff"; - RASTER_DEBUGF(3, "output format is %s", format); - - /* load raster into a GDAL MEM raster */ - src_ds = rt_raster_to_gdal_mem(raster, srs, NULL, NULL, 0, &src_drv); - if (NULL == src_ds) { - rterror("rt_raster_to_gdal: Could not convert raster to GDAL MEM format"); - return 0; - } - - /* load driver */ - rtn_drv = GDALGetDriverByName(format); - if (NULL == rtn_drv) { - rterror("rt_raster_to_gdal: Could not load the output GDAL driver"); - GDALClose(src_ds); - return 0; - } - RASTER_DEBUG(3, "Output driver loaded"); - - /* convert GDAL MEM raster to output format */ - RASTER_DEBUG(3, "Copying GDAL MEM raster to memory file in output format"); - rtn_ds = GDALCreateCopy( - rtn_drv, - "/vsimem/out.dat", /* should be fine assuming this is in a process */ - src_ds, - FALSE, /* should copy be strictly equivelent? */ - options, /* format options */ - NULL, /* progress function */ - NULL /* progress data */ - ); - if (NULL == rtn_ds) { - rterror("rt_raster_to_gdal: Could not create the output GDAL dataset"); - GDALClose(src_ds); - return 0; - } - - /* close source dataset */ - GDALClose(src_ds); - RASTER_DEBUG(3, "Closed GDAL MEM raster"); - - RASTER_DEBUGF(4, "dataset SRS: %s", GDALGetProjectionRef(rtn_ds)); - - /* close dataset, this also flushes any pending writes */ - GDALClose(rtn_ds); - RASTER_DEBUG(3, "Closed GDAL output raster"); - - RASTER_DEBUG(3, "Done copying GDAL MEM raster to memory file in output format"); - - /* from memory file to buffer */ - RASTER_DEBUG(3, "Copying GDAL memory file to buffer"); - rtn = VSIGetMemFileBuffer("/vsimem/out.dat", &rtn_lenvsi, TRUE); - RASTER_DEBUG(3, "Done copying GDAL memory file to buffer"); - if (NULL == rtn) { - rterror("rt_raster_to_gdal: Could not create the output GDAL raster"); - return 0; - } - - rtn_len = (uint64_t) rtn_lenvsi; - *gdalsize = rtn_len; - - return rtn; -} - -/** - * Returns a set of available GDAL drivers - * - * @param drv_count : number of GDAL drivers available - * @param cancc : if non-zero, filter drivers to only those - * with support for CreateCopy and VirtualIO - * - * @return set of "gdaldriver" values of available GDAL drivers - */ -rt_gdaldriver -rt_raster_gdal_drivers(uint32_t *drv_count, uint8_t cancc) { - const char *state; - const char *txt; - int txt_len; - GDALDriverH *drv = NULL; - rt_gdaldriver rtn = NULL; - int count; - int i; - uint32_t j; - - assert(drv_count != NULL); - - rt_util_gdal_register_all(); - count = GDALGetDriverCount(); - rtn = (rt_gdaldriver) rtalloc(count * sizeof(struct rt_gdaldriver_t)); - if (NULL == rtn) { - rterror("rt_raster_gdal_drivers: Could not allocate memory for gdaldriver structure"); - return 0; - } - - for (i = 0, j = 0; i < count; i++) { - drv = GDALGetDriver(i); - - if (cancc) { - /* CreateCopy support */ - state = GDALGetMetadataItem(drv, GDAL_DCAP_CREATECOPY, NULL); - if (state == NULL) continue; - - /* VirtualIO support */ - state = GDALGetMetadataItem(drv, GDAL_DCAP_VIRTUALIO, NULL); - if (state == NULL) continue; - } - - /* index of driver */ - rtn[j].idx = i; - - /* short name */ - txt = GDALGetDriverShortName(drv); - txt_len = strlen(txt); - - if (cancc) { - RASTER_DEBUGF(3, "rt_raster_gdal_driver: driver %s (%d) supports CreateCopy() and VirtualIO()", txt, i); - } - - txt_len = (txt_len + 1) * sizeof(char); - rtn[j].short_name = (char *) rtalloc(txt_len); - memcpy(rtn[j].short_name, txt, txt_len); - - /* long name */ - txt = GDALGetDriverLongName(drv); - txt_len = strlen(txt); - - txt_len = (txt_len + 1) * sizeof(char); - rtn[j].long_name = (char *) rtalloc(txt_len); - memcpy(rtn[j].long_name, txt, txt_len); - - /* creation options */ - txt = GDALGetDriverCreationOptionList(drv); - txt_len = strlen(txt); - - txt_len = (txt_len + 1) * sizeof(char); - rtn[j].create_options = (char *) rtalloc(txt_len); - memcpy(rtn[j].create_options, txt, txt_len); - - j++; - } - - /* free unused memory */ - rtn = rtrealloc(rtn, j * sizeof(struct rt_gdaldriver_t)); - *drv_count = j; - - return rtn; -} - -/** - * Return GDAL dataset using GDAL MEM driver from raster - * - * @param raster : raster to convert to GDAL MEM - * @param srs : the raster's coordinate system in OGC WKT - * @param bandNums : array of band numbers to extract from raster - * and include in the GDAL dataset (0 based) - * @param excludeNodataValues : array of zero, nonzero where if non-zero, - * ignore nodata values for the band - * @param count : number of elements in bandNums - * @param rtn_drv : is set to the GDAL driver object - * - * @return GDAL dataset using GDAL MEM driver - */ -GDALDatasetH -rt_raster_to_gdal_mem( - rt_raster raster, - const char *srs, - uint32_t *bandNums, - int *excludeNodataValues, - int count, - GDALDriverH *rtn_drv -) { - GDALDriverH drv = NULL; - GDALDatasetH ds = NULL; - double gt[6] = {0.0}; - CPLErr cplerr; - GDALDataType gdal_pt = GDT_Unknown; - GDALRasterBandH band; - void *pVoid; - char *pszDataPointer; - char szGDALOption[50]; - char *apszOptions[4]; - double nodata = 0.0; - int allocBandNums = 0; - int allocNodataValues = 0; - - int i; - int numBands; - uint32_t width = 0; - uint32_t height = 0; - rt_band rtband = NULL; - rt_pixtype pt = PT_END; - - assert(NULL != raster); - assert(NULL != rtn_drv); - - /* store raster in GDAL MEM raster */ - if (!rt_util_gdal_driver_registered("MEM")) - GDALRegister_MEM(); - drv = GDALGetDriverByName("MEM"); - if (NULL == drv) { - rterror("rt_raster_to_gdal_mem: Could not load the MEM GDAL driver"); - return 0; - } - *rtn_drv = drv; - - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - ds = GDALCreate( - drv, "", - width, height, - 0, GDT_Byte, NULL - ); - if (NULL == ds) { - rterror("rt_raster_to_gdal_mem: Could not create a GDALDataset to convert into"); - return 0; - } - - /* add geotransform */ - rt_raster_get_geotransform_matrix(raster, gt); - cplerr = GDALSetGeoTransform(ds, gt); - if (cplerr != CE_None) { - rterror("rt_raster_to_gdal_mem: Could not set geotransformation"); - GDALClose(ds); - return 0; - } - - /* set spatial reference */ - if (NULL != srs && strlen(srs)) { - char *_srs = rt_util_gdal_convert_sr(srs, 0); - if (_srs == NULL) { - rterror("rt_raster_to_gdal_mem: Could not convert srs to GDAL accepted format"); - GDALClose(ds); - return 0; - } - - cplerr = GDALSetProjection(ds, _srs); - CPLFree(_srs); - if (cplerr != CE_None) { - rterror("rt_raster_to_gdal_mem: Could not set projection"); - GDALClose(ds); - return 0; - } - RASTER_DEBUGF(3, "Projection set to: %s", GDALGetProjectionRef(ds)); - } - - /* process bandNums */ - numBands = rt_raster_get_num_bands(raster); - if (NULL != bandNums && count > 0) { - for (i = 0; i < count; i++) { - if (bandNums[i] >= numBands) { - rterror("rt_raster_to_gdal_mem: The band index %d is invalid", bandNums[i]); - GDALClose(ds); - return 0; - } - } - } - else { - count = numBands; - bandNums = (uint32_t *) rtalloc(sizeof(uint32_t) * count); - if (NULL == bandNums) { - rterror("rt_raster_to_gdal_mem: Could not allocate memory for band indices"); - GDALClose(ds); - return 0; - } - allocBandNums = 1; - for (i = 0; i < count; i++) bandNums[i] = i; - } - - /* process exclude_nodata_values */ - if (NULL == excludeNodataValues) { - excludeNodataValues = (int *) rtalloc(sizeof(int) * count); - if (NULL == excludeNodataValues) { - rterror("rt_raster_to_gdal_mem: Could not allocate memory for NODATA flags"); - GDALClose(ds); - return 0; - } - allocNodataValues = 1; - for (i = 0; i < count; i++) excludeNodataValues[i] = 1; - } - - /* add band(s) */ - for (i = 0; i < count; i++) { - rtband = rt_raster_get_band(raster, bandNums[i]); - if (NULL == rtband) { - rterror("rt_raster_to_gdal_mem: Could not get requested band index %d", bandNums[i]); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - - pt = rt_band_get_pixtype(rtband); - gdal_pt = rt_util_pixtype_to_gdal_datatype(pt); - if (gdal_pt == GDT_Unknown) - rtwarn("rt_raster_to_gdal_mem: Unknown pixel type for band"); - - /* - For all pixel types other than PT_8BSI, set pointer to start of data - */ - if (pt != PT_8BSI) { - pVoid = rt_band_get_data(rtband); - RASTER_DEBUGF(4, "Band data is at pos %p", pVoid); - - pszDataPointer = (char *) rtalloc(20 * sizeof (char)); - sprintf(pszDataPointer, "%p", pVoid); - RASTER_DEBUGF(4, "rt_raster_to_gdal_mem: szDatapointer is %p", - pszDataPointer); - - if (strnicmp(pszDataPointer, "0x", 2) == 0) - sprintf(szGDALOption, "DATAPOINTER=%s", pszDataPointer); - else - sprintf(szGDALOption, "DATAPOINTER=0x%s", pszDataPointer); - - RASTER_DEBUG(3, "Storing info for GDAL MEM raster band"); - - apszOptions[0] = szGDALOption; - apszOptions[1] = NULL; /* pixel offset, not needed */ - apszOptions[2] = NULL; /* line offset, not needed */ - apszOptions[3] = NULL; - - /* free */ - rtdealloc(pszDataPointer); - - /* add band */ - if (GDALAddBand(ds, gdal_pt, apszOptions) == CE_Failure) { - rterror("rt_raster_to_gdal_mem: Could not add GDAL raster band"); - if (allocBandNums) rtdealloc(bandNums); - GDALClose(ds); - return 0; - } - } - /* - PT_8BSI is special as GDAL has no equivalent pixel type. - Must convert 8BSI to 16BSI so create basic band - */ - else { - /* add band */ - if (GDALAddBand(ds, gdal_pt, NULL) == CE_Failure) { - rterror("rt_raster_to_gdal_mem: Could not add GDAL raster band"); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - } - - /* check band count */ - if (GDALGetRasterCount(ds) != i + 1) { - rterror("rt_raster_to_gdal_mem: Error creating GDAL MEM raster band"); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - - /* get new band */ - band = NULL; - band = GDALGetRasterBand(ds, i + 1); - if (NULL == band) { - rterror("rt_raster_to_gdal_mem: Could not get GDAL band for additional processing"); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - - /* PT_8BSI requires manual setting of pixels */ - if (pt == PT_8BSI) { - int nXBlocks, nYBlocks; - int nXBlockSize, nYBlockSize; - int iXBlock, iYBlock; - int nXValid, nYValid; - int iX, iY; - int iXMax, iYMax; - - int x, y, z; - uint32_t valueslen = 0; - int16_t *values = NULL; - double value = 0.; - - /* this makes use of GDAL's "natural" blocks */ - GDALGetBlockSize(band, &nXBlockSize, &nYBlockSize); - nXBlocks = (width + nXBlockSize - 1) / nXBlockSize; - nYBlocks = (height + nYBlockSize - 1) / nYBlockSize; - RASTER_DEBUGF(4, "(nXBlockSize, nYBlockSize) = (%d, %d)", nXBlockSize, nYBlockSize); - RASTER_DEBUGF(4, "(nXBlocks, nYBlocks) = (%d, %d)", nXBlocks, nYBlocks); - - /* length is for the desired pixel type */ - valueslen = rt_pixtype_size(PT_16BSI) * nXBlockSize * nYBlockSize; - values = rtalloc(valueslen); - if (NULL == values) { - rterror("rt_raster_to_gdal_mem: Could not allocate memory for GDAL band pixel values"); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - - for (iYBlock = 0; iYBlock < nYBlocks; iYBlock++) { - for (iXBlock = 0; iXBlock < nXBlocks; iXBlock++) { - memset(values, 0, valueslen); - - x = iXBlock * nXBlockSize; - y = iYBlock * nYBlockSize; - RASTER_DEBUGF(4, "(iXBlock, iYBlock) = (%d, %d)", iXBlock, iYBlock); - RASTER_DEBUGF(4, "(x, y) = (%d, %d)", x, y); - - /* valid block width */ - if ((iXBlock + 1) * nXBlockSize > width) - nXValid = width - (iXBlock * nXBlockSize); - else - nXValid = nXBlockSize; - - /* valid block height */ - if ((iYBlock + 1) * nYBlockSize > height) - nYValid = height - (iYBlock * nYBlockSize); - else - nYValid = nYBlockSize; - - RASTER_DEBUGF(4, "(nXValid, nYValid) = (%d, %d)", nXValid, nYValid); - - /* convert 8BSI values to 16BSI */ - z = 0; - iYMax = y + nYValid; - iXMax = x + nXValid; - for (iY = y; iY < iYMax; iY++) { - for (iX = x; iX < iXMax; iX++) { - if (rt_band_get_pixel(rtband, iX, iY, &value, NULL) != ES_NONE) { - rterror("rt_raster_to_gdal_mem: Could not get pixel value to convert from 8BSI to 16BSI"); - rtdealloc(values); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - - values[z++] = rt_util_clamp_to_16BSI(value); - } - } - - /* burn values */ - if (GDALRasterIO( - band, GF_Write, - x, y, - nXValid, nYValid, - values, nXValid, nYValid, - gdal_pt, - 0, 0 - ) != CE_None) { - rterror("rt_raster_to_gdal_mem: Could not write converted 8BSI to 16BSI values to GDAL band"); - rtdealloc(values); - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - GDALClose(ds); - return 0; - } - } - } - - rtdealloc(values); - } - - /* Add nodata value for band */ - if (rt_band_get_hasnodata_flag(rtband) != FALSE && excludeNodataValues[i]) { - rt_band_get_nodata(rtband, &nodata); - if (GDALSetRasterNoDataValue(band, nodata) != CE_None) - rtwarn("rt_raster_to_gdal_mem: Could not set nodata value for band"); - RASTER_DEBUGF(3, "nodata value set to %f", GDALGetRasterNoDataValue(band, NULL)); - } - -#if POSTGIS_DEBUG_LEVEL > 3 - { - GDALRasterBandH _grb = NULL; - double _min; - double _max; - double _mean; - double _stddev; - - _grb = GDALGetRasterBand(ds, i + 1); - GDALComputeRasterStatistics(_grb, FALSE, &_min, &_max, &_mean, &_stddev, NULL, NULL); - RASTER_DEBUGF(4, "GDAL Band %d stats: %f, %f, %f, %f", i + 1, _min, _max, _mean, _stddev); - } -#endif - - } - - /* necessary??? */ - GDALFlushCache(ds); - - if (allocBandNums) rtdealloc(bandNums); - if (allocNodataValues) rtdealloc(excludeNodataValues); - - return ds; -} - -/** - * Return a raster from a GDAL dataset - * - * @param ds : the GDAL dataset to convert to a raster - * - * @return raster or NULL - */ -rt_raster -rt_raster_from_gdal_dataset(GDALDatasetH ds) { - rt_raster rast = NULL; - double gt[6] = {0}; - CPLErr cplerr; - uint32_t width = 0; - uint32_t height = 0; - uint32_t numBands = 0; - int i = 0; - char *authname = NULL; - char *authcode = NULL; - - GDALRasterBandH gdband = NULL; - GDALDataType gdpixtype = GDT_Unknown; - rt_band band; - int32_t idx; - rt_pixtype pt = PT_END; - uint32_t ptlen = 0; - int hasnodata = 0; - double nodataval; - - int x; - int y; - - int nXBlocks, nYBlocks; - int nXBlockSize, nYBlockSize; - int iXBlock, iYBlock; - int nXValid, nYValid; - int iY; - - void *values = NULL; - uint32_t valueslen = 0; - void *ptr = NULL; - - assert(NULL != ds); - - /* raster size */ - width = GDALGetRasterXSize(ds); - height = GDALGetRasterYSize(ds); - RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", width, height); - - /* create new raster */ - RASTER_DEBUG(3, "Creating new raster"); - rast = rt_raster_new(width, height); - if (NULL == rast) { - rterror("rt_raster_from_gdal_dataset: Out of memory allocating new raster"); - return NULL; - } - RASTER_DEBUGF(3, "Created raster dimensions (width x height): %d x %d", rast->width, rast->height); - - /* get raster attributes */ - cplerr = GDALGetGeoTransform(ds, gt); - if (GDALGetGeoTransform(ds, gt) != CE_None) { - RASTER_DEBUG(4, "Using default geotransform matrix (0, 1, 0, 0, 0, -1)"); - gt[0] = 0; - gt[1] = 1; - gt[2] = 0; - gt[3] = 0; - gt[4] = 0; - gt[5] = -1; - } - - /* apply raster attributes */ - rt_raster_set_geotransform_matrix(rast, gt); - - RASTER_DEBUGF(3, "Raster geotransform (%f, %f, %f, %f, %f, %f)", - gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); - - /* srid */ - if (rt_util_gdal_sr_auth_info(ds, &authname, &authcode) == ES_NONE) { - if ( - authname != NULL && - strcmp(authname, "EPSG") == 0 && - authcode != NULL - ) { - rt_raster_set_srid(rast, atoi(authcode)); - RASTER_DEBUGF(3, "New raster's SRID = %d", rast->srid); - } - - if (authname != NULL) - rtdealloc(authname); - if (authcode != NULL) - rtdealloc(authcode); - } - - numBands = GDALGetRasterCount(ds); - -#if POSTGIS_DEBUG_LEVEL > 3 - for (i = 1; i <= numBands; i++) { - GDALRasterBandH _grb = NULL; - double _min; - double _max; - double _mean; - double _stddev; - - _grb = GDALGetRasterBand(ds, i); - GDALComputeRasterStatistics(_grb, FALSE, &_min, &_max, &_mean, &_stddev, NULL, NULL); - RASTER_DEBUGF(4, "GDAL Band %d stats: %f, %f, %f, %f", i, _min, _max, _mean, _stddev); - } -#endif - - /* copy bands */ - for (i = 1; i <= numBands; i++) { - RASTER_DEBUGF(3, "Processing band %d of %d", i, numBands); - gdband = NULL; - gdband = GDALGetRasterBand(ds, i); - if (NULL == gdband) { - rterror("rt_raster_from_gdal_dataset: Could not get GDAL band"); - rt_raster_destroy(rast); - return NULL; - } - RASTER_DEBUGF(4, "gdband @ %p", gdband); - - /* pixtype */ - gdpixtype = GDALGetRasterDataType(gdband); - RASTER_DEBUGF(4, "gdpixtype, size = %s, %d", GDALGetDataTypeName(gdpixtype), GDALGetDataTypeSize(gdpixtype) / 8); - pt = rt_util_gdal_datatype_to_pixtype(gdpixtype); - if (pt == PT_END) { - rterror("rt_raster_from_gdal_dataset: Unknown pixel type for GDAL band"); - rt_raster_destroy(rast); - return NULL; - } - ptlen = rt_pixtype_size(pt); - - /* size: width and height */ - width = GDALGetRasterBandXSize(gdband); - height = GDALGetRasterBandYSize(gdband); - RASTER_DEBUGF(3, "GDAL band dimensions (width x height): %d x %d", width, height); - - /* nodata */ - nodataval = GDALGetRasterNoDataValue(gdband, &hasnodata); - RASTER_DEBUGF(3, "(hasnodata, nodataval) = (%d, %f)", hasnodata, nodataval); - - /* create band object */ - idx = rt_raster_generate_new_band( - rast, pt, - (hasnodata ? nodataval : 0), - hasnodata, nodataval, rt_raster_get_num_bands(rast) - ); - if (idx < 0) { - rterror("rt_raster_from_gdal_dataset: Could not allocate memory for raster band"); - rt_raster_destroy(rast); - return NULL; - } - band = rt_raster_get_band(rast, idx); - RASTER_DEBUGF(3, "Created band of dimension (width x height): %d x %d", band->width, band->height); - - /* this makes use of GDAL's "natural" blocks */ - GDALGetBlockSize(gdband, &nXBlockSize, &nYBlockSize); - nXBlocks = (width + nXBlockSize - 1) / nXBlockSize; - nYBlocks = (height + nYBlockSize - 1) / nYBlockSize; - RASTER_DEBUGF(4, "(nXBlockSize, nYBlockSize) = (%d, %d)", nXBlockSize, nYBlockSize); - RASTER_DEBUGF(4, "(nXBlocks, nYBlocks) = (%d, %d)", nXBlocks, nYBlocks); - - /* allocate memory for values */ - valueslen = ptlen * nXBlockSize * nYBlockSize; - switch (gdpixtype) { - case GDT_Byte: - values = (uint8_t *) rtalloc(valueslen); - break; - case GDT_UInt16: - values = (uint16_t *) rtalloc(valueslen); - break; - case GDT_Int16: - values = (int16_t *) rtalloc(valueslen); - break; - case GDT_UInt32: - values = (uint32_t *) rtalloc(valueslen); - break; - case GDT_Int32: - values = (int32_t *) rtalloc(valueslen); - break; - case GDT_Float32: - values = (float *) rtalloc(valueslen); - break; - case GDT_Float64: - values = (double *) rtalloc(valueslen); - break; - default: - /* should NEVER get here */ - rterror("rt_raster_from_gdal_dataset: Could not allocate memory for unknown pixel type"); - rt_raster_destroy(rast); - return NULL; - } - if (values == NULL) { - rterror("rt_raster_from_gdal_dataset: Could not allocate memory for GDAL band pixel values"); - rt_raster_destroy(rast); - return NULL; - } - RASTER_DEBUGF(3, "values @ %p of length = %d", values, valueslen); - - for (iYBlock = 0; iYBlock < nYBlocks; iYBlock++) { - for (iXBlock = 0; iXBlock < nXBlocks; iXBlock++) { - x = iXBlock * nXBlockSize; - y = iYBlock * nYBlockSize; - RASTER_DEBUGF(4, "(iXBlock, iYBlock) = (%d, %d)", iXBlock, iYBlock); - RASTER_DEBUGF(4, "(x, y) = (%d, %d)", x, y); - - memset(values, 0, valueslen); - - /* valid block width */ - if ((iXBlock + 1) * nXBlockSize > width) - nXValid = width - (iXBlock * nXBlockSize); - else - nXValid = nXBlockSize; - - /* valid block height */ - if ((iYBlock + 1) * nYBlockSize > height) - nYValid = height - (iYBlock * nYBlockSize); - else - nYValid = nYBlockSize; - - RASTER_DEBUGF(4, "(nXValid, nYValid) = (%d, %d)", nXValid, nYValid); - - cplerr = GDALRasterIO( - gdband, GF_Read, - x, y, - nXValid, nYValid, - values, nXValid, nYValid, - gdpixtype, - 0, 0 - ); - if (cplerr != CE_None) { - rterror("rt_raster_from_gdal_dataset: Could not get data from GDAL raster"); - rtdealloc(values); - rt_raster_destroy(rast); - return NULL; - } - - /* if block width is same as raster width, shortcut */ - if (nXBlocks == 1 && nYBlockSize > 1 && nXValid == width) { - x = 0; - y = nYBlockSize * iYBlock; - - RASTER_DEBUGF(4, "Setting set of pixel lines at (%d, %d) for %d pixels", x, y, nXValid * nYValid); - rt_band_set_pixel_line(band, x, y, values, nXValid * nYValid); - } - else { - ptr = values; - x = nXBlockSize * iXBlock; - for (iY = 0; iY < nYValid; iY++) { - y = iY + (nYBlockSize * iYBlock); - - RASTER_DEBUGF(4, "Setting pixel line at (%d, %d) for %d pixels", x, y, nXValid); - rt_band_set_pixel_line(band, x, y, ptr, nXValid); - ptr += (nXValid * ptlen); - } - } - } - } - - /* free memory */ - rtdealloc(values); - } - - return rast; -} - -/****************************************************************************** -* rt_raster_gdal_warp() -******************************************************************************/ - -typedef struct _rti_warp_arg_t* _rti_warp_arg; -struct _rti_warp_arg_t { - - struct { - GDALDriverH drv; - GDALDatasetH ds; - char *srs; - } src, dst; - - GDALWarpOptions *wopts; - - struct { - struct { - char **item; - int len; - } option; - - struct { - void *transform; - void *imgproj; - void *approx; - } arg; - - GDALTransformerFunc func; - } transform; - -}; - -static _rti_warp_arg -_rti_warp_arg_init() { - _rti_warp_arg arg = NULL; - - arg = rtalloc(sizeof(struct _rti_warp_arg_t)); - if (arg == NULL) { - rterror("_rti_warp_arg_init: Could not allocate memory for _rti_warp_arg"); - return NULL; - } - - arg->src.drv = NULL; - arg->src.ds = NULL; - arg->src.srs = NULL; - - arg->dst.drv = NULL; - arg->dst.ds = NULL; - arg->dst.srs = NULL; - - arg->wopts = NULL; - - arg->transform.option.item = NULL; - arg->transform.option.len = 0; - - arg->transform.arg.transform = NULL; - arg->transform.arg.imgproj = NULL; - arg->transform.arg.approx = NULL; - - arg->transform.func = NULL; - - return arg; -} - -static void -_rti_warp_arg_destroy(_rti_warp_arg arg) { - int i = 0; - - if (arg->dst.ds != NULL) - GDALClose(arg->dst.ds); - if (arg->dst.srs != NULL) - CPLFree(arg->dst.srs); - - if (arg->src.ds != NULL) - GDALClose(arg->src.ds); - if (arg->src.srs != NULL) - CPLFree(arg->src.srs); - - if (arg->transform.func == GDALApproxTransform) { - if (arg->transform.arg.imgproj != NULL) - GDALDestroyGenImgProjTransformer(arg->transform.arg.imgproj); - } - - if (arg->wopts != NULL) - GDALDestroyWarpOptions(arg->wopts); - - if (arg->transform.option.len > 0 && arg->transform.option.item != NULL) { - for (i = 0; i < arg->transform.option.len; i++) { - if (arg->transform.option.item[i] != NULL) - rtdealloc(arg->transform.option.item[i]); - } - rtdealloc(arg->transform.option.item); - } - - rtdealloc(arg); - arg = NULL; -} - -/** - * Return a warped raster using GDAL Warp API - * - * @param raster : raster to transform - * @param src_srs : the raster's coordinate system in OGC WKT - * @param dst_srs : the warped raster's coordinate system in OGC WKT - * @param scale_x : the x size of pixels of the warped raster's pixels in - * units of dst_srs - * @param scale_y : the y size of pixels of the warped raster's pixels in - * units of dst_srs - * @param width : the number of columns of the warped raster. note that - * width/height CANNOT be used with scale_x/scale_y - * @param height : the number of rows of the warped raster. note that - * width/height CANNOT be used with scale_x/scale_y - * @param ul_xw : the X value of upper-left corner of the warped raster in - * units of dst_srs - * @param ul_yw : the Y value of upper-left corner of the warped raster in - * units of dst_srs - * @param grid_xw : the X value of point on a grid to align warped raster - * to in units of dst_srs - * @param grid_yw : the Y value of point on a grid to align warped raster - * to in units of dst_srs - * @param skew_x : the X skew of the warped raster in units of dst_srs - * @param skew_y : the Y skew of the warped raster in units of dst_srs - * @param resample_alg : the resampling algorithm - * @param max_err : maximum error measured in input pixels permitted - * (0.0 for exact calculations) - * - * @return the warped raster or NULL - */ -rt_raster rt_raster_gdal_warp( - rt_raster raster, - const char *src_srs, const char *dst_srs, - double *scale_x, double *scale_y, - int *width, int *height, - double *ul_xw, double *ul_yw, - double *grid_xw, double *grid_yw, - double *skew_x, double *skew_y, - GDALResampleAlg resample_alg, double max_err -) { - CPLErr cplerr; - char *dst_options[] = {"SUBCLASS=VRTWarpedDataset", NULL}; - _rti_warp_arg arg = NULL; - - int hasnodata = 0; - - GDALRasterBandH band; - rt_band rtband = NULL; - rt_pixtype pt = PT_END; - GDALDataType gdal_pt = GDT_Unknown; - double nodata = 0.0; - - double _gt[6] = {0}; - double dst_extent[4]; - rt_envelope extent; - - int _dim[2] = {0}; - double _skew[2] = {0}; - double _scale[2] = {0}; - int ul_user = 0; - - rt_raster rast = NULL; - int i = 0; - int numBands = 0; - - int subgt = 0; - - RASTER_DEBUG(3, "starting"); - - assert(NULL != raster); - - /* internal variables */ - arg = _rti_warp_arg_init(); - if (arg == NULL) { - rterror("rt_raster_gdal_warp: Could not initialize internal variables"); - return NULL; - } - - /* - max_err must be gte zero - - the value 0.125 is the default used in gdalwarp.cpp on line 283 - */ - if (max_err < 0.) max_err = 0.125; - RASTER_DEBUGF(4, "max_err = %f", max_err); - - /* handle srs */ - if (src_srs != NULL) { - /* reprojection taking place */ - if (dst_srs != NULL && strcmp(src_srs, dst_srs) != 0) { - RASTER_DEBUG(4, "Warp operation does include a reprojection"); - arg->src.srs = rt_util_gdal_convert_sr(src_srs, 0); - arg->dst.srs = rt_util_gdal_convert_sr(dst_srs, 0); - - if (arg->src.srs == NULL || arg->dst.srs == NULL) { - rterror("rt_raster_gdal_warp: Could not convert srs values to GDAL accepted format"); - _rti_warp_arg_destroy(arg); - return NULL; - } - } - /* no reprojection, a stub just for clarity */ - else { - RASTER_DEBUG(4, "Warp operation does NOT include reprojection"); - } - } - else if (dst_srs != NULL) { - /* dst_srs provided but not src_srs */ - rterror("rt_raster_gdal_warp: SRS required for input raster if SRS provided for warped raster"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* load raster into a GDAL MEM dataset */ - arg->src.ds = rt_raster_to_gdal_mem(raster, arg->src.srs, NULL, NULL, 0, &(arg->src.drv)); - if (NULL == arg->src.ds) { - rterror("rt_raster_gdal_warp: Could not convert raster to GDAL MEM format"); - _rti_warp_arg_destroy(arg); - return NULL; - } - RASTER_DEBUG(3, "raster loaded into GDAL MEM dataset"); - - /* special case when src_srs and dst_srs is NULL and raster's geotransform matrix is default */ - if (src_srs == NULL && dst_srs == NULL && rt_raster_get_srid(raster) == SRID_UNKNOWN) { - double gt[6]; - -#if POSTGIS_DEBUG_LEVEL > 3 - GDALGetGeoTransform(arg->src.ds, gt); - RASTER_DEBUGF(3, "GDAL MEM geotransform: %f, %f, %f, %f, %f, %f", - gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); -#endif - - /* default geotransform */ - rt_raster_get_geotransform_matrix(raster, gt); - RASTER_DEBUGF(3, "raster geotransform: %f, %f, %f, %f, %f, %f", - gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); - - if ( - FLT_EQ(gt[0], 0) && - FLT_EQ(gt[1], 1) && - FLT_EQ(gt[2], 0) && - FLT_EQ(gt[3], 0) && - FLT_EQ(gt[4], 0) && - FLT_EQ(gt[5], -1) - ) { - double ngt[6] = {0, 10, 0, 0, 0, -10}; - - rtinfo("Raster has default geotransform. Adjusting metadata for use of GDAL Warp API"); - - GDALSetGeoTransform(arg->src.ds, ngt); - GDALFlushCache(arg->src.ds); - - subgt = 1; - -#if POSTGIS_DEBUG_LEVEL > 3 - GDALGetGeoTransform(arg->src.ds, gt); - RASTER_DEBUGF(3, "GDAL MEM geotransform: %f, %f, %f, %f, %f, %f", - gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); -#endif - } - } - - /* set transform options */ - if (arg->src.srs != NULL || arg->dst.srs != NULL) { - arg->transform.option.len = 2; - arg->transform.option.item = rtalloc(sizeof(char *) * (arg->transform.option.len + 1)); - if (NULL == arg->transform.option.item) { - rterror("rt_raster_gdal_warp: Could not allocation memory for transform options"); - _rti_warp_arg_destroy(arg); - return NULL; - } - memset(arg->transform.option.item, 0, sizeof(char *) * (arg->transform.option.len + 1)); - - for (i = 0; i < arg->transform.option.len; i++) { - switch (i) { - case 1: - if (arg->dst.srs != NULL) - arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("DST_SRS=") + strlen(arg->dst.srs) + 1)); - else - arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("DST_SRS=") + 1)); - break; - case 0: - if (arg->src.srs != NULL) - arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("SRC_SRS=") + strlen(arg->src.srs) + 1)); - else - arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("SRC_SRS=") + 1)); - break; - } - if (NULL == arg->transform.option.item[i]) { - rterror("rt_raster_gdal_warp: Could not allocation memory for transform options"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - switch (i) { - case 1: - if (arg->dst.srs != NULL) { - snprintf( - arg->transform.option.item[i], - sizeof(char) * (strlen("DST_SRS=") + strlen(arg->dst.srs) + 1), - "DST_SRS=%s", - arg->dst.srs - ); - } - else - sprintf(arg->transform.option.item[i], "%s", "DST_SRS="); - break; - case 0: - if (arg->src.srs != NULL) { - snprintf( - arg->transform.option.item[i], - sizeof(char) * (strlen("SRC_SRS=") + strlen(arg->src.srs) + 1), - "SRC_SRS=%s", - arg->src.srs - ); - } - else - sprintf(arg->transform.option.item[i], "%s", "SRC_SRS="); - break; - } - RASTER_DEBUGF(4, "arg->transform.option.item[%d] = %s", i, arg->transform.option.item[i]); - } - } - else - arg->transform.option.len = 0; - - /* transformation object for building dst dataset */ - arg->transform.arg.transform = GDALCreateGenImgProjTransformer2(arg->src.ds, NULL, arg->transform.option.item); - if (NULL == arg->transform.arg.transform) { - rterror("rt_raster_gdal_warp: Could not create GDAL transformation object for output dataset creation"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* get approximate output georeferenced bounds and resolution */ - cplerr = GDALSuggestedWarpOutput2( - arg->src.ds, GDALGenImgProjTransform, - arg->transform.arg.transform, _gt, &(_dim[0]), &(_dim[1]), dst_extent, 0); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_warp: Could not get GDAL suggested warp output for output dataset creation"); - _rti_warp_arg_destroy(arg); - return NULL; - } - GDALDestroyGenImgProjTransformer(arg->transform.arg.transform); - arg->transform.arg.transform = NULL; - - /* - don't use suggested dimensions as use of suggested scales - on suggested extent will result in suggested dimensions - */ - _dim[0] = 0; - _dim[1] = 0; - - RASTER_DEBUGF(3, "Suggested geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); - - /* store extent in easier-to-use object */ - extent.MinX = dst_extent[0]; - extent.MinY = dst_extent[1]; - extent.MaxX = dst_extent[2]; - extent.MaxY = dst_extent[3]; - - extent.UpperLeftX = dst_extent[0]; - extent.UpperLeftY = dst_extent[3]; - - RASTER_DEBUGF(3, "Suggested extent: %f, %f, %f, %f", - extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); - - /* scale and width/height are mutually exclusive */ - if ( - ((NULL != scale_x) || (NULL != scale_y)) && - ((NULL != width) || (NULL != height)) - ) { - rterror("rt_raster_gdal_warp: Scale X/Y and width/height are mutually exclusive. Only provide one"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* user-defined width */ - if (NULL != width) { - _dim[0] = abs(*width); - _scale[0] = fabs((extent.MaxX - extent.MinX) / ((double) _dim[0])); - } - /* user-defined height */ - if (NULL != height) { - _dim[1] = abs(*height); - _scale[1] = fabs((extent.MaxY - extent.MinY) / ((double) _dim[1])); - } - - /* user-defined scale */ - if ( - ((NULL != scale_x) && (FLT_NEQ(*scale_x, 0.0))) && - ((NULL != scale_y) && (FLT_NEQ(*scale_y, 0.0))) - ) { - _scale[0] = fabs(*scale_x); - _scale[1] = fabs(*scale_y); - - /* special override */ - if (subgt) { - _scale[0] *= 10; - _scale[1] *= 10; - } - } - else if ( - ((NULL != scale_x) && (NULL == scale_y)) || - ((NULL == scale_x) && (NULL != scale_y)) - ) { - rterror("rt_raster_gdal_warp: Both X and Y scale values must be provided for scale"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* scale not defined, use suggested */ - if (FLT_EQ(_scale[0], 0) && FLT_EQ(_scale[1], 0)) { - _scale[0] = fabs(_gt[1]); - _scale[1] = fabs(_gt[5]); - } - - RASTER_DEBUGF(4, "Using scale: %f x %f", _scale[0], -1 * _scale[1]); - - /* user-defined skew */ - if (NULL != skew_x) { - _skew[0] = *skew_x; - - /* - negative scale-x affects skew - for now, force skew to be in left-right, top-down orientation - */ - if ( - NULL != scale_x && - *scale_x < 0. - ) { - _skew[0] *= -1; - } - } - if (NULL != skew_y) { - _skew[1] = *skew_y; - - /* - positive scale-y affects skew - for now, force skew to be in left-right, top-down orientation - */ - if ( - NULL != scale_y && - *scale_y > 0. - ) { - _skew[1] *= -1; - } - } - - RASTER_DEBUGF(4, "Using skew: %f x %f", _skew[0], _skew[1]); - - /* reprocess extent if skewed */ - if ( - FLT_NEQ(_skew[0], 0) || - FLT_NEQ(_skew[1], 0) - ) { - rt_raster skewedrast; - - RASTER_DEBUG(3, "Computing skewed extent's envelope"); - - skewedrast = rt_raster_compute_skewed_raster( - extent, - _skew, - _scale, - 0.01 - ); - if (skewedrast == NULL) { - rterror("rt_raster_gdal_warp: Could not compute skewed raster"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - if (_dim[0] == 0) - _dim[0] = skewedrast->width; - if (_dim[1] == 0) - _dim[1] = skewedrast->height; - - extent.UpperLeftX = skewedrast->ipX; - extent.UpperLeftY = skewedrast->ipY; - - rt_raster_destroy(skewedrast); - } - - /* dimensions not defined, compute */ - if (!_dim[0]) - _dim[0] = (int) fmax((fabs(extent.MaxX - extent.MinX) + (_scale[0] / 2.)) / _scale[0], 1); - if (!_dim[1]) - _dim[1] = (int) fmax((fabs(extent.MaxY - extent.MinY) + (_scale[1] / 2.)) / _scale[1], 1); - - /* temporary raster */ - rast = rt_raster_new(_dim[0], _dim[1]); - if (rast == NULL) { - rterror("rt_raster_gdal_warp: Out of memory allocating temporary raster"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* set raster's spatial attributes */ - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - rt_raster_set_scale(rast, _scale[0], -1 * _scale[1]); - rt_raster_set_skews(rast, _skew[0], _skew[1]); - - rt_raster_get_geotransform_matrix(rast, _gt); - RASTER_DEBUGF(3, "Temp raster's geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); - RASTER_DEBUGF(3, "Temp raster's dimensions (width x height): %d x %d", - _dim[0], _dim[1]); - - /* user-defined upper-left corner */ - if ( - NULL != ul_xw && - NULL != ul_yw - ) { - ul_user = 1; - - RASTER_DEBUGF(4, "Using user-specified upper-left corner: %f, %f", *ul_xw, *ul_yw); - - /* set upper-left corner */ - rt_raster_set_offsets(rast, *ul_xw, *ul_yw); - extent.UpperLeftX = *ul_xw; - extent.UpperLeftY = *ul_yw; - } - else if ( - ((NULL != ul_xw) && (NULL == ul_yw)) || - ((NULL == ul_xw) && (NULL != ul_yw)) - ) { - rterror("rt_raster_gdal_warp: Both X and Y upper-left corner values must be provided"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* alignment only considered if upper-left corner not provided */ - if ( - !ul_user && ( - (NULL != grid_xw) || (NULL != grid_yw) - ) - ) { - - if ( - ((NULL != grid_xw) && (NULL == grid_yw)) || - ((NULL == grid_xw) && (NULL != grid_yw)) - ) { - rterror("rt_raster_gdal_warp: Both X and Y alignment values must be provided"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - RASTER_DEBUGF(4, "Aligning extent to user-specified grid: %f, %f", *grid_xw, *grid_yw); - - do { - double _r[2] = {0}; - double _w[2] = {0}; - - /* raster is already aligned */ - if (FLT_EQ(*grid_xw, extent.UpperLeftX) && FLT_EQ(*grid_yw, extent.UpperLeftY)) { - RASTER_DEBUG(3, "Skipping raster alignment as it is already aligned to grid"); - break; - } - - extent.UpperLeftX = rast->ipX; - extent.UpperLeftY = rast->ipY; - rt_raster_set_offsets(rast, *grid_xw, *grid_yw); - - /* process upper-left corner */ - if (rt_raster_geopoint_to_cell( - rast, - extent.UpperLeftX, extent.UpperLeftY, - &(_r[0]), &(_r[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute raster pixel for spatial coordinates"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - if (rt_raster_cell_to_geopoint( - rast, - _r[0], _r[1], - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* shift occurred */ - if (FLT_NEQ(_w[0], extent.UpperLeftX)) { - if (NULL == width) - rast->width++; - else if (NULL == scale_x) { - double _c[2] = {0}; - - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - - /* get upper-right corner */ - if (rt_raster_cell_to_geopoint( - rast, - rast->width, 0, - &(_c[0]), &(_c[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - rast->scaleX = fabs((_c[0] - _w[0]) / ((double) rast->width)); - } - } - if (FLT_NEQ(_w[1], extent.UpperLeftY)) { - if (NULL == height) - rast->height++; - else if (NULL == scale_y) { - double _c[2] = {0}; - - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - - /* get upper-right corner */ - if (rt_raster_cell_to_geopoint( - rast, - 0, rast->height, - &(_c[0]), &(_c[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - rast->scaleY = -1 * fabs((_c[1] - _w[1]) / ((double) rast->height)); - } - } - - rt_raster_set_offsets(rast, _w[0], _w[1]); - RASTER_DEBUGF(4, "aligned offsets: %f x %f", _w[0], _w[1]); - } - while (0); - } - - /* - after this point, rt_envelope extent is no longer used - */ - - /* get key attributes from rast */ - _dim[0] = rast->width; - _dim[1] = rast->height; - rt_raster_get_geotransform_matrix(rast, _gt); - - /* scale-x is negative or scale-y is positive */ - if (( - (NULL != scale_x) && (*scale_x < 0.) - ) || ( - (NULL != scale_y) && (*scale_y > 0) - )) { - double _w[2] = {0}; - - /* negative scale-x */ - if ( - (NULL != scale_x) && - (*scale_x < 0.) - ) { - if (rt_raster_cell_to_geopoint( - rast, - rast->width, 0, - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - _gt[0] = _w[0]; - _gt[1] = *scale_x; - - /* check for skew */ - if (NULL != skew_x && FLT_NEQ(*skew_x, 0)) - _gt[2] = *skew_x; - } - /* positive scale-y */ - if ( - (NULL != scale_y) && - (*scale_y > 0) - ) { - if (rt_raster_cell_to_geopoint( - rast, - 0, rast->height, - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); - rt_raster_destroy(rast); - _rti_warp_arg_destroy(arg); - return NULL; - } - - _gt[3] = _w[1]; - _gt[5] = *scale_y; - - /* check for skew */ - if (NULL != skew_y && FLT_NEQ(*skew_y, 0)) - _gt[4] = *skew_y; - } - } - - rt_raster_destroy(rast); - rast = NULL; - - RASTER_DEBUGF(3, "Applied geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); - RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", - _dim[0], _dim[1]); - - if (FLT_EQ(_dim[0], 0) || FLT_EQ(_dim[1], 0)) { - rterror("rt_raster_gdal_warp: The width (%f) or height (%f) of the warped raster is zero", _dim[0], _dim[1]); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* load VRT driver */ - if (!rt_util_gdal_driver_registered("VRT")) - GDALRegister_VRT(); - arg->dst.drv = GDALGetDriverByName("VRT"); - if (NULL == arg->dst.drv) { - rterror("rt_raster_gdal_warp: Could not load the output GDAL VRT driver"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* create dst dataset */ - arg->dst.ds = GDALCreate(arg->dst.drv, "", _dim[0], _dim[1], 0, GDT_Byte, dst_options); - if (NULL == arg->dst.ds) { - rterror("rt_raster_gdal_warp: Could not create GDAL VRT dataset"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* set dst srs */ - if (arg->dst.srs != NULL) { - cplerr = GDALSetProjection(arg->dst.ds, arg->dst.srs); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_warp: Could not set projection"); - _rti_warp_arg_destroy(arg); - return NULL; - } - RASTER_DEBUGF(3, "Applied SRS: %s", GDALGetProjectionRef(arg->dst.ds)); - } - - /* set dst geotransform */ - cplerr = GDALSetGeoTransform(arg->dst.ds, _gt); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_warp: Could not set geotransform"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* add bands to dst dataset */ - numBands = rt_raster_get_num_bands(raster); - for (i = 0; i < numBands; i++) { - rtband = rt_raster_get_band(raster, i); - if (NULL == rtband) { - rterror("rt_raster_gdal_warp: Could not get band %d for adding to VRT dataset", i); - _rti_warp_arg_destroy(arg); - return NULL; - } - - pt = rt_band_get_pixtype(rtband); - gdal_pt = rt_util_pixtype_to_gdal_datatype(pt); - if (gdal_pt == GDT_Unknown) - rtwarn("rt_raster_gdal_warp: Unknown pixel type for band %d", i); - - cplerr = GDALAddBand(arg->dst.ds, gdal_pt, NULL); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_warp: Could not add band to VRT dataset"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* get band to write data to */ - band = NULL; - band = GDALGetRasterBand(arg->dst.ds, i + 1); - if (NULL == band) { - rterror("rt_raster_gdal_warp: Could not get GDAL band for additional processing"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* set nodata */ - if (rt_band_get_hasnodata_flag(rtband) != FALSE) { - hasnodata = 1; - rt_band_get_nodata(rtband, &nodata); - if (GDALSetRasterNoDataValue(band, nodata) != CE_None) - rtwarn("rt_raster_gdal_warp: Could not set nodata value for band %d", i); - RASTER_DEBUGF(3, "nodata value set to %f", GDALGetRasterNoDataValue(band, NULL)); - } - } - - /* create transformation object */ - arg->transform.arg.transform = arg->transform.arg.imgproj = GDALCreateGenImgProjTransformer2( - arg->src.ds, arg->dst.ds, - arg->transform.option.item - ); - if (NULL == arg->transform.arg.transform) { - rterror("rt_raster_gdal_warp: Could not create GDAL transformation object"); - _rti_warp_arg_destroy(arg); - return NULL; - } - arg->transform.func = GDALGenImgProjTransform; - - /* use approximate transformation object */ - if (max_err > 0.0) { - arg->transform.arg.transform = arg->transform.arg.approx = GDALCreateApproxTransformer( - GDALGenImgProjTransform, - arg->transform.arg.imgproj, max_err - ); - if (NULL == arg->transform.arg.transform) { - rterror("rt_raster_gdal_warp: Could not create GDAL approximate transformation object"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - arg->transform.func = GDALApproxTransform; - } - - /* warp options */ - arg->wopts = GDALCreateWarpOptions(); - if (NULL == arg->wopts) { - rterror("rt_raster_gdal_warp: Could not create GDAL warp options object"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - /* set options */ - arg->wopts->eResampleAlg = resample_alg; - arg->wopts->hSrcDS = arg->src.ds; - arg->wopts->hDstDS = arg->dst.ds; - arg->wopts->pfnTransformer = arg->transform.func; - arg->wopts->pTransformerArg = arg->transform.arg.transform; - arg->wopts->papszWarpOptions = (char **) CPLMalloc(sizeof(char *) * 2); - arg->wopts->papszWarpOptions[0] = (char *) CPLMalloc(sizeof(char) * (strlen("INIT_DEST=NO_DATA") + 1)); - strcpy(arg->wopts->papszWarpOptions[0], "INIT_DEST=NO_DATA"); - arg->wopts->papszWarpOptions[1] = NULL; - - /* set band mapping */ - arg->wopts->nBandCount = numBands; - arg->wopts->panSrcBands = (int *) CPLMalloc(sizeof(int) * arg->wopts->nBandCount); - arg->wopts->panDstBands = (int *) CPLMalloc(sizeof(int) * arg->wopts->nBandCount); - for (i = 0; i < arg->wopts->nBandCount; i++) - arg->wopts->panDstBands[i] = arg->wopts->panSrcBands[i] = i + 1; - - /* set nodata mapping */ - if (hasnodata) { - RASTER_DEBUG(3, "Setting nodata mapping"); - arg->wopts->padfSrcNoDataReal = (double *) CPLMalloc(numBands * sizeof(double)); - arg->wopts->padfDstNoDataReal = (double *) CPLMalloc(numBands * sizeof(double)); - arg->wopts->padfSrcNoDataImag = (double *) CPLMalloc(numBands * sizeof(double)); - arg->wopts->padfDstNoDataImag = (double *) CPLMalloc(numBands * sizeof(double)); - if ( - NULL == arg->wopts->padfSrcNoDataReal || - NULL == arg->wopts->padfDstNoDataReal || - NULL == arg->wopts->padfSrcNoDataImag || - NULL == arg->wopts->padfDstNoDataImag - ) { - rterror("rt_raster_gdal_warp: Out of memory allocating nodata mapping"); - _rti_warp_arg_destroy(arg); - return NULL; - } - for (i = 0; i < numBands; i++) { - band = rt_raster_get_band(raster, i); - if (!band) { - rterror("rt_raster_gdal_warp: Could not process bands for nodata values"); - _rti_warp_arg_destroy(arg); - return NULL; - } - - if (!rt_band_get_hasnodata_flag(band)) { - /* - based on line 1004 of gdalwarp.cpp - the problem is that there is a chance that this number is a legitimate value - */ - arg->wopts->padfSrcNoDataReal[i] = -123456.789; - } - else { - rt_band_get_nodata(band, &(arg->wopts->padfSrcNoDataReal[i])); - } - - arg->wopts->padfDstNoDataReal[i] = arg->wopts->padfSrcNoDataReal[i]; - arg->wopts->padfDstNoDataImag[i] = arg->wopts->padfSrcNoDataImag[i] = 0.0; - RASTER_DEBUGF(4, "Mapped nodata value for band %d: %f (%f) => %f (%f)", - i, - arg->wopts->padfSrcNoDataReal[i], arg->wopts->padfSrcNoDataImag[i], - arg->wopts->padfDstNoDataReal[i], arg->wopts->padfDstNoDataImag[i] - ); - } - } - - /* warp raster */ - RASTER_DEBUG(3, "Warping raster"); - cplerr = GDALInitializeWarpedVRT(arg->dst.ds, arg->wopts); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_warp: Could not warp raster"); - _rti_warp_arg_destroy(arg); - return NULL; - } - /* - GDALSetDescription(arg->dst.ds, "/tmp/warped.vrt"); - */ - GDALFlushCache(arg->dst.ds); - RASTER_DEBUG(3, "Raster warped"); - - /* convert gdal dataset to raster */ - RASTER_DEBUG(3, "Converting GDAL dataset to raster"); - rast = rt_raster_from_gdal_dataset(arg->dst.ds); - - _rti_warp_arg_destroy(arg); - - if (NULL == rast) { - rterror("rt_raster_gdal_warp: Could not warp raster"); - return NULL; - } - - /* substitute geotransform matrix, reset back to default */ - if (subgt) { - double gt[6] = {0, 1, 0, 0, 0, -1}; - - rt_raster_set_geotransform_matrix(rast, gt); - } - - RASTER_DEBUG(3, "done"); - - return rast; -} - -/****************************************************************************** -* rt_raster_gdal_rasterize() -******************************************************************************/ - -typedef struct _rti_rasterize_arg_t* _rti_rasterize_arg; -struct _rti_rasterize_arg_t { - uint8_t noband; - - uint32_t numbands; - - rt_pixtype *pixtype; - double *init; - double *nodata; - uint8_t *hasnodata; - double *value; - int *bandlist; -}; - -static _rti_rasterize_arg -_rti_rasterize_arg_init() { - _rti_rasterize_arg arg = NULL; - - arg = rtalloc(sizeof(struct _rti_rasterize_arg_t)); - if (arg == NULL) { - rterror("_rti_rasterize_arg_init: Could not allocate memory for _rti_rasterize_arg"); - return NULL; - } - - arg->noband = 0; - - arg->numbands = 0; - arg->pixtype = NULL; - arg->init = NULL; - arg->nodata = NULL; - arg->hasnodata = NULL; - arg->value = NULL; - arg->bandlist = NULL; - - return arg; -} - -static void -_rti_rasterize_arg_destroy(_rti_rasterize_arg arg) { - if (arg->noband) { - if (arg->pixtype != NULL) - rtdealloc(arg->pixtype); - if (arg->init != NULL) - rtdealloc(arg->init); - if (arg->nodata != NULL) - rtdealloc(arg->nodata); - if (arg->hasnodata != NULL) - rtdealloc(arg->hasnodata); - if (arg->value != NULL) - rtdealloc(arg->value); - } - - if (arg->bandlist != NULL) - rtdealloc(arg->bandlist); - - rtdealloc(arg); -} - -/** - * Return a raster of the provided geometry - * - * @param wkb : WKB representation of the geometry to convert - * @param wkb_len : length of the WKB representation of the geometry - * @param srs : the geometry's coordinate system in OGC WKT - * @param num_bands : number of bands in the output raster - * @param pixtype : data type of each band - * @param init : array of values to initialize each band with - * @param value : array of values for pixels of geometry - * @param nodata : array of nodata values for each band - * @param hasnodata : array flagging the presence of nodata for each band - * @param width : the number of columns of the raster - * @param height : the number of rows of the raster - * @param scale_x : the pixel width of the raster - * @param scale_y : the pixel height of the raster - * @param ul_xw : the X value of upper-left corner of the raster - * @param ul_yw : the Y value of upper-left corner of the raster - * @param grid_xw : the X value of point on grid to align raster to - * @param grid_yw : the Y value of point on grid to align raster to - * @param skew_x : the X skew of the raster - * @param skew_y : the Y skew of the raster - * @param options : array of options. only option is "ALL_TOUCHED" - * - * @return the raster of the provided geometry or NULL - */ -rt_raster -rt_raster_gdal_rasterize( - const unsigned char *wkb, uint32_t wkb_len, - const char *srs, - uint32_t num_bands, rt_pixtype *pixtype, - double *init, double *value, - double *nodata, uint8_t *hasnodata, - int *width, int *height, - double *scale_x, double *scale_y, - double *ul_xw, double *ul_yw, - double *grid_xw, double *grid_yw, - double *skew_x, double *skew_y, - char **options -) { - rt_raster rast = NULL; - int i = 0; - int err = 0; - - _rti_rasterize_arg arg = NULL; - - int _dim[2] = {0}; - double _scale[2] = {0}; - double _skew[2] = {0}; - - OGRErr ogrerr; - OGRSpatialReferenceH src_sr = NULL; - OGRGeometryH src_geom; - OGREnvelope src_env; - rt_envelope extent; - OGRwkbGeometryType wkbtype = wkbUnknown; - - int ul_user = 0; - - CPLErr cplerr; - double _gt[6] = {0}; - GDALDriverH _drv = NULL; - GDALDatasetH _ds = NULL; - GDALRasterBandH _band = NULL; - - uint16_t _width = 0; - uint16_t _height = 0; - - RASTER_DEBUG(3, "starting"); - - assert(NULL != wkb); - assert(0 != wkb_len); - - /* internal variables */ - arg = _rti_rasterize_arg_init(); - if (arg == NULL) { - rterror("rt_raster_gdal_rasterize: Could not initialize internal variables"); - return NULL; - } - - /* no bands, raster is a mask */ - if (num_bands < 1) { - arg->noband = 1; - arg->numbands = 1; - - arg->pixtype = (rt_pixtype *) rtalloc(sizeof(rt_pixtype)); - arg->pixtype[0] = PT_8BUI; - - arg->init = (double *) rtalloc(sizeof(double)); - arg->init[0] = 0; - - arg->nodata = (double *) rtalloc(sizeof(double)); - arg->nodata[0] = 0; - - arg->hasnodata = (uint8_t *) rtalloc(sizeof(uint8_t)); - arg->hasnodata[0] = 1; - - arg->value = (double *) rtalloc(sizeof(double)); - arg->value[0] = 1; - } - else { - arg->noband = 0; - arg->numbands = num_bands; - - arg->pixtype = pixtype; - arg->init = init; - arg->nodata = nodata; - arg->hasnodata = hasnodata; - arg->value = value; - } - - /* OGR spatial reference */ - if (NULL != srs && strlen(srs)) { - src_sr = OSRNewSpatialReference(NULL); - if (OSRSetFromUserInput(src_sr, srs) != OGRERR_NONE) { - rterror("rt_raster_gdal_rasterize: Could not create OSR spatial reference using the provided srs: %s", srs); - _rti_rasterize_arg_destroy(arg); - return NULL; - } - } - - /* convert WKB to OGR Geometry */ - ogrerr = OGR_G_CreateFromWkb((unsigned char *) wkb, src_sr, &src_geom, wkb_len); - if (ogrerr != OGRERR_NONE) { - rterror("rt_raster_gdal_rasterize: Could not create OGR Geometry from WKB"); - - _rti_rasterize_arg_destroy(arg); - - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* OGR Geometry is empty */ - if (OGR_G_IsEmpty(src_geom)) { - rtinfo("Geometry provided is empty. Returning empty raster"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return rt_raster_new(0, 0); - } - - /* get envelope */ - OGR_G_GetEnvelope(src_geom, &src_env); - rt_util_from_ogr_envelope(src_env, &extent); - - RASTER_DEBUGF(3, "Suggested raster envelope: %f, %f, %f, %f", - extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); - - /* user-defined scale */ - if ( - (NULL != scale_x) && - (NULL != scale_y) && - (FLT_NEQ(*scale_x, 0.0)) && - (FLT_NEQ(*scale_y, 0.0)) - ) { - /* for now, force scale to be in left-right, top-down orientation */ - _scale[0] = fabs(*scale_x); - _scale[1] = fabs(*scale_y); - } - /* user-defined width/height */ - else if ( - (NULL != width) && - (NULL != height) && - (FLT_NEQ(*width, 0.0)) && - (FLT_NEQ(*height, 0.0)) - ) { - _dim[0] = fabs(*width); - _dim[1] = fabs(*height); - - if (FLT_NEQ(extent.MaxX, extent.MinX)) - _scale[0] = fabs((extent.MaxX - extent.MinX) / _dim[0]); - else - _scale[0] = 1.; - - if (FLT_NEQ(extent.MaxY, extent.MinY)) - _scale[1] = fabs((extent.MaxY - extent.MinY) / _dim[1]); - else - _scale[1] = 1.; - } - else { - rterror("rt_raster_gdal_rasterize: Values must be provided for width and height or X and Y of scale"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - RASTER_DEBUGF(3, "scale (x, y) = %f, %f", _scale[0], -1 * _scale[1]); - RASTER_DEBUGF(3, "dim (x, y) = %d, %d", _dim[0], _dim[1]); - - /* user-defined skew */ - if (NULL != skew_x) { - _skew[0] = *skew_x; - - /* - negative scale-x affects skew - for now, force skew to be in left-right, top-down orientation - */ - if ( - NULL != scale_x && - *scale_x < 0. - ) { - _skew[0] *= -1; - } - } - if (NULL != skew_y) { - _skew[1] = *skew_y; - - /* - positive scale-y affects skew - for now, force skew to be in left-right, top-down orientation - */ - if ( - NULL != scale_y && - *scale_y > 0. - ) { - _skew[1] *= -1; - } - } - - /* - if geometry is a point, a linestring or set of either and bounds not set, - increase extent by a pixel to avoid missing points on border - - a whole pixel is used instead of half-pixel due to backward - compatibility with GDAL 1.6, 1.7 and 1.8. 1.9+ works fine with half-pixel. - */ - wkbtype = wkbFlatten(OGR_G_GetGeometryType(src_geom)); - if (( - (wkbtype == wkbPoint) || - (wkbtype == wkbMultiPoint) || - (wkbtype == wkbLineString) || - (wkbtype == wkbMultiLineString) - ) && - FLT_EQ(_dim[0], 0) && - FLT_EQ(_dim[1], 0) - ) { - int result; - LWPOLY *epoly = NULL; - LWGEOM *lwgeom = NULL; - GEOSGeometry *egeom = NULL; - GEOSGeometry *geom = NULL; - - RASTER_DEBUG(3, "Testing geometry is properly contained by extent"); - - /* - see if geometry is properly contained by extent - all parts of geometry lies within extent - */ - - /* initialize GEOS */ - initGEOS(lwnotice, lwgeom_geos_error); - - /* convert envelope to geometry */ - RASTER_DEBUG(4, "Converting envelope to geometry"); - epoly = rt_util_envelope_to_lwpoly(extent); - if (epoly == NULL) { - rterror("rt_raster_gdal_rasterize: Could not create envelope's geometry to test if geometry is properly contained by extent"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - egeom = (GEOSGeometry *) LWGEOM2GEOS(lwpoly_as_lwgeom(epoly)); - lwpoly_free(epoly); - - /* convert WKB to geometry */ - RASTER_DEBUG(4, "Converting WKB to geometry"); - lwgeom = lwgeom_from_wkb(wkb, wkb_len, LW_PARSER_CHECK_NONE); - geom = (GEOSGeometry *) LWGEOM2GEOS(lwgeom); - lwgeom_free(lwgeom); - - result = GEOSRelatePattern(egeom, geom, "T**FF*FF*"); - GEOSGeom_destroy(geom); - GEOSGeom_destroy(egeom); - - if (result == 2) { - rterror("rt_raster_gdal_rasterize: Could not test if geometry is properly contained by extent for geometry within extent"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* geometry NOT properly contained by extent */ - if (!result) { - -#if POSTGIS_GDAL_VERSION > 18 - - /* check alignment flag: grid_xw */ - if ( - (NULL == ul_xw && NULL == ul_yw) && - (NULL != grid_xw && NULL != grid_xw) && - FLT_NEQ(*grid_xw, extent.MinX) - ) { - // do nothing - RASTER_DEBUG(3, "Skipping extent adjustment on X-axis due to upcoming alignment"); - } - else { - RASTER_DEBUG(3, "Adjusting extent for GDAL > 1.8 by half the scale on X-axis"); - extent.MinX -= (_scale[0] / 2.); - extent.MaxX += (_scale[0] / 2.); - } - - /* check alignment flag: grid_yw */ - if ( - (NULL == ul_xw && NULL == ul_yw) && - (NULL != grid_xw && NULL != grid_xw) && - FLT_NEQ(*grid_yw, extent.MaxY) - ) { - // do nothing - RASTER_DEBUG(3, "Skipping extent adjustment on Y-axis due to upcoming alignment"); - } - else { - RASTER_DEBUG(3, "Adjusting extent for GDAL > 1.8 by half the scale on Y-axis"); - extent.MinY -= (_scale[1] / 2.); - extent.MaxY += (_scale[1] / 2.); - } - -#else - - /* check alignment flag: grid_xw */ - if ( - (NULL == ul_xw && NULL == ul_yw) && - (NULL != grid_xw && NULL != grid_xw) && - FLT_NEQ(*grid_xw, extent.MinX) - ) { - // do nothing - RASTER_DEBUG(3, "Skipping extent adjustment on X-axis due to upcoming alignment"); - } - else { - RASTER_DEBUG(3, "Adjusting extent for GDAL <= 1.8 by the scale on X-axis"); - extent.MinX -= _scale[0]; - extent.MaxX += _scale[0]; - } - - - /* check alignment flag: grid_yw */ - if ( - (NULL == ul_xw && NULL == ul_yw) && - (NULL != grid_xw && NULL != grid_xw) && - FLT_NEQ(*grid_yw, extent.MaxY) - ) { - // do nothing - RASTER_DEBUG(3, "Skipping extent adjustment on Y-axis due to upcoming alignment"); - } - else { - RASTER_DEBUG(3, "Adjusting extent for GDAL <= 1.8 by the scale on Y-axis"); - extent.MinY -= _scale[1]; - extent.MaxY += _scale[1]; - } - -#endif - - } - - RASTER_DEBUGF(3, "Adjusted extent: %f, %f, %f, %f", - extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); - - extent.UpperLeftX = extent.MinX; - extent.UpperLeftY = extent.MaxY; - } - - /* reprocess extent if skewed */ - if ( - FLT_NEQ(_skew[0], 0) || - FLT_NEQ(_skew[1], 0) - ) { - rt_raster skewedrast; - - RASTER_DEBUG(3, "Computing skewed extent's envelope"); - - skewedrast = rt_raster_compute_skewed_raster( - extent, - _skew, - _scale, - 0.01 - ); - if (skewedrast == NULL) { - rterror("rt_raster_gdal_rasterize: Could not compute skewed raster"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - _dim[0] = skewedrast->width; - _dim[1] = skewedrast->height; - - extent.UpperLeftX = skewedrast->ipX; - extent.UpperLeftY = skewedrast->ipY; - - rt_raster_destroy(skewedrast); - } - - /* raster dimensions */ - if (!_dim[0]) - _dim[0] = (int) fmax((fabs(extent.MaxX - extent.MinX) + (_scale[0] / 2.)) / _scale[0], 1); - if (!_dim[1]) - _dim[1] = (int) fmax((fabs(extent.MaxY - extent.MinY) + (_scale[1] / 2.)) / _scale[1], 1); - - /* temporary raster */ - rast = rt_raster_new(_dim[0], _dim[1]); - if (rast == NULL) { - rterror("rt_raster_gdal_rasterize: Out of memory allocating temporary raster"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* set raster's spatial attributes */ - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - rt_raster_set_scale(rast, _scale[0], -1 * _scale[1]); - rt_raster_set_skews(rast, _skew[0], _skew[1]); - - rt_raster_get_geotransform_matrix(rast, _gt); - RASTER_DEBUGF(3, "Temp raster's geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); - RASTER_DEBUGF(3, "Temp raster's dimensions (width x height): %d x %d", - _dim[0], _dim[1]); - - /* user-specified upper-left corner */ - if ( - NULL != ul_xw && - NULL != ul_yw - ) { - ul_user = 1; - - RASTER_DEBUGF(4, "Using user-specified upper-left corner: %f, %f", *ul_xw, *ul_yw); - - /* set upper-left corner */ - rt_raster_set_offsets(rast, *ul_xw, *ul_yw); - extent.UpperLeftX = *ul_xw; - extent.UpperLeftY = *ul_yw; - } - else if ( - ((NULL != ul_xw) && (NULL == ul_yw)) || - ((NULL == ul_xw) && (NULL != ul_yw)) - ) { - rterror("rt_raster_gdal_rasterize: Both X and Y upper-left corner values must be provided"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* alignment only considered if upper-left corner not provided */ - if ( - !ul_user && ( - (NULL != grid_xw) || (NULL != grid_yw) - ) - ) { - - if ( - ((NULL != grid_xw) && (NULL == grid_yw)) || - ((NULL == grid_xw) && (NULL != grid_yw)) - ) { - rterror("rt_raster_gdal_rasterize: Both X and Y alignment values must be provided"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - RASTER_DEBUGF(4, "Aligning extent to user-specified grid: %f, %f", *grid_xw, *grid_yw); - - do { - double _r[2] = {0}; - double _w[2] = {0}; - - /* raster is already aligned */ - if (FLT_EQ(*grid_xw, extent.UpperLeftX) && FLT_EQ(*grid_yw, extent.UpperLeftY)) { - RASTER_DEBUG(3, "Skipping raster alignment as it is already aligned to grid"); - break; - } - - extent.UpperLeftX = rast->ipX; - extent.UpperLeftY = rast->ipY; - rt_raster_set_offsets(rast, *grid_xw, *grid_yw); - - /* process upper-left corner */ - if (rt_raster_geopoint_to_cell( - rast, - extent.UpperLeftX, extent.UpperLeftY, - &(_r[0]), &(_r[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute raster pixel for spatial coordinates"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - if (rt_raster_cell_to_geopoint( - rast, - _r[0], _r[1], - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* shift occurred */ - if (FLT_NEQ(_w[0], extent.UpperLeftX)) { - if (NULL == width) - rast->width++; - else if (NULL == scale_x) { - double _c[2] = {0}; - - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - - /* get upper-right corner */ - if (rt_raster_cell_to_geopoint( - rast, - rast->width, 0, - &(_c[0]), &(_c[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - rast->scaleX = fabs((_c[0] - _w[0]) / ((double) rast->width)); - } - } - if (FLT_NEQ(_w[1], extent.UpperLeftY)) { - if (NULL == height) - rast->height++; - else if (NULL == scale_y) { - double _c[2] = {0}; - - rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); - - /* get upper-right corner */ - if (rt_raster_cell_to_geopoint( - rast, - 0, rast->height, - &(_c[0]), &(_c[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - rast->scaleY = -1 * fabs((_c[1] - _w[1]) / ((double) rast->height)); - } - } - - rt_raster_set_offsets(rast, _w[0], _w[1]); - } - while (0); - } - - /* - after this point, rt_envelope extent is no longer used - */ - - /* get key attributes from rast */ - _dim[0] = rast->width; - _dim[1] = rast->height; - rt_raster_get_geotransform_matrix(rast, _gt); - - /* scale-x is negative or scale-y is positive */ - if (( - (NULL != scale_x) && (*scale_x < 0.) - ) || ( - (NULL != scale_y) && (*scale_y > 0) - )) { - double _w[2] = {0}; - - /* negative scale-x */ - if ( - (NULL != scale_x) && - (*scale_x < 0.) - ) { - RASTER_DEBUG(3, "Processing negative scale-x"); - - if (rt_raster_cell_to_geopoint( - rast, - _dim[0], 0, - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - _gt[0] = _w[0]; - _gt[1] = *scale_x; - - /* check for skew */ - if (NULL != skew_x && FLT_NEQ(*skew_x, 0)) - _gt[2] = *skew_x; - } - /* positive scale-y */ - if ( - (NULL != scale_y) && - (*scale_y > 0) - ) { - RASTER_DEBUG(3, "Processing positive scale-y"); - - if (rt_raster_cell_to_geopoint( - rast, - 0, _dim[1], - &(_w[0]), &(_w[1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); - - rt_raster_destroy(rast); - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - _gt[3] = _w[1]; - _gt[5] = *scale_y; - - /* check for skew */ - if (NULL != skew_y && FLT_NEQ(*skew_y, 0)) - _gt[4] = *skew_y; - } - } - - rt_raster_destroy(rast); - rast = NULL; - - RASTER_DEBUGF(3, "Applied geotransform: %f, %f, %f, %f, %f, %f", - _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); - RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", - _dim[0], _dim[1]); - - /* load GDAL mem */ - if (!rt_util_gdal_driver_registered("MEM")) - GDALRegister_MEM(); - _drv = GDALGetDriverByName("MEM"); - if (NULL == _drv) { - rterror("rt_raster_gdal_rasterize: Could not load the MEM GDAL driver"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - _ds = GDALCreate(_drv, "", _dim[0], _dim[1], 0, GDT_Byte, NULL); - if (NULL == _ds) { - rterror("rt_raster_gdal_rasterize: Could not create a GDALDataset to rasterize the geometry into"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - return NULL; - } - - /* set geotransform */ - cplerr = GDALSetGeoTransform(_ds, _gt); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not set geotransform on GDALDataset"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - GDALClose(_ds); - - return NULL; - } - - /* set SRS */ - if (NULL != src_sr) { - char *_srs = NULL; - OSRExportToWkt(src_sr, &_srs); - - cplerr = GDALSetProjection(_ds, _srs); - CPLFree(_srs); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not set projection on GDALDataset"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - GDALClose(_ds); - - return NULL; - } - } - - /* set bands */ - for (i = 0; i < arg->numbands; i++) { - err = 0; - - do { - /* add band */ - cplerr = GDALAddBand(_ds, rt_util_pixtype_to_gdal_datatype(arg->pixtype[i]), NULL); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not add band to GDALDataset"); - err = 1; - break; - } - - _band = GDALGetRasterBand(_ds, i + 1); - if (NULL == _band) { - rterror("rt_raster_gdal_rasterize: Could not get band %d from GDALDataset", i + 1); - err = 1; - break; - } - - /* nodata value */ - if (arg->hasnodata[i]) { - RASTER_DEBUGF(4, "Setting NODATA value of band %d to %f", i, arg->nodata[i]); - cplerr = GDALSetRasterNoDataValue(_band, arg->nodata[i]); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not set nodata value"); - err = 1; - break; - } - RASTER_DEBUGF(4, "NODATA value set to %f", GDALGetRasterNoDataValue(_band, NULL)); - } - - /* initial value */ - cplerr = GDALFillRaster(_band, arg->init[i], 0); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not set initial value"); - err = 1; - break; - } - } - while (0); - - if (err) { - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - GDALClose(_ds); - - return NULL; - } - } - - arg->bandlist = (int *) rtalloc(sizeof(int) * arg->numbands); - for (i = 0; i < arg->numbands; i++) arg->bandlist[i] = i + 1; - - - /* burn geometry */ - cplerr = GDALRasterizeGeometries( - _ds, - arg->numbands, arg->bandlist, - 1, &src_geom, - NULL, NULL, - arg->value, - options, - NULL, NULL - ); - if (cplerr != CE_None) { - rterror("rt_raster_gdal_rasterize: Could not rasterize geometry"); - - _rti_rasterize_arg_destroy(arg); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - GDALClose(_ds); - - return NULL; - } - - /* convert gdal dataset to raster */ - GDALFlushCache(_ds); - RASTER_DEBUG(3, "Converting GDAL dataset to raster"); - rast = rt_raster_from_gdal_dataset(_ds); - - OGR_G_DestroyGeometry(src_geom); - if (src_sr != NULL) OSRDestroySpatialReference(src_sr); - /* OGRCleanupAll(); */ - - GDALClose(_ds); - - if (NULL == rast) { - rterror("rt_raster_gdal_rasterize: Could not rasterize geometry"); - return NULL; - } - - /* width, height */ - _width = rt_raster_get_width(rast); - _height = rt_raster_get_height(rast); - - /* check each band for pixtype */ - for (i = 0; i < arg->numbands; i++) { - uint8_t *data = NULL; - rt_band band = NULL; - rt_band oldband = NULL; - - double val = 0; - int nodata = 0; - int hasnodata = 0; - double nodataval = 0; - int x = 0; - int y = 0; - - oldband = rt_raster_get_band(rast, i); - if (oldband == NULL) { - rterror("rt_raster_gdal_rasterize: Could not get band %d of output raster", i); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - return NULL; - } - - /* band is of user-specified type */ - if (rt_band_get_pixtype(oldband) == arg->pixtype[i]) - continue; - - /* hasnodata, nodataval */ - hasnodata = rt_band_get_hasnodata_flag(oldband); - if (hasnodata) - rt_band_get_nodata(oldband, &nodataval); - - /* allocate data */ - data = rtalloc(rt_pixtype_size(arg->pixtype[i]) * _width * _height); - if (data == NULL) { - rterror("rt_raster_gdal_rasterize: Could not allocate memory for band data"); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - return NULL; - } - memset(data, 0, rt_pixtype_size(arg->pixtype[i]) * _width * _height); - - /* create new band of correct type */ - band = rt_band_new_inline( - _width, _height, - arg->pixtype[i], - hasnodata, nodataval, - data - ); - if (band == NULL) { - rterror("rt_raster_gdal_rasterize: Could not create band"); - rtdealloc(data); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - return NULL; - } - - /* give ownership of data to band */ - rt_band_set_ownsdata_flag(band, 1); - - /* copy pixel by pixel */ - for (x = 0; x < _width; x++) { - for (y = 0; y < _height; y++) { - err = rt_band_get_pixel(oldband, x, y, &val, &nodata); - if (err != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not get pixel value"); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - rt_band_destroy(band); - return NULL; - } - - if (nodata) - val = nodataval; - - err = rt_band_set_pixel(band, x, y, val, NULL); - if (err != ES_NONE) { - rterror("rt_raster_gdal_rasterize: Could not set pixel value"); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - rt_band_destroy(band); - return NULL; - } - } - } - - /* replace band */ - oldband = rt_raster_replace_band(rast, band, i); - if (oldband == NULL) { - rterror("rt_raster_gdal_rasterize: Could not replace band %d of output raster", i); - _rti_rasterize_arg_destroy(arg); - rt_raster_destroy(rast); - rt_band_destroy(band); - return NULL; - } - - /* free oldband */ - rt_band_destroy(oldband); - } - - _rti_rasterize_arg_destroy(arg); - - RASTER_DEBUG(3, "done"); - - return rast; -} - -/****************************************************************************** -* rt_raster_intersects() -******************************************************************************/ - -static -int rt_raster_intersects_algorithm( - rt_raster rast1, rt_raster rast2, - rt_band band1, rt_band band2, - int hasnodata1, int hasnodata2, - double nodata1, double nodata2 -) { - int i; - int byHeight = 1; - uint32_t dimValue; - - uint32_t row; - uint32_t rowoffset; - uint32_t col; - uint32_t coloffset; - - enum line_points {X1, Y1, X2, Y2}; - enum point {pX, pY}; - double line1[4] = {0.}; - double line2[4] = {0.}; - double P[2] = {0.}; - double Qw[2] = {0.}; - double Qr[2] = {0.}; - double gt1[6] = {0.}; - double gt2[6] = {0.}; - double igt1[6] = {0}; - double igt2[6] = {0}; - double d; - double val1; - int noval1; - int isnodata1; - double val2; - int noval2; - int isnodata2; - uint32_t adjacent[8] = {0}; - - double xscale; - double yscale; - - uint16_t width1; - uint16_t height1; - uint16_t width2; - uint16_t height2; - - width1 = rt_raster_get_width(rast1); - height1 = rt_raster_get_height(rast1); - width2 = rt_raster_get_width(rast2); - height2 = rt_raster_get_height(rast2); - - /* sampling scale */ - xscale = fmin(rt_raster_get_x_scale(rast1), rt_raster_get_x_scale(rast2)) / 10.; - yscale = fmin(rt_raster_get_y_scale(rast1), rt_raster_get_y_scale(rast2)) / 10.; - - /* see if skew made rast2's rows are parallel to rast1's cols */ - rt_raster_cell_to_geopoint( - rast1, - 0, 0, - &(line1[X1]), &(line1[Y1]), - gt1 - ); - - rt_raster_cell_to_geopoint( - rast1, - 0, height1, - &(line1[X2]), &(line1[Y2]), - gt1 - ); - - rt_raster_cell_to_geopoint( - rast2, - 0, 0, - &(line2[X1]), &(line2[Y1]), - gt2 - ); - - rt_raster_cell_to_geopoint( - rast2, - width2, 0, - &(line2[X2]), &(line2[Y2]), - gt2 - ); - - /* parallel vertically */ - if (FLT_EQ(line1[X2] - line1[X1], 0.) && FLT_EQ(line2[X2] - line2[X1], 0.)) - byHeight = 0; - /* parallel */ - else if (FLT_EQ(((line1[Y2] - line1[Y1]) / (line1[X2] - line1[X1])), ((line2[Y2] - line2[Y1]) / (line2[X2] - line2[X1])))) - byHeight = 0; - - if (byHeight) - dimValue = height2; - else - dimValue = width2; - RASTER_DEBUGF(4, "byHeight: %d, dimValue: %d", byHeight, dimValue); - - /* 3 x 3 search */ - for (coloffset = 0; coloffset < 3; coloffset++) { - for (rowoffset = 0; rowoffset < 3; rowoffset++) { - /* smaller raster */ - for (col = coloffset; col <= width1; col += 3) { - - rt_raster_cell_to_geopoint( - rast1, - col, 0, - &(line1[X1]), &(line1[Y1]), - gt1 - ); - - rt_raster_cell_to_geopoint( - rast1, - col, height1, - &(line1[X2]), &(line1[Y2]), - gt1 - ); - - /* larger raster */ - for (row = rowoffset; row <= dimValue; row += 3) { - - if (byHeight) { - rt_raster_cell_to_geopoint( - rast2, - 0, row, - &(line2[X1]), &(line2[Y1]), - gt2 - ); - - rt_raster_cell_to_geopoint( - rast2, - width2, row, - &(line2[X2]), &(line2[Y2]), - gt2 - ); - } - else { - rt_raster_cell_to_geopoint( - rast2, - row, 0, - &(line2[X1]), &(line2[Y1]), - gt2 - ); - - rt_raster_cell_to_geopoint( - rast2, - row, height2, - &(line2[X2]), &(line2[Y2]), - gt2 - ); - } - - RASTER_DEBUGF(4, "(col, row) = (%d, %d)", col, row); - RASTER_DEBUGF(4, "line1(x1, y1, x2, y2) = (%f, %f, %f, %f)", - line1[X1], line1[Y1], line1[X2], line1[Y2]); - RASTER_DEBUGF(4, "line2(x1, y1, x2, y2) = (%f, %f, %f, %f)", - line2[X1], line2[Y1], line2[X2], line2[Y2]); - - /* intersection */ - /* http://en.wikipedia.org/wiki/Line-line_intersection */ - d = ((line1[X1] - line1[X2]) * (line2[Y1] - line2[Y2])) - ((line1[Y1] - line1[Y2]) * (line2[X1] - line2[X2])); - /* no intersection */ - if (FLT_EQ(d, 0.)) { - continue; - } - - P[pX] = (((line1[X1] * line1[Y2]) - (line1[Y1] * line1[X2])) * (line2[X1] - line2[X2])) - - ((line1[X1] - line1[X2]) * ((line2[X1] * line2[Y2]) - (line2[Y1] * line2[X2]))); - P[pX] = P[pX] / d; - - P[pY] = (((line1[X1] * line1[Y2]) - (line1[Y1] * line1[X2])) * (line2[Y1] - line2[Y2])) - - ((line1[Y1] - line1[Y2]) * ((line2[X1] * line2[Y2]) - (line2[Y1] * line2[X2]))); - P[pY] = P[pY] / d; - - RASTER_DEBUGF(4, "P(x, y) = (%f, %f)", P[pX], P[pY]); - - /* intersection within bounds */ - if (( - (FLT_EQ(P[pX], line1[X1]) || FLT_EQ(P[pX], line1[X2])) || - (P[pX] > fmin(line1[X1], line1[X2]) && (P[pX] < fmax(line1[X1], line1[X2]))) - ) && ( - (FLT_EQ(P[pY], line1[Y1]) || FLT_EQ(P[pY], line1[Y2])) || - (P[pY] > fmin(line1[Y1], line1[Y2]) && (P[pY] < fmax(line1[Y1], line1[Y2]))) - ) && ( - (FLT_EQ(P[pX], line2[X1]) || FLT_EQ(P[pX], line2[X2])) || - (P[pX] > fmin(line2[X1], line2[X2]) && (P[pX] < fmax(line2[X1], line2[X2]))) - ) && ( - (FLT_EQ(P[pY], line2[Y1]) || FLT_EQ(P[pY], line2[Y2])) || - (P[pY] > fmin(line2[Y1], line2[Y2]) && (P[pY] < fmax(line2[Y1], line2[Y2]))) - )) { - RASTER_DEBUG(4, "within bounds"); - - for (i = 0; i < 8; i++) adjacent[i] = 0; - - /* test points around intersection */ - for (i = 0; i < 8; i++) { - switch (i) { - case 7: - Qw[pX] = P[pX] - xscale; - Qw[pY] = P[pY] + yscale; - break; - /* 270 degrees = 09:00 */ - case 6: - Qw[pX] = P[pX] - xscale; - Qw[pY] = P[pY]; - break; - case 5: - Qw[pX] = P[pX] - xscale; - Qw[pY] = P[pY] - yscale; - break; - /* 180 degrees = 06:00 */ - case 4: - Qw[pX] = P[pX]; - Qw[pY] = P[pY] - yscale; - break; - case 3: - Qw[pX] = P[pX] + xscale; - Qw[pY] = P[pY] - yscale; - break; - /* 90 degrees = 03:00 */ - case 2: - Qw[pX] = P[pX] + xscale; - Qw[pY] = P[pY]; - break; - /* 45 degrees */ - case 1: - Qw[pX] = P[pX] + xscale; - Qw[pY] = P[pY] + yscale; - break; - /* 0 degrees = 00:00 */ - case 0: - Qw[pX] = P[pX]; - Qw[pY] = P[pY] + yscale; - break; - } - - /* unable to convert point to cell */ - noval1 = 0; - if (rt_raster_geopoint_to_cell( - rast1, - Qw[pX], Qw[pY], - &(Qr[pX]), &(Qr[pY]), - igt1 - ) != ES_NONE) { - noval1 = 1; - } - /* cell is outside bounds of grid */ - else if ( - (Qr[pX] < 0 || Qr[pX] > width1 || FLT_EQ(Qr[pX], width1)) || - (Qr[pY] < 0 || Qr[pY] > height1 || FLT_EQ(Qr[pY], height1)) - ) { - noval1 = 1; - } - else if (hasnodata1 == FALSE) - val1 = 1; - /* unable to get value at cell */ - else if (rt_band_get_pixel(band1, Qr[pX], Qr[pY], &val1, &isnodata1) != ES_NONE) - noval1 = 1; - - /* unable to convert point to cell */ - noval2 = 0; - if (rt_raster_geopoint_to_cell( - rast2, - Qw[pX], Qw[pY], - &(Qr[pX]), &(Qr[pY]), - igt2 - ) != ES_NONE) { - noval2 = 1; - } - /* cell is outside bounds of grid */ - else if ( - (Qr[pX] < 0 || Qr[pX] > width2 || FLT_EQ(Qr[pX], width2)) || - (Qr[pY] < 0 || Qr[pY] > height2 || FLT_EQ(Qr[pY], height2)) - ) { - noval2 = 1; - } - else if (hasnodata2 == FALSE) - val2 = 1; - /* unable to get value at cell */ - else if (rt_band_get_pixel(band2, Qr[pX], Qr[pY], &val2, &isnodata2) != ES_NONE) - noval2 = 1; - - if (!noval1) { - RASTER_DEBUGF(4, "val1 = %f", val1); - } - if (!noval2) { - RASTER_DEBUGF(4, "val2 = %f", val2); - } - - /* pixels touch */ - if (!noval1 && ( - (hasnodata1 == FALSE) || !isnodata1 - )) { - adjacent[i]++; - } - if (!noval2 && ( - (hasnodata2 == FALSE) || !isnodata2 - )) { - adjacent[i] += 3; - } - - /* two pixel values not present */ - if (noval1 || noval2) { - RASTER_DEBUGF(4, "noval1 = %d, noval2 = %d", noval1, noval2); - continue; - } - - /* pixels valid, so intersect */ - if ( - ((hasnodata1 == FALSE) || !isnodata1) && - ((hasnodata2 == FALSE) || !isnodata2) - ) { - RASTER_DEBUG(3, "The two rasters do intersect"); - - return 1; - } - } - - /* pixels touch */ - for (i = 0; i < 4; i++) { - RASTER_DEBUGF(4, "adjacent[%d] = %d, adjacent[%d] = %d" - , i, adjacent[i], i + 4, adjacent[i + 4]); - if (adjacent[i] == 0) continue; - - if (adjacent[i] + adjacent[i + 4] == 4) { - RASTER_DEBUG(3, "The two rasters touch"); - - return 1; - } - } - } - else { - RASTER_DEBUG(4, "outside of bounds"); - } - } - } - } - } - - return 0; -} - -/** - * Return zero if error occurred in function. - * Parameter intersects returns non-zero if two rasters intersect - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param intersects : non-zero value if the two rasters' bands intersects - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_intersects( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *intersects -) { - int i; - int j; - int within = 0; - - LWGEOM *hull[2] = {NULL}; - GEOSGeometry *ghull[2] = {NULL}; - - uint16_t width1; - uint16_t height1; - uint16_t width2; - uint16_t height2; - double area1; - double area2; - double pixarea1; - double pixarea2; - rt_raster rastS = NULL; - rt_raster rastL = NULL; - uint16_t *widthS = NULL; - uint16_t *heightS = NULL; - uint16_t *widthL = NULL; - uint16_t *heightL = NULL; - int nbandS; - int nbandL; - rt_band bandS = NULL; - rt_band bandL = NULL; - int hasnodataS = FALSE; - int hasnodataL = FALSE; - double nodataS = 0; - double nodataL = 0; - int isnodataS = 0; - int isnodataL = 0; - double gtS[6] = {0}; - double igtL[6] = {0}; - - uint32_t row; - uint32_t rowoffset; - uint32_t col; - uint32_t coloffset; - - enum line_points {X1, Y1, X2, Y2}; - enum point {pX, pY}; - double lineS[4]; - double Qr[2]; - double valS; - double valL; - - RASTER_DEBUG(3, "Starting"); - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != intersects); - - if (nband1 < 0 && nband2 < 0) { - nband1 = -1; - nband2 = -1; - } - else { - assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); - assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); - } - - /* same srid */ - if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { - rterror("rt_raster_intersects: The two rasters provided have different SRIDs"); - *intersects = 0; - return ES_ERROR; - } - - /* raster extents need to intersect */ - do { - int rtn; - - initGEOS(lwnotice, lwgeom_geos_error); - - rtn = 1; - for (i = 0; i < 2; i++) { - if ((rt_raster_get_convex_hull(i < 1 ? rast1 : rast2, &(hull[i])) != ES_NONE) || NULL == hull[i]) { - for (j = 0; j < i; j++) { - GEOSGeom_destroy(ghull[j]); - lwgeom_free(hull[j]); - } - rtn = 0; - break; - } - ghull[i] = (GEOSGeometry *) LWGEOM2GEOS(hull[i]); - if (NULL == ghull[i]) { - for (j = 0; j < i; j++) { - GEOSGeom_destroy(ghull[j]); - lwgeom_free(hull[j]); - } - lwgeom_free(hull[i]); - rtn = 0; - break; - } - } - if (!rtn) break; - - /* test to see if raster within the other */ - within = 0; - if (GEOSWithin(ghull[0], ghull[1]) == 1) - within = -1; - else if (GEOSWithin(ghull[1], ghull[0]) == 1) - within = 1; - - if (within != 0) - rtn = 1; - else - rtn = GEOSIntersects(ghull[0], ghull[1]); - - for (i = 0; i < 2; i++) { - GEOSGeom_destroy(ghull[i]); - lwgeom_free(hull[i]); - } - - if (rtn != 2) { - RASTER_DEBUGF(4, "convex hulls of rasters do %sintersect", rtn != 1 ? "NOT " : ""); - if (rtn != 1) { - *intersects = 0; - return ES_NONE; - } - /* band isn't specified */ - else if (nband1 < 0) { - *intersects = 1; - return ES_NONE; - } - } - else { - RASTER_DEBUG(4, "GEOSIntersects() returned a 2!!!!"); - } - } - while (0); - - /* smaller raster by area or width */ - width1 = rt_raster_get_width(rast1); - height1 = rt_raster_get_height(rast1); - width2 = rt_raster_get_width(rast2); - height2 = rt_raster_get_height(rast2); - pixarea1 = fabs(rt_raster_get_x_scale(rast1) * rt_raster_get_y_scale(rast1)); - pixarea2 = fabs(rt_raster_get_x_scale(rast2) * rt_raster_get_y_scale(rast2)); - area1 = fabs(width1 * height1 * pixarea1); - area2 = fabs(width2 * height2 * pixarea2); - RASTER_DEBUGF(4, "pixarea1, pixarea2, area1, area2 = %f, %f, %f, %f", - pixarea1, pixarea2, area1, area2); - if ( - (within <= 0) || - (area1 < area2) || - FLT_EQ(area1, area2) || - (area1 < pixarea2) || /* area of rast1 smaller than pixel area of rast2 */ - FLT_EQ(area1, pixarea2) - ) { - rastS = rast1; - nbandS = nband1; - widthS = &width1; - heightS = &height1; - - rastL = rast2; - nbandL = nband2; - widthL = &width2; - heightL = &height2; - } - else { - rastS = rast2; - nbandS = nband2; - widthS = &width2; - heightS = &height2; - - rastL = rast1; - nbandL = nband1; - widthL = &width1; - heightL = &height1; - } - - /* no band to use, set band to zero */ - if (nband1 < 0) { - nbandS = 0; - nbandL = 0; - } - - RASTER_DEBUGF(4, "rast1 @ %p", rast1); - RASTER_DEBUGF(4, "rast2 @ %p", rast2); - RASTER_DEBUGF(4, "rastS @ %p", rastS); - RASTER_DEBUGF(4, "rastL @ %p", rastL); - - /* load band of smaller raster */ - bandS = rt_raster_get_band(rastS, nbandS); - if (NULL == bandS) { - rterror("rt_raster_intersects: Could not get band %d of the first raster", nbandS); - *intersects = 0; - return ES_ERROR; - } - - hasnodataS = rt_band_get_hasnodata_flag(bandS); - if (hasnodataS != FALSE) - rt_band_get_nodata(bandS, &nodataS); - - /* load band of larger raster */ - bandL = rt_raster_get_band(rastL, nbandL); - if (NULL == bandL) { - rterror("rt_raster_intersects: Could not get band %d of the first raster", nbandL); - *intersects = 0; - return ES_ERROR; - } - - hasnodataL = rt_band_get_hasnodata_flag(bandL); - if (hasnodataL != FALSE) - rt_band_get_nodata(bandL, &nodataL); - - /* no band to use, ignore nodata */ - if (nband1 < 0) { - hasnodataS = FALSE; - hasnodataL = FALSE; - } - - /* hasnodata(S|L) = TRUE and one of the two bands is isnodata */ - if ( - (hasnodataS && rt_band_get_isnodata_flag(bandS)) || - (hasnodataL && rt_band_get_isnodata_flag(bandL)) - ) { - RASTER_DEBUG(3, "One of the two raster bands is NODATA. The two rasters do not intersect"); - *intersects = 0; - return ES_NONE; - } - - /* special case where a raster can fit inside another raster's pixel */ - if (within != 0 && ((pixarea1 > area2) || (pixarea2 > area1))) { - RASTER_DEBUG(4, "Using special case of raster fitting into another raster's pixel"); - /* 3 x 3 search */ - for (coloffset = 0; coloffset < 3; coloffset++) { - for (rowoffset = 0; rowoffset < 3; rowoffset++) { - for (col = coloffset; col < *widthS; col += 3) { - for (row = rowoffset; row < *heightS; row += 3) { - if (hasnodataS == FALSE) - valS = 1; - else if (rt_band_get_pixel(bandS, col, row, &valS, &isnodataS) != ES_NONE) - continue; - - if ((hasnodataS == FALSE) || !isnodataS) { - rt_raster_cell_to_geopoint( - rastS, - col, row, - &(lineS[X1]), &(lineS[Y1]), - gtS - ); - - if (rt_raster_geopoint_to_cell( - rastL, - lineS[X1], lineS[Y1], - &(Qr[pX]), &(Qr[pY]), - igtL - ) != ES_NONE) { - continue; - } - - if ( - (Qr[pX] < 0 || Qr[pX] > *widthL || FLT_EQ(Qr[pX], *widthL)) || - (Qr[pY] < 0 || Qr[pY] > *heightL || FLT_EQ(Qr[pY], *heightL)) - ) { - continue; - } - - if (hasnodataS == FALSE) - valL = 1; - else if (rt_band_get_pixel(bandL, Qr[pX], Qr[pY], &valL, &isnodataL) != ES_NONE) - continue; - - if ((hasnodataL == FALSE) || !isnodataL) { - RASTER_DEBUG(3, "The two rasters do intersect"); - *intersects = 1; - return ES_NONE; - } - } - } - } - } - } - RASTER_DEBUG(4, "Smaller raster not in the other raster's pixel. Continuing"); - } - - RASTER_DEBUG(4, "Testing smaller raster vs larger raster"); - *intersects = rt_raster_intersects_algorithm( - rastS, rastL, - bandS, bandL, - hasnodataS, hasnodataL, - nodataS, nodataL - ); - - if (*intersects) return ES_NONE; - - RASTER_DEBUG(4, "Testing larger raster vs smaller raster"); - *intersects = rt_raster_intersects_algorithm( - rastL, rastS, - bandL, bandS, - hasnodataL, hasnodataS, - nodataL, nodataS - ); - - if (*intersects) return ES_NONE; - - RASTER_DEBUG(3, "The two rasters do not intersect"); - - *intersects = 0; - return ES_NONE; -} - -/****************************************************************************** -* GEOS-based spatial relationship tests -******************************************************************************/ - -static -rt_errorstate rt_raster_geos_spatial_relationship( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - rt_geos_spatial_test testtype, - int *testresult -) { - LWMPOLY *surface1 = NULL; - LWMPOLY *surface2 = NULL; - GEOSGeometry *geom1 = NULL; - GEOSGeometry *geom2 = NULL; - int rtn = 0; - int flag = 0; - - RASTER_DEBUG(3, "Starting"); - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != testresult); - - if (nband1 < 0 && nband2 < 0) { - nband1 = -1; - nband2 = -1; - } - else { - assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); - assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); - } - - /* initialize to zero, false result of spatial relationship test */ - *testresult = 0; - - /* same srid */ - if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { - rterror("rt_raster_geos_spatial_relationship: The two rasters provided have different SRIDs"); - return ES_ERROR; - } - - initGEOS(lwnotice, lwgeom_geos_error); - - /* get LWMPOLY of each band */ - if (rt_raster_surface(rast1, nband1, &surface1) != ES_NONE) { - rterror("rt_raster_geos_spatial_relationship: Could not get surface of the specified band from the first raster"); - return ES_ERROR; - } - if (rt_raster_surface(rast2, nband2, &surface2) != ES_NONE) { - rterror("rt_raster_geos_spatial_relationship: Could not get surface of the specified band from the second raster"); - lwmpoly_free(surface1); - return ES_ERROR; - } - - /* either surface is NULL, spatial relationship test is false */ - if (surface1 == NULL || surface2 == NULL) { - if (surface1 != NULL) lwmpoly_free(surface1); - if (surface2 != NULL) lwmpoly_free(surface2); - return ES_NONE; - } - - /* convert LWMPOLY to GEOSGeometry */ - geom1 = LWGEOM2GEOS(lwmpoly_as_lwgeom(surface1)); - lwmpoly_free(surface1); - if (geom1 == NULL) { - rterror("rt_raster_geos_spatial_relationship: Could not convert surface of the specified band from the first raster to a GEOSGeometry"); - lwmpoly_free(surface2); - return ES_ERROR; - } - - geom2 = LWGEOM2GEOS(lwmpoly_as_lwgeom(surface2)); - lwmpoly_free(surface2); - if (geom2 == NULL) { - rterror("rt_raster_geos_spatial_relationship: Could not convert surface of the specified band from the second raster to a GEOSGeometry"); - return ES_ERROR; - } - - flag = 0; - switch (testtype) { - case GSR_OVERLAPS: - rtn = GEOSOverlaps(geom1, geom2); - break; - case GSR_TOUCHES: - rtn = GEOSTouches(geom1, geom2); - break; - case GSR_CONTAINS: - rtn = GEOSContains(geom1, geom2); - break; - case GSR_CONTAINSPROPERLY: - rtn = GEOSRelatePattern(geom1, geom2, "T**FF*FF*"); - break; - case GSR_COVERS: - rtn = GEOSRelatePattern(geom1, geom2, "******FF*"); - break; - case GSR_COVEREDBY: - rtn = GEOSRelatePattern(geom1, geom2, "**F**F***"); - break; - default: - rterror("rt_raster_geos_spatial_relationship: Unknown or unsupported GEOS spatial relationship test"); - flag = -1; - break; - } - GEOSGeom_destroy(geom1); - GEOSGeom_destroy(geom2); - - /* something happened in the spatial relationship test */ - if (rtn == 2) { - rterror("rt_raster_geos_spatial_relationship: Could not run the appropriate GEOS spatial relationship test"); - flag = ES_ERROR; - } - /* spatial relationship test ran fine */ - else if (flag >= 0) { - if (rtn != 0) - *testresult = 1; - flag = ES_NONE; - } - /* flag < 0 for when testtype is unknown */ - else - flag = ES_ERROR; - - return flag; -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter overlaps returns non-zero if two rasters overlap - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param overlaps : non-zero value if the two rasters' bands overlaps - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_overlaps( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *overlaps -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_OVERLAPS, - overlaps - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter touches returns non-zero if two rasters touch - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param touches : non-zero value if the two rasters' bands touch - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_touches( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *touches -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_TOUCHES, - touches - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter contains returns non-zero if rast1 contains rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param contains : non-zero value if rast1 contains rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_contains( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *contains -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_CONTAINS, - contains - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter contains returns non-zero if rast1 contains properly rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param contains : non-zero value if rast1 contains properly rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_contains_properly( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *contains -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_CONTAINSPROPERLY, - contains - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter covers returns non-zero if rast1 covers rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param covers : non-zero value if rast1 covers rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_covers( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *covers -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_COVERS, - covers - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter coveredby returns non-zero if rast1 is covered by rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param coveredby : non-zero value if rast1 is covered by rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_coveredby( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - int *coveredby -) { - RASTER_DEBUG(3, "Starting"); - - return rt_raster_geos_spatial_relationship( - rast1, nband1, - rast2, nband2, - GSR_COVEREDBY, - coveredby - ); -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter dwithin returns non-zero if rast1 is within the specified - * distance of rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param dwithin : non-zero value if rast1 is within the specified distance - * of rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_within_distance( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - double distance, - int *dwithin -) { - LWMPOLY *surface = NULL; - LWGEOM *surface1 = NULL; - LWGEOM *surface2 = NULL; - double mindist = 0; - - RASTER_DEBUG(3, "Starting"); - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != dwithin); - - if (nband1 < 0 && nband2 < 0) { - nband1 = -1; - nband2 = -1; - } - else { - assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); - assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); - } - - /* initialize to zero, false result */ - *dwithin = 0; - - /* same srid */ - if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { - rterror("rt_raster_distance_within: The two rasters provided have different SRIDs"); - return ES_ERROR; - } - - /* distance cannot be less than zero */ - if (distance < 0) { - rterror("rt_raster_distance_within: Distance cannot be less than zero"); - return ES_ERROR; - } - - /* get LWMPOLY of each band */ - if (rt_raster_surface(rast1, nband1, &surface) != ES_NONE) { - rterror("rt_raster_distance_within: Could not get surface of the specified band from the first raster"); - return ES_ERROR; - } - surface1 = lwmpoly_as_lwgeom(surface); - - if (rt_raster_surface(rast2, nband2, &surface) != ES_NONE) { - rterror("rt_raster_distance_within: Could not get surface of the specified band from the second raster"); - lwgeom_free(surface1); - return ES_ERROR; - } - surface2 = lwmpoly_as_lwgeom(surface); - - /* either surface is NULL, test is false */ - if (surface1 == NULL || surface2 == NULL) { - if (surface1 != NULL) lwgeom_free(surface1); - if (surface2 != NULL) lwgeom_free(surface2); - return ES_NONE; - } - - /* get the min distance between the two surfaces */ - mindist = lwgeom_mindistance2d_tolerance(surface1, surface2, distance); - - lwgeom_free(surface1); - lwgeom_free(surface2); - - /* if distance >= mindist, true */ - if (FLT_EQ(mindist, distance) || distance > mindist) - *dwithin = 1; - - RASTER_DEBUGF(3, "(mindist, distance) = (%f, %f, %d)", mindist, distance, *dwithin); - - return ES_NONE; -} - -/** - * Return ES_ERROR if error occurred in function. - * Parameter dfwithin returns non-zero if rast1 is fully within the specified - * distance of rast2 - * - * @param rast1 : the first raster whose band will be tested - * @param nband1 : the 0-based band of raster rast1 to use - * if value is less than zero, bands are ignored. - * if nband1 gte zero, nband2 must be gte zero - * @param rast2 : the second raster whose band will be tested - * @param nband2 : the 0-based band of raster rast2 to use - * if value is less than zero, bands are ignored - * if nband2 gte zero, nband1 must be gte zero - * @param dfwithin : non-zero value if rast1 is fully within the specified - * distance of rast2 - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_fully_within_distance( - rt_raster rast1, int nband1, - rt_raster rast2, int nband2, - double distance, - int *dfwithin -) { - LWMPOLY *surface = NULL; - LWGEOM *surface1 = NULL; - LWGEOM *surface2 = NULL; - double maxdist = 0; - - RASTER_DEBUG(3, "Starting"); - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != dfwithin); - - if (nband1 < 0 && nband2 < 0) { - nband1 = -1; - nband2 = -1; - } - else { - assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); - assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); - } - - /* initialize to zero, false result */ - *dfwithin = 0; - - /* same srid */ - if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { - rterror("rt_raster_fully_within_distance: The two rasters provided have different SRIDs"); - return ES_ERROR; - } - - /* distance cannot be less than zero */ - if (distance < 0) { - rterror("rt_raster_fully_within_distance: Distance cannot be less than zero"); - return ES_ERROR; - } - - /* get LWMPOLY of each band */ - if (rt_raster_surface(rast1, nband1, &surface) != ES_NONE) { - rterror("rt_raster_fully_within_distance: Could not get surface of the specified band from the first raster"); - return ES_ERROR; - } - surface1 = lwmpoly_as_lwgeom(surface); - - if (rt_raster_surface(rast2, nband2, &surface) != ES_NONE) { - rterror("rt_raster_fully_within_distance: Could not get surface of the specified band from the second raster"); - lwgeom_free(surface1); - return ES_ERROR; - } - surface2 = lwmpoly_as_lwgeom(surface); - - /* either surface is NULL, test is false */ - if (surface1 == NULL || surface2 == NULL) { - if (surface1 != NULL) lwgeom_free(surface1); - if (surface2 != NULL) lwgeom_free(surface2); - return ES_NONE; - } - - /* get the maximum distance between the two surfaces */ - maxdist = lwgeom_maxdistance2d_tolerance(surface1, surface2, distance); - - lwgeom_free(surface1); - lwgeom_free(surface2); - - /* if distance >= maxdist, true */ - if (FLT_EQ(maxdist, distance) || distance > maxdist) - *dfwithin = 1; - - RASTER_DEBUGF(3, "(maxdist, distance, dfwithin) = (%f, %f, %d)", maxdist, distance, *dfwithin); - - return ES_NONE; -} - -/* - * Return ES_ERROR if error occurred in function. - * Paramter aligned returns non-zero if two rasters are aligned - * - * @param rast1 : the first raster for alignment test - * @param rast2 : the second raster for alignment test - * @param *aligned : non-zero value if the two rasters are aligned - * @param *reason : reason why rasters are not aligned - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_same_alignment( - rt_raster rast1, - rt_raster rast2, - int *aligned, char **reason -) { - double xr; - double yr; - double xw; - double yw; - int err = 0; - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != aligned); - - err = 0; - /* same srid */ - if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { - if (reason != NULL) *reason = "The rasters have different SRIDs"; - err = 1; - } - /* scales must match */ - else if (FLT_NEQ(fabs(rast1->scaleX), fabs(rast2->scaleX))) { - if (reason != NULL) *reason = "The rasters have different scales on the X axis"; - err = 1; - } - else if (FLT_NEQ(fabs(rast1->scaleY), fabs(rast2->scaleY))) { - if (reason != NULL) *reason = "The rasters have different scales on the Y axis"; - err = 1; - } - /* skews must match */ - else if (FLT_NEQ(rast1->skewX, rast2->skewX)) { - if (reason != NULL) *reason = "The rasters have different skews on the X axis"; - err = 1; - } - else if (FLT_NEQ(rast1->skewY, rast2->skewY)) { - if (reason != NULL) *reason = "The rasters have different skews on the Y axis"; - err = 1; - } - - if (err) { - *aligned = 0; - return ES_NONE; - } - - /* raster coordinates in context of second raster of first raster's upper-left corner */ - if (rt_raster_geopoint_to_cell( - rast2, - rast1->ipX, rast1->ipY, - &xr, &yr, - NULL - ) != ES_NONE) { - rterror("rt_raster_same_alignment: Could not get raster coordinates of second raster from first raster's spatial coordinates"); - *aligned = 0; - return ES_ERROR; - } - - /* spatial coordinates of raster coordinates from above */ - if (rt_raster_cell_to_geopoint( - rast2, - xr, yr, - &xw, &yw, - NULL - ) != ES_NONE) { - rterror("rt_raster_same_alignment: Could not get spatial coordinates of second raster from raster coordinates"); - *aligned = 0; - return ES_ERROR; - } - - RASTER_DEBUGF(4, "rast1(ipX, ipxY) = (%f, %f)", rast1->ipX, rast1->ipY); - RASTER_DEBUGF(4, "rast2(xr, yr) = (%f, %f)", xr, yr); - RASTER_DEBUGF(4, "rast2(xw, yw) = (%f, %f)", xw, yw); - - /* spatial coordinates are identical to that of first raster's upper-left corner */ - if (FLT_EQ(xw, rast1->ipX) && FLT_EQ(yw, rast1->ipY)) { - if (reason != NULL) *reason = "The rasters are aligned"; - *aligned = 1; - return ES_NONE; - } - - /* no alignment */ - if (reason != NULL) *reason = "The rasters (pixel corner coordinates) are not aligned"; - - *aligned = 0; - return ES_NONE; -} - -/* - * Return raster of computed extent specified extenttype applied - * on two input rasters. The raster returned should be freed by - * the caller - * - * @param rast1 : the first raster - * @param rast2 : the second raster - * @param extenttype : type of extent for the output raster - * @param *rtnraster : raster of computed extent - * @param *offset : 4-element array indicating the X,Y offsets - * for each raster. 0,1 for rast1 X,Y. 2,3 for rast2 X,Y. - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate -rt_raster_from_two_rasters( - rt_raster rast1, rt_raster rast2, - rt_extenttype extenttype, - rt_raster *rtnraster, double *offset -) { - int i; - - rt_raster _rast[2] = {rast1, rast2}; - double _offset[2][4] = {{0.}}; - uint16_t _dim[2][2] = {{0}}; - - rt_raster raster = NULL; - int aligned = 0; - int dim[2] = {0}; - double gt[6] = {0.}; - - assert(NULL != rast1); - assert(NULL != rast2); - assert(NULL != rtnraster); - - /* set rtnraster to NULL */ - *rtnraster = NULL; - - /* rasters must be aligned */ - if (rt_raster_same_alignment(rast1, rast2, &aligned, NULL) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not test for alignment on the two rasters"); - return ES_ERROR; - } - if (!aligned) { - rterror("rt_raster_from_two_rasters: The two rasters provided do not have the same alignment"); - return ES_ERROR; - } - - /* dimensions */ - _dim[0][0] = rast1->width; - _dim[0][1] = rast1->height; - _dim[1][0] = rast2->width; - _dim[1][1] = rast2->height; - - /* get raster offsets */ - if (rt_raster_geopoint_to_cell( - _rast[1], - _rast[0]->ipX, _rast[0]->ipY, - &(_offset[1][0]), &(_offset[1][1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not compute offsets of the second raster relative to the first raster"); - return ES_ERROR; - } - _offset[1][0] = -1 * _offset[1][0]; - _offset[1][1] = -1 * _offset[1][1]; - _offset[1][2] = _offset[1][0] + _dim[1][0] - 1; - _offset[1][3] = _offset[1][1] + _dim[1][1] - 1; - - i = -1; - switch (extenttype) { - case ET_FIRST: - i = 0; - _offset[0][0] = 0.; - _offset[0][1] = 0.; - case ET_LAST: - case ET_SECOND: - if (i < 0) { - i = 1; - _offset[0][0] = -1 * _offset[1][0]; - _offset[0][1] = -1 * _offset[1][1]; - _offset[1][0] = 0.; - _offset[1][1] = 0.; - } - - dim[0] = _dim[i][0]; - dim[1] = _dim[i][1]; - raster = rt_raster_new( - dim[0], - dim[1] - ); - if (raster == NULL) { - rterror("rt_raster_from_two_rasters: Could not create output raster"); - return ES_ERROR; - } - rt_raster_set_srid(raster, _rast[i]->srid); - rt_raster_get_geotransform_matrix(_rast[i], gt); - rt_raster_set_geotransform_matrix(raster, gt); - break; - case ET_UNION: { - double off[4] = {0}; - - rt_raster_get_geotransform_matrix(_rast[0], gt); - RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", - gt[0], - gt[1], - gt[2], - gt[3], - gt[4], - gt[5] - ); - - /* new raster upper left offset */ - off[0] = 0; - if (_offset[1][0] < 0) - off[0] = _offset[1][0]; - off[1] = 0; - if (_offset[1][1] < 0) - off[1] = _offset[1][1]; - - /* new raster lower right offset */ - off[2] = _dim[0][0] - 1; - if ((int) _offset[1][2] >= _dim[0][0]) - off[2] = _offset[1][2]; - off[3] = _dim[0][1] - 1; - if ((int) _offset[1][3] >= _dim[0][1]) - off[3] = _offset[1][3]; - - /* upper left corner */ - if (rt_raster_cell_to_geopoint( - _rast[0], - off[0], off[1], - &(gt[0]), &(gt[3]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get spatial coordinates of upper-left pixel of output raster"); - return ES_ERROR; - } - - dim[0] = off[2] - off[0] + 1; - dim[1] = off[3] - off[1] + 1; - RASTER_DEBUGF(4, "off = (%f, %f, %f, %f)", - off[0], - off[1], - off[2], - off[3] - ); - RASTER_DEBUGF(4, "dim = (%d, %d)", dim[0], dim[1]); - - raster = rt_raster_new( - dim[0], - dim[1] - ); - if (raster == NULL) { - rterror("rt_raster_from_two_rasters: Could not create output raster"); - return ES_ERROR; - } - rt_raster_set_srid(raster, _rast[0]->srid); - rt_raster_set_geotransform_matrix(raster, gt); - RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", - gt[0], - gt[1], - gt[2], - gt[3], - gt[4], - gt[5] - ); - - /* get offsets */ - if (rt_raster_geopoint_to_cell( - _rast[0], - gt[0], gt[3], - &(_offset[0][0]), &(_offset[0][1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get offsets of the FIRST raster relative to the output raster"); - rt_raster_destroy(raster); - return ES_ERROR; - } - _offset[0][0] *= -1; - _offset[0][1] *= -1; - - if (rt_raster_geopoint_to_cell( - _rast[1], - gt[0], gt[3], - &(_offset[1][0]), &(_offset[1][1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get offsets of the SECOND raster relative to the output raster"); - rt_raster_destroy(raster); - return ES_ERROR; - } - _offset[1][0] *= -1; - _offset[1][1] *= -1; - break; - } - case ET_INTERSECTION: { - double off[4] = {0}; - - /* no intersection */ - if ( - (_offset[1][2] < 0 || _offset[1][0] > (_dim[0][0] - 1)) || - (_offset[1][3] < 0 || _offset[1][1] > (_dim[0][1] - 1)) - ) { - RASTER_DEBUG(3, "The two rasters provided have no intersection. Returning no band raster"); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - rterror("rt_raster_from_two_rasters: Could not create output raster"); - return ES_ERROR; - } - rt_raster_set_srid(raster, _rast[0]->srid); - rt_raster_set_scale(raster, 0, 0); - - /* set offsets if provided */ - if (NULL != offset) { - for (i = 0; i < 4; i++) - offset[i] = _offset[i / 2][i % 2]; - } - - *rtnraster = raster; - return ES_NONE; - } - - if (_offset[1][0] > 0) - off[0] = _offset[1][0]; - if (_offset[1][1] > 0) - off[1] = _offset[1][1]; - - off[2] = _dim[0][0] - 1; - if (_offset[1][2] < _dim[0][0]) - off[2] = _offset[1][2]; - off[3] = _dim[0][1] - 1; - if (_offset[1][3] < _dim[0][1]) - off[3] = _offset[1][3]; - - dim[0] = off[2] - off[0] + 1; - dim[1] = off[3] - off[1] + 1; - raster = rt_raster_new( - dim[0], - dim[1] - ); - if (raster == NULL) { - rterror("rt_raster_from_two_rasters: Could not create output raster"); - return ES_ERROR; - } - rt_raster_set_srid(raster, _rast[0]->srid); - - /* get upper-left corner */ - rt_raster_get_geotransform_matrix(_rast[0], gt); - if (rt_raster_cell_to_geopoint( - _rast[0], - off[0], off[1], - &(gt[0]), &(gt[3]), - gt - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get spatial coordinates of upper-left pixel of output raster"); - rt_raster_destroy(raster); - return ES_ERROR; - } - - rt_raster_set_geotransform_matrix(raster, gt); - - /* get offsets */ - if (rt_raster_geopoint_to_cell( - _rast[0], - gt[0], gt[3], - &(_offset[0][0]), &(_offset[0][1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get pixel coordinates to compute the offsets of the FIRST raster relative to the output raster"); - rt_raster_destroy(raster); - return ES_ERROR; - } - _offset[0][0] *= -1; - _offset[0][1] *= -1; - - if (rt_raster_geopoint_to_cell( - _rast[1], - gt[0], gt[3], - &(_offset[1][0]), &(_offset[1][1]), - NULL - ) != ES_NONE) { - rterror("rt_raster_from_two_rasters: Could not get pixel coordinates to compute the offsets of the SECOND raster relative to the output raster"); - rt_raster_destroy(raster); - return ES_ERROR; - } - _offset[1][0] *= -1; - _offset[1][1] *= -1; - break; - } - case ET_CUSTOM: - rterror("rt_raster_from_two_rasters: Extent type ET_CUSTOM is not supported by this function"); - break; - } - - /* set offsets if provided */ - if (NULL != offset) { - for (i = 0; i < 4; i++) - offset[i] = _offset[i / 2][i % 2]; - } - - *rtnraster = raster; - return ES_NONE; -} - -/** - * Get a raster pixel as a polygon. - * - * The pixel shape is a 4 vertices (5 to be closed) single - * ring polygon bearing the raster's rotation - * and using projection coordinates - * - * @param raster : the raster to get pixel from - * @param x : the column number - * @param y : the row number - * - * @return the pixel polygon, or NULL on error. - * - */ -LWPOLY* -rt_raster_pixel_as_polygon(rt_raster rast, int x, int y) -{ - double scale_x, scale_y; - double skew_x, skew_y; - double ul_x, ul_y; - int srid; - POINTARRAY **points; - POINT4D p, p0; - LWPOLY *poly; - - assert(rast != NULL); - - scale_x = rt_raster_get_x_scale(rast); - scale_y = rt_raster_get_y_scale(rast); - skew_x = rt_raster_get_x_skew(rast); - skew_y = rt_raster_get_y_skew(rast); - ul_x = rt_raster_get_x_offset(rast); - ul_y = rt_raster_get_y_offset(rast); - srid = rt_raster_get_srid(rast); - - points = rtalloc(sizeof(POINTARRAY *)*1); - points[0] = ptarray_construct(0, 0, 5); - - p0.x = scale_x * x + skew_x * y + ul_x; - p0.y = scale_y * y + skew_y * x + ul_y; - ptarray_set_point4d(points[0], 0, &p0); - - p.x = p0.x + scale_x; - p.y = p0.y + skew_y; - ptarray_set_point4d(points[0], 1, &p); - - p.x = p0.x + scale_x + skew_x; - p.y = p0.y + scale_y + skew_y; - ptarray_set_point4d(points[0], 2, &p); - - p.x = p0.x + skew_x; - p.y = p0.y + scale_y; - ptarray_set_point4d(points[0], 3, &p); - - /* close it */ - ptarray_set_point4d(points[0], 4, &p0); - - poly = lwpoly_construct(srid, NULL, 1, points); - - return poly; -} - -/** - * Get a raster as a surface (multipolygon). If a band is specified, - * those pixels with value (not NODATA) contribute to the area - * of the output multipolygon. - * - * @param raster : the raster to convert to a multipolygon - * @param nband : the 0-based band of raster rast to use - * if value is less than zero, bands are ignored. - * @param *surface : raster as a surface (multipolygon). - * if all pixels are NODATA, NULL is set - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate rt_raster_surface(rt_raster raster, int nband, LWMPOLY **surface) { - rt_band band = NULL; - LWGEOM *mpoly = NULL; - LWGEOM *tmp = NULL; - LWGEOM *clone = NULL; - rt_geomval gv = NULL; - int gvcount = 0; - GEOSGeometry *gc = NULL; - GEOSGeometry *gunion = NULL; - GEOSGeometry **geoms = NULL; - int geomscount = 0; - int i = 0; - - assert(surface != NULL); - - /* init *surface to NULL */ - *surface = NULL; - - /* raster is empty, surface = NULL */ - if (rt_raster_is_empty(raster)) - return ES_NONE; - - /* if nband < 0, return the convex hull as a multipolygon */ - if (nband < 0) { - /* - lwgeom_as_multi() only does a shallow clone internally - so input and output geometries may share memory - hence the deep clone of the output geometry for returning - is the only way to guarentee the memory isn't shared - */ - if (rt_raster_get_convex_hull(raster, &tmp) != ES_NONE) { - rterror("rt_raster_surface: Could not get convex hull of raster"); - return ES_ERROR; - } - mpoly = lwgeom_as_multi(tmp); - clone = lwgeom_clone_deep(mpoly); - lwgeom_free(tmp); - lwgeom_free(mpoly); - - *surface = lwgeom_as_lwmpoly(clone); - return ES_NONE; - } - /* check that nband is valid */ - else if (nband >= rt_raster_get_num_bands(raster)) { - rterror("rt_raster_surface: The band index %d is invalid", nband); - return ES_ERROR; - } - - /* get band */ - band = rt_raster_get_band(raster, nband); - if (band == NULL) { - rterror("rt_raster_surface: Error getting band %d from raster", nband); - return ES_ERROR; - } - - /* band does not have a NODATA flag, return convex hull */ - if (!rt_band_get_hasnodata_flag(band)) { - /* - lwgeom_as_multi() only does a shallow clone internally - so input and output geometries may share memory - hence the deep clone of the output geometry for returning - is the only way to guarentee the memory isn't shared - */ - if (rt_raster_get_convex_hull(raster, &tmp) != ES_NONE) { - rterror("rt_raster_surface: Could not get convex hull of raster"); - return ES_ERROR; - } - mpoly = lwgeom_as_multi(tmp); - clone = lwgeom_clone_deep(mpoly); - lwgeom_free(tmp); - lwgeom_free(mpoly); - - *surface = lwgeom_as_lwmpoly(clone); - return ES_NONE; - } - /* band is NODATA, return NULL */ - else if (rt_band_get_isnodata_flag(band)) { - RASTER_DEBUG(3, "Band is NODATA. Returning NULL"); - return ES_NONE; - } - - /* initialize GEOS */ - initGEOS(lwnotice, lwgeom_geos_error); - - /* use gdal polygonize */ - gv = rt_raster_gdal_polygonize(raster, nband, 1, &gvcount); - /* no polygons returned */ - if (gvcount < 1) { - RASTER_DEBUG(3, "All pixels of band are NODATA. Returning NULL"); - if (gv != NULL) rtdealloc(gv); - return ES_NONE; - } - /* more than 1 polygon */ - else if (gvcount > 1) { - /* convert LWPOLY to GEOSGeometry */ - geomscount = gvcount; - geoms = rtalloc(sizeof(GEOSGeometry *) * geomscount); - if (geoms == NULL) { - rterror("rt_raster_surface: Could not allocate memory for pixel polygons as GEOSGeometry"); - for (i = 0; i < gvcount; i++) lwpoly_free(gv[i].geom); - rtdealloc(gv); - return ES_ERROR; - } - for (i = 0; i < gvcount; i++) { -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(lwpoly_as_lwgeom(gv[i].geom), WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "geom %d = %s", i, wkt); - rtdealloc(wkt); - } -#endif - - geoms[i] = LWGEOM2GEOS(lwpoly_as_lwgeom(gv[i].geom)); - lwpoly_free(gv[i].geom); - } - rtdealloc(gv); - - /* create geometry collection */ -#if POSTGIS_GEOS_VERSION >= 33 - gc = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, geoms, geomscount); -#else - gc = GEOSGeom_createCollection(GEOS_MULTIPOLYGON, geoms, geomscount); -#endif - - if (gc == NULL) { -#if POSTGIS_GEOS_VERSION >= 33 - rterror("rt_raster_surface: Could not create GEOS GEOMETRYCOLLECTION from set of pixel polygons"); -#else - rterror("rt_raster_surface: Could not create GEOS MULTIPOLYGON from set of pixel polygons"); -#endif - - for (i = 0; i < geomscount; i++) - GEOSGeom_destroy(geoms[i]); - rtdealloc(geoms); - return ES_ERROR; - } - - /* run the union */ -#if POSTGIS_GEOS_VERSION >= 33 - gunion = GEOSUnaryUnion(gc); -#else - gunion = GEOSUnionCascaded(gc); -#endif - GEOSGeom_destroy(gc); - rtdealloc(geoms); - - if (gunion == NULL) { -#if POSTGIS_GEOS_VERSION >= 33 - rterror("rt_raster_surface: Could not union the pixel polygons using GEOSUnaryUnion()"); -#else - rterror("rt_raster_surface: Could not union the pixel polygons using GEOSUnionCascaded()"); -#endif - return ES_ERROR; - } - - /* convert union result from GEOSGeometry to LWGEOM */ - mpoly = GEOS2LWGEOM(gunion, 0); - - /* - is geometry valid? - if not, try to make valid - */ - do { - LWGEOM *mpolyValid = NULL; - -#if POSTGIS_GEOS_VERSION < 33 - break; -#endif - - if (GEOSisValid(gunion)) - break; - - /* make geometry valid */ - mpolyValid = lwgeom_make_valid(mpoly); - if (mpolyValid == NULL) { - rtwarn("Cannot fix invalid geometry"); - break; - } - - lwgeom_free(mpoly); - mpoly = mpolyValid; - } - while (0); - - GEOSGeom_destroy(gunion); - } - else { - mpoly = lwpoly_as_lwgeom(gv[0].geom); - rtdealloc(gv); - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "geom 0 = %s", wkt); - rtdealloc(wkt); - } -#endif - } - - /* specify SRID */ - lwgeom_set_srid(mpoly, rt_raster_get_srid(raster)); - - if (mpoly != NULL) { - /* convert to multi */ - if (!lwgeom_is_collection(mpoly)) { - tmp = mpoly; - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "before multi = %s", wkt); - rtdealloc(wkt); - } -#endif - - RASTER_DEBUGF(4, "mpoly @ %p", mpoly); - - /* - lwgeom_as_multi() only does a shallow clone internally - so input and output geometries may share memory - hence the deep clone of the output geometry for returning - is the only way to guarentee the memory isn't shared - */ - mpoly = lwgeom_as_multi(tmp); - clone = lwgeom_clone_deep(mpoly); - lwgeom_free(tmp); - lwgeom_free(mpoly); - mpoly = clone; - - RASTER_DEBUGF(4, "mpoly @ %p", mpoly); - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "after multi = %s", wkt); - rtdealloc(wkt); - } -#endif - } - -#if POSTGIS_DEBUG_LEVEL > 3 - { - char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); - RASTER_DEBUGF(4, "returning geometry = %s", wkt); - rtdealloc(wkt); - } -#endif - - *surface = lwgeom_as_lwmpoly(mpoly); - return ES_NONE; - } - - return ES_NONE; -} - -/****************************************************************************** -* rt_raster_iterator() -******************************************************************************/ - -typedef struct _rti_iterator_arg_t* _rti_iterator_arg; -struct _rti_iterator_arg_t { - int count; - - rt_raster *raster; - int *isempty; - double **offset; - int *width; - int *height; - - struct { - rt_band *rtband; - int *hasnodata; - int *isnodata; - double *nodataval; - double *minval; - } band; - - struct { - uint16_t x; - uint16_t y; - } distance; - - struct { - uint32_t rows; - uint32_t columns; - } dimension; - - struct { - double **values; - int **nodata; - } empty; - - rt_iterator_arg arg; -}; - -static _rti_iterator_arg -_rti_iterator_arg_init() { - _rti_iterator_arg _param; - - _param = rtalloc(sizeof(struct _rti_iterator_arg_t)); - if (_param == NULL) { - rterror("_rti_iterator_arg_init: Could not allocate memory for _rti_iterator_arg"); - return NULL; - } - - _param->count = 0; - - _param->raster = NULL; - _param->isempty = NULL; - _param->offset = NULL; - _param->width = NULL; - _param->height = NULL; - - _param->band.rtband = NULL; - _param->band.hasnodata = NULL; - _param->band.isnodata = NULL; - _param->band.nodataval = NULL; - _param->band.minval = NULL; - - _param->distance.x = 0; - _param->distance.y = 0; - - _param->dimension.rows = 0; - _param->dimension.columns = 0; - - _param->empty.values = NULL; - _param->empty.nodata = NULL; - - _param->arg = NULL; - - return _param; -} - -static void -_rti_iterator_arg_destroy(_rti_iterator_arg _param) { - int i = 0; - - if (_param->raster != NULL) - rtdealloc(_param->raster); - if (_param->isempty != NULL) - rtdealloc(_param->isempty); - if (_param->width != NULL) - rtdealloc(_param->width); - if (_param->height != NULL) - rtdealloc(_param->height); - - if (_param->band.rtband != NULL) - rtdealloc(_param->band.rtband); - if (_param->band.hasnodata != NULL) - rtdealloc(_param->band.hasnodata); - if (_param->band.isnodata != NULL) - rtdealloc(_param->band.isnodata); - if (_param->band.nodataval != NULL) - rtdealloc(_param->band.nodataval); - if (_param->band.minval != NULL) - rtdealloc(_param->band.minval); - - if (_param->offset != NULL) { - for (i = 0; i < _param->count; i++) { - if (_param->offset[i] == NULL) - continue; - rtdealloc(_param->offset[i]); - } - rtdealloc(_param->offset); - } - - if (_param->empty.values != NULL) { - for (i = 0; i < _param->dimension.rows; i++) { - if (_param->empty.values[i] == NULL) - continue; - rtdealloc(_param->empty.values[i]); - } - rtdealloc(_param->empty.values); - } - if (_param->empty.nodata != NULL) { - for (i = 0; i < _param->dimension.rows; i++) { - if (_param->empty.nodata[i] == NULL) - continue; - rtdealloc(_param->empty.nodata[i]); - } - rtdealloc(_param->empty.nodata); - } - - if (_param->arg != NULL) { - if (_param->arg->values != NULL) - rtdealloc(_param->arg->values); - if (_param->arg->nodata != NULL) - rtdealloc(_param->arg->nodata); - if (_param->arg->src_pixel != NULL) { - for (i = 0; i < _param->count; i++) { - if (_param->arg->src_pixel[i] == NULL) - continue; - rtdealloc(_param->arg->src_pixel[i]); - } - - rtdealloc(_param->arg->src_pixel); - } - - rtdealloc(_param->arg); - } - - rtdealloc(_param); -} - -static int -_rti_iterator_arg_populate( - _rti_iterator_arg _param, - rt_iterator itrset, uint16_t itrcount, - uint16_t distancex, uint16_t distancey, - int *allnull, int *allempty -) { - int i = 0; - int hasband = 0; - - _param->count = itrcount; - _param->distance.x = distancex; - _param->distance.y = distancey; - _param->dimension.columns = distancex * 2 + 1; - _param->dimension.rows = distancey * 2 + 1; - - /* allocate memory for children */ - _param->raster = rtalloc(sizeof(rt_raster) * itrcount); - _param->isempty = rtalloc(sizeof(int) * itrcount); - _param->width = rtalloc(sizeof(int) * itrcount); - _param->height = rtalloc(sizeof(int) * itrcount); - - _param->offset = rtalloc(sizeof(double *) * itrcount); - - _param->band.rtband = rtalloc(sizeof(rt_band) * itrcount); - _param->band.hasnodata = rtalloc(sizeof(int) * itrcount); - _param->band.isnodata = rtalloc(sizeof(int) * itrcount); - _param->band.nodataval = rtalloc(sizeof(double) * itrcount); - _param->band.minval = rtalloc(sizeof(double) * itrcount); - - if ( - _param->raster == NULL || - _param->isempty == NULL || - _param->width == NULL || - _param->height == NULL || - _param->offset == NULL || - _param->band.rtband == NULL || - _param->band.hasnodata == NULL || - _param->band.isnodata == NULL || - _param->band.nodataval == NULL || - _param->band.minval == NULL - ) { - rterror("_rti_iterator_arg_populate: Could not allocate memory for children of _rti_iterator_arg"); - return 0; - } - - *allnull = 0; - *allempty = 0; - - /* - check input rasters - not empty, band # is valid - copy raster pointers and set flags - */ - for (i = 0; i < itrcount; i++) { - /* initialize elements */ - _param->raster[i] = NULL; - _param->isempty[i] = 0; - _param->width[i] = 0; - _param->height[i] = 0; - - _param->offset[i] = NULL; - - _param->band.rtband[i] = NULL; - _param->band.hasnodata[i] = 0; - _param->band.isnodata[i] = 0; - _param->band.nodataval[i] = 0; - _param->band.minval[i] = 0; - - /* set isempty */ - if (itrset[i].raster == NULL) { - _param->isempty[i] = 1; - - (*allnull)++; - (*allempty)++; - - continue; - } - else if (rt_raster_is_empty(itrset[i].raster)) { - _param->isempty[i] = 1; - - (*allempty)++; - - continue; - } - - /* check band number */ - hasband = rt_raster_has_band(itrset[i].raster, itrset[i].nband); - if (!hasband) { - if (!itrset[i].nbnodata) { - rterror("_rti_iterator_arg_populate: Band %d not found for raster %d", itrset[i].nband, i); - return 0; - } - else { - RASTER_DEBUGF(4, "Band %d not found for raster %d. Using NODATA", itrset[i].nband, i); - } - } - - _param->raster[i] = itrset[i].raster; - if (hasband) { - _param->band.rtband[i] = rt_raster_get_band(itrset[i].raster, itrset[i].nband); - if (_param->band.rtband[i] == NULL) { - rterror("_rti_iterator_arg_populate: Could not get band %d for raster %d", itrset[i].nband, i); - return 0; - } - - /* hasnodata */ - _param->band.hasnodata[i] = rt_band_get_hasnodata_flag(_param->band.rtband[i]); - - /* hasnodata = TRUE */ - if (_param->band.hasnodata[i]) { - /* nodataval */ - rt_band_get_nodata(_param->band.rtband[i], &(_param->band.nodataval[i])); - - /* isnodata */ - _param->band.isnodata[i] = rt_band_get_isnodata_flag(_param->band.rtband[i]); - } - /* hasnodata = FALSE */ - else { - /* minval */ - _param->band.minval[i] = rt_band_get_min_value(_param->band.rtband[i]); - } - } - - /* width, height */ - _param->width[i] = rt_raster_get_width(_param->raster[i]); - _param->height[i] = rt_raster_get_height(_param->raster[i]); - - /* init offset */ - _param->offset[i] = rtalloc(sizeof(double) * 2); - if (_param->offset[i] == NULL) { - rterror("_rti_iterator_arg_populate: Could not allocate memory for offsets"); - return 0; - } - } - - return 1; -} - -static int -_rti_iterator_arg_empty_init(_rti_iterator_arg _param) { - int x = 0; - int y = 0; - - _param->empty.values = rtalloc(sizeof(double *) * _param->dimension.rows); - _param->empty.nodata = rtalloc(sizeof(int *) * _param->dimension.rows); - if (_param->empty.values == NULL || _param->empty.nodata == NULL) { - rterror("_rti_iterator_arg_empty_init: Could not allocate memory for empty values and NODATA"); - return 0; - } - - for (y = 0; y < _param->dimension.rows; y++) { - _param->empty.values[y] = rtalloc(sizeof(double) * _param->dimension.columns); - _param->empty.nodata[y] = rtalloc(sizeof(int) * _param->dimension.columns); - - if (_param->empty.values[y] == NULL || _param->empty.nodata[y] == NULL) { - rterror("_rti_iterator_arg_empty_init: Could not allocate memory for elements of empty values and NODATA"); - return 0; - } - - for (x = 0; x < _param->dimension.columns; x++) { - _param->empty.values[y][x] = 0; - _param->empty.nodata[y][x] = 1; - } - } - - return 1; -} - -static int -_rti_iterator_arg_callback_init(_rti_iterator_arg _param) { - int i = 0; - - _param->arg = rtalloc(sizeof(struct rt_iterator_arg_t)); - if (_param->arg == NULL) { - rterror("_rti_iterator_arg_callback_init: Could not allocate memory for rt_iterator_arg"); - return 0; - } - - _param->arg->values = NULL; - _param->arg->nodata = NULL; - _param->arg->src_pixel = NULL; - - /* initialize argument components */ - _param->arg->values = rtalloc(sizeof(double **) * _param->count); - _param->arg->nodata = rtalloc(sizeof(int **) * _param->count); - _param->arg->src_pixel = rtalloc(sizeof(int *) * _param->count); - if (_param->arg->values == NULL || _param->arg->nodata == NULL || _param->arg->src_pixel == NULL) { - rterror("_rti_iterator_arg_callback_init: Could not allocate memory for element of rt_iterator_arg"); - return 0; - } - memset(_param->arg->values, 0, sizeof(double **) * _param->count); - memset(_param->arg->nodata, 0, sizeof(int **) * _param->count); - - /* initialize pos */ - for (i = 0; i < _param->count; i++) { - - _param->arg->src_pixel[i] = rtalloc(sizeof(int) * 2); - if (_param->arg->src_pixel[i] == NULL) { - rterror("_rti_iterator_arg_callback_init: Could not allocate memory for position elements of rt_iterator_arg"); - return 0; - } - memset(_param->arg->src_pixel[i], 0, sizeof(int) * 2); - } - - _param->arg->rasters = _param->count; - _param->arg->rows = _param->dimension.rows; - _param->arg->columns = _param->dimension.columns; - - _param->arg->dst_pixel[0] = 0; - _param->arg->dst_pixel[1] = 0; - - return 1; -} - -static void -_rti_iterator_arg_callback_clean(_rti_iterator_arg _param) { - int i = 0; - int y = 0; - - for (i = 0; i < _param->count; i++) { - RASTER_DEBUGF(5, "empty at @ %p", _param->empty.values); - RASTER_DEBUGF(5, "values at @ %p", _param->arg->values[i]); - - if (_param->arg->values[i] == _param->empty.values) { - _param->arg->values[i] = NULL; - _param->arg->nodata[i] = NULL; - - continue; - } - - for (y = 0; y < _param->dimension.rows; y++) { - rtdealloc(_param->arg->values[i][y]); - rtdealloc(_param->arg->nodata[i][y]); - } - - rtdealloc(_param->arg->values[i]); - rtdealloc(_param->arg->nodata[i]); - - _param->arg->values[i] = NULL; - _param->arg->nodata[i] = NULL; - } -} - -/** - * n-raster iterator. - * The raster returned should be freed by the caller - * - * @param itrset : set of rt_iterator objects. - * @param itrcount : number of objects in itrset. - * @param extenttype : type of extent for the output raster. - * @param customextent : raster specifying custom extent. - * is only used if extenttype is ET_CUSTOM. - * @param pixtype : the desired pixel type of the output raster's band. - * @param hasnodata : indicates if the band has nodata value - * @param nodataval : the nodata value, will be appropriately - * truncated to fit the pixtype size. - * @param distancex : the number of pixels around the specified pixel - * along the X axis - * @param distancey : the number of pixels around the specified pixel - * along the Y axis - * @param userarg : pointer to any argument that is passed as-is to callback. - * @param callback : callback function for actual processing of pixel values. - * @param *rtnraster : return one band raster from iterator process - * - * The callback function _must_ have the following signature. - * - * int FNAME(rt_iterator_arg arg, void *userarg, double *value, int *nodata) - * - * The callback function _must_ return zero (error) or non-zero (success) - * indicating whether the function ran successfully. - * The parameters passed to the callback function are as follows. - * - * - rt_iterator_arg arg: struct containing pixel values, NODATA flags and metadata - * - void *userarg: NULL or calling function provides to rt_raster_iterator() for use by callback function - * - double *value: value of pixel to be burned by rt_raster_iterator() - * - int *nodata: flag (0 or 1) indicating that pixel to be burned is NODATA - * - * @return ES_NONE on success, ES_ERROR on error - */ -rt_errorstate -rt_raster_iterator( - rt_iterator itrset, uint16_t itrcount, - rt_extenttype extenttype, rt_raster customextent, - rt_pixtype pixtype, - uint8_t hasnodata, double nodataval, - uint16_t distancex, uint16_t distancey, - void *userarg, - int (*callback)( - rt_iterator_arg arg, - void *userarg, - double *value, - int *nodata - ), - rt_raster *rtnraster -) { - /* output raster */ - rt_raster rtnrast = NULL; - /* output raster's band */ - rt_band rtnband = NULL; - - /* working raster */ - rt_raster rast = NULL; - - _rti_iterator_arg _param = NULL; - int allnull = 0; - int allempty = 0; - int aligned = 0; - double offset[4] = {0.}; - rt_pixel npixels; - - int i = 0; - int status = 0; - int inextent = 0; - int x = 0; - int y = 0; - int _x = 0; - int _y = 0; - - int _width = 0; - int _height = 0; - - double minval; - double value; - int isnodata; - int nodata; - - RASTER_DEBUG(3, "Starting..."); - - assert(itrset != NULL && itrcount > 0); - assert(rtnraster != NULL); - - /* init rtnraster to NULL */ - *rtnraster = NULL; - - /* check that callback function is not NULL */ - if (callback == NULL) { - rterror("rt_raster_iterator: Callback function not provided"); - return ES_ERROR; - } - - /* check that custom extent is provided if extenttype = ET_CUSTOM */ - if (extenttype == ET_CUSTOM && rt_raster_is_empty(customextent)) { - rterror("rt_raster_iterator: Custom extent cannot be empty if extent type is ET_CUSTOM"); - return ES_ERROR; - } - - /* check that pixtype != PT_END */ - if (pixtype == PT_END) { - rterror("rt_raster_iterator: Pixel type cannot be PT_END"); - return ES_ERROR; - } - - /* initialize _param */ - if ((_param = _rti_iterator_arg_init()) == NULL) { - rterror("rt_raster_iterator: Could not initialize internal variables"); - return ES_ERROR; - } - - /* fill _param */ - if (!_rti_iterator_arg_populate(_param, itrset, itrcount, distancex, distancey, &allnull, &allempty)) { - rterror("rt_raster_iterator: Could not populate for internal variables"); - _rti_iterator_arg_destroy(_param); - return ES_ERROR; - } - - /* shortcut if all null, return NULL */ - if (allnull == itrcount) { - RASTER_DEBUG(3, "all rasters are NULL, returning NULL"); - - _rti_iterator_arg_destroy(_param); - - return ES_NONE; - } - /* shortcut if all empty, return empty raster */ - else if (allempty == itrcount) { - RASTER_DEBUG(3, "all rasters are empty, returning empty raster"); - - _rti_iterator_arg_destroy(_param); - - rtnrast = rt_raster_new(0, 0); - if (rtnrast == NULL) { - rterror("rt_raster_iterator: Could not create empty raster"); - return ES_ERROR; - } - rt_raster_set_scale(rtnrast, 0, 0); - - *rtnraster = rtnrast; - return ES_NONE; - } - - /* check that all rasters are aligned */ - RASTER_DEBUG(3, "checking alignment of all rasters"); - rast = NULL; - - /* find raster to use as reference */ - /* use custom if provided */ - if (extenttype == ET_CUSTOM) { - RASTER_DEBUG(4, "using custom extent as reference raster"); - rast = customextent; - } - /* use first valid one in _param->raster */ - else { - for (i = 0; i < itrcount; i++) { - if (!_param->isempty[i]) { - RASTER_DEBUGF(4, "using raster at index %d as reference raster", i); - rast = _param->raster[i]; - break; - } - } - } - - /* no rasters found, SHOULD NEVER BE HERE! */ - if (rast == NULL) { - rterror("rt_raster_iterator: Could not find reference raster to use for alignment tests"); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - - do { - aligned = 1; - - /* check custom first if set. also skip if rasters are the same */ - if (extenttype == ET_CUSTOM && rast != customextent) { - if (rt_raster_same_alignment(rast, customextent, &aligned, NULL) != ES_NONE) { - rterror("rt_raster_iterator: Could not test for alignment between reference raster and custom extent"); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - - RASTER_DEBUGF(5, "custom extent alignment: %d", aligned); - if (!aligned) - break; - } - - for (i = 0; i < itrcount; i++) { - /* skip NULL rasters and if rasters are the same */ - if (_param->isempty[i] || rast == _param->raster[i]) - continue; - - if (rt_raster_same_alignment(rast, _param->raster[i], &aligned, NULL) != ES_NONE) { - rterror("rt_raster_iterator: Could not test for alignment between reference raster and raster %d", i); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - RASTER_DEBUGF(5, "raster at index %d alignment: %d", i, aligned); - - /* abort checking since a raster isn't aligned */ - if (!aligned) - break; - } - } - while (0); - - /* not aligned, error */ - if (!aligned) { - rterror("rt_raster_iterator: The set of rasters provided (custom extent included, if appropriate) do not have the same alignment"); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - - /* use extenttype to build output raster (no bands though) */ - i = -1; - switch (extenttype) { - case ET_INTERSECTION: - case ET_UNION: - /* make copy of first "real" raster */ - rtnrast = rtalloc(sizeof(struct rt_raster_t)); - if (rtnrast == NULL) { - rterror("rt_raster_iterator: Could not allocate memory for output raster"); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - - for (i = 0; i < itrcount; i++) { - if (!_param->isempty[i]) { - memcpy(rtnrast, _param->raster[i], sizeof(struct rt_raster_serialized_t)); - break; - } - } - rtnrast->numBands = 0; - rtnrast->bands = NULL; - - /* get extent of output raster */ - rast = NULL; - for (i = i + 1; i < itrcount; i++) { - if (_param->isempty[i]) - continue; - - status = rt_raster_from_two_rasters(rtnrast, _param->raster[i], extenttype, &rast, NULL); - rtdealloc(rtnrast); - - if (rast == NULL || status != ES_NONE) { - rterror("rt_raster_iterator: Could not compute %s extent of rasters", - extenttype == ET_UNION ? "union" : "intersection" - ); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - else if (rt_raster_is_empty(rast)) { - rtinfo("rt_raster_iterator: Computed raster for %s extent is empty", - extenttype == ET_UNION ? "union" : "intersection" - ); - - _rti_iterator_arg_destroy(_param); - - *rtnraster = rast; - return ES_NONE; - } - - rtnrast = rast; - rast = NULL; - } - - break; - /* - first, second and last have similar checks - and continue into custom - */ - case ET_FIRST: - i = 0; - case ET_SECOND: - if (i < 0) { - if (itrcount < 2) - i = 0; - else - i = 1; - } - case ET_LAST: - if (i < 0) i = itrcount - 1; - - /* input raster is null, return NULL */ - if (_param->raster[i] == NULL) { - RASTER_DEBUGF(3, "returning NULL as %s raster is NULL and extent type is ET_%s", - (i == 0 ? "first" : (i == 1 ? "second" : "last")), - (i == 0 ? "FIRST" : (i == 1 ? "SECOND" : "LAST")) - ); - - _rti_iterator_arg_destroy(_param); - - return ES_NONE; - } - /* input raster is empty, return empty raster */ - else if (_param->isempty[i]) { - RASTER_DEBUGF(3, "returning empty raster as %s raster is empty and extent type is ET_%s", - (i == 0 ? "first" : (i == 1 ? "second" : "last")), - (i == 0 ? "FIRST" : (i == 1 ? "SECOND" : "LAST")) - ); - - _rti_iterator_arg_destroy(_param); - - rtnrast = rt_raster_new(0, 0); - if (rtnrast == NULL) { - rterror("rt_raster_iterator: Could not create empty raster"); - return ES_ERROR; - } - rt_raster_set_scale(rtnrast, 0, 0); - - *rtnraster = rtnrast; - return ES_NONE; - } - /* copy the custom extent raster */ - case ET_CUSTOM: - rtnrast = rtalloc(sizeof(struct rt_raster_t)); - if (rtnrast == NULL) { - rterror("rt_raster_iterator: Could not allocate memory for output raster"); - - _rti_iterator_arg_destroy(_param); - - return ES_ERROR; - } - - switch (extenttype) { - case ET_CUSTOM: - memcpy(rtnrast, customextent, sizeof(struct rt_raster_serialized_t)); - break; - /* first, second, last */ - default: - memcpy(rtnrast, _param->raster[i], sizeof(struct rt_raster_serialized_t)); - break; - } - rtnrast->numBands = 0; - rtnrast->bands = NULL; - break; - } - - _width = rt_raster_get_width(rtnrast); - _height = rt_raster_get_height(rtnrast); - - RASTER_DEBUGF(4, "rtnrast (width, height, ulx, uly, scalex, scaley, skewx, skewy, srid) = (%d, %d, %f, %f, %f, %f, %f, %f, %d)", - _width, - _height, - rt_raster_get_x_offset(rtnrast), - rt_raster_get_y_offset(rtnrast), - rt_raster_get_x_scale(rtnrast), - rt_raster_get_y_scale(rtnrast), - rt_raster_get_x_skew(rtnrast), - rt_raster_get_y_skew(rtnrast), - rt_raster_get_srid(rtnrast) - ); - - /* init values and NODATA for use with empty rasters */ - if (!_rti_iterator_arg_empty_init(_param)) { - rterror("rt_raster_iterator: Could not initialize empty values and NODATA"); - - _rti_iterator_arg_destroy(_param); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - /* create output band */ - if (rt_raster_generate_new_band( - rtnrast, - pixtype, - nodataval, - hasnodata, nodataval, - 0 - ) < 0) { - rterror("rt_raster_iterator: Could not add new band to output raster"); - - _rti_iterator_arg_destroy(_param); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - /* get output band */ - rtnband = rt_raster_get_band(rtnrast, 0); - if (rtnband == NULL) { - rterror("rt_raster_iterator: Could not get new band from output raster"); - - _rti_iterator_arg_destroy(_param); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - /* output band's minimum value */ - minval = rt_band_get_min_value(rtnband); - - /* initialize argument for callback function */ - if (!_rti_iterator_arg_callback_init(_param)) { - rterror("rt_raster_iterator: Could not initialize callback function argument"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - /* fill _param->offset */ - for (i = 0; i < itrcount; i++) { - if (_param->isempty[i]) - continue; - - status = rt_raster_from_two_rasters(rtnrast, _param->raster[i], ET_FIRST, &rast, offset); - rtdealloc(rast); - if (status != ES_NONE) { - rterror("rt_raster_iterator: Could not compute raster offsets"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - _param->offset[i][0] = offset[2]; - _param->offset[i][1] = offset[3]; - RASTER_DEBUGF(4, "rast %d offset: %f %f", i, offset[2], offset[3]); - } - - /* loop over each pixel (POI) of output raster */ - /* _x,_y are for output raster */ - /* x,y are for input raster */ - for (_y = 0; _y < _height; _y++) { - for (_x = 0; _x < _width; _x++) { - RASTER_DEBUGF(4, "iterating output pixel (x, y) = (%d, %d)", _x, _y); - _param->arg->dst_pixel[0] = _x; - _param->arg->dst_pixel[1] = _y; - - /* loop through each input raster */ - for (i = 0; i < itrcount; i++) { - RASTER_DEBUGF(4, "raster %d", i); - - /* - empty raster - OR band does not exist and flag set to use NODATA - OR band is NODATA - */ - if ( - _param->isempty[i] || - (_param->band.rtband[i] == NULL && itrset[i].nbnodata) || - _param->band.isnodata[i] - ) { - RASTER_DEBUG(4, "empty raster, band does not exist or band is NODATA. using empty values and NODATA"); - - x = _x; - y = _y; - - _param->arg->values[i] = _param->empty.values; - _param->arg->nodata[i] = _param->empty.nodata; - - continue; - } - - /* input raster's X,Y */ - x = _x - (int) _param->offset[i][0]; - y = _y - (int) _param->offset[i][1]; - RASTER_DEBUGF(4, "source pixel (x, y) = (%d, %d)", x, y); - - _param->arg->src_pixel[i][0] = x; - _param->arg->src_pixel[i][1] = y; - - /* neighborhood */ - npixels = NULL; - status = 0; - if (distancex > 0 && distancey > 0) { - RASTER_DEBUG(4, "getting neighborhood"); - - status = rt_band_get_nearest_pixel( - _param->band.rtband[i], - x, y, - distancex, distancey, - 1, - &npixels - ); - if (status < 0) { - rterror("rt_raster_iterator: Could not get pixel neighborhood"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - } - - /* get value of POI */ - /* get pixel's value */ - if ( - (x >= 0 && x < _param->width[i]) && - (y >= 0 && y < _param->height[i]) - ) { - RASTER_DEBUG(4, "getting value of POI"); - if (rt_band_get_pixel( - _param->band.rtband[i], - x, y, - &value, - &isnodata - ) != ES_NONE) { - rterror("rt_raster_iterator: Could not get the pixel value of band"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - inextent = 1; - } - /* outside band extent, set to NODATA */ - else { - RASTER_DEBUG(4, "Outside band extent, setting value to NODATA"); - /* has NODATA, use NODATA */ - if (_param->band.hasnodata[i]) - value = _param->band.nodataval[i]; - /* no NODATA, use min possible value */ - else - value = _param->band.minval[i]; - - inextent = 0; - isnodata = 1; - } - - /* add pixel to neighborhood */ - status++; - if (status > 1) - npixels = (rt_pixel) rtrealloc(npixels, sizeof(struct rt_pixel_t) * status); - else - npixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t)); - - if (npixels == NULL) { - rterror("rt_raster_iterator: Could not reallocate memory for neighborhood"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - npixels[status - 1].x = x; - npixels[status - 1].y = y; - npixels[status - 1].nodata = 1; - npixels[status - 1].value = value; - - /* set nodata flag */ - if ((!_param->band.hasnodata[i] && inextent) || !isnodata) { - npixels[status - 1].nodata = 0; - } - RASTER_DEBUGF(4, "value, nodata: %f, %d", value, npixels[status - 1].nodata); - - /* convert set of rt_pixel to 2D array */ - status = rt_pixel_set_to_array( - npixels, status, - x, y, - distancex, distancey, - &(_param->arg->values[i]), - &(_param->arg->nodata[i]), - NULL, NULL - ); - rtdealloc(npixels); - if (status != ES_NONE) { - rterror("rt_raster_iterator: Could not create 2D array of neighborhood"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - } - - /* callback */ - RASTER_DEBUG(4, "calling callback function"); - value = 0; - nodata = 0; - status = callback(_param->arg, userarg, &value, &nodata); - - /* free memory from callback */ - _rti_iterator_arg_callback_clean(_param); - - /* handle callback status */ - if (status == 0) { - rterror("rt_raster_iterator: Callback function returned an error"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - - /* burn value to pixel */ - status = 0; - if (!nodata) { - status = rt_band_set_pixel(rtnband, _x, _y, value, NULL); - RASTER_DEBUGF(4, "burning pixel (%d, %d) with value: %f", _x, _y, value); - } - else if (!hasnodata) { - status = rt_band_set_pixel(rtnband, _x, _y, minval, NULL); - RASTER_DEBUGF(4, "burning pixel (%d, %d) with minval: %f", _x, _y, minval); - } - else { - RASTER_DEBUGF(4, "NOT burning pixel (%d, %d)", _x, _y); - } - if (status != ES_NONE) { - rterror("rt_raster_iterator: Could not set pixel value"); - - _rti_iterator_arg_destroy(_param); - rt_band_destroy(rtnband); - rt_raster_destroy(rtnrast); - - return ES_ERROR; - } - } - } - - /* lots of cleanup */ - _rti_iterator_arg_destroy(_param); - - *rtnraster = rtnrast; - return ES_NONE; -} - -/****************************************************************************** -* rt_raster_perimeter() -******************************************************************************/ -static rt_errorstate -_rti_raster_get_band_perimeter(rt_band band, uint16_t *trim) { - uint16_t width = 0; - uint16_t height = 0; - int x = 0; - int y = 0; - int offset = 0; - int done[4] = {0}; - double value = 0; - int nodata = 0; - - assert(band != NULL); - assert(band->raster != NULL); - assert(trim != NULL); - - memset(trim, 0, sizeof(uint16_t) * 4); - - width = rt_band_get_width(band); - height = rt_band_get_height(band); - - /* top */ - for (y = 0; y < height; y++) { - for (offset = 0; offset < 3; offset++) { - /* every third pixel */ - for (x = offset; x < width; x += 3) { - if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { - rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); - return ES_ERROR; - } - - RASTER_DEBUGF(4, "top (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); - if (!nodata) { - trim[0] = y; - done[0] = 1; - break; - } - } - - if (done[0]) - break; - } - - if (done[0]) - break; - } - - /* right */ - for (x = width - 1; x >= 0; x--) { - for (offset = 0; offset < 3; offset++) { - /* every third pixel */ - for (y = offset; y < height; y += 3) { - if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { - rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); - return ES_ERROR; - } - - RASTER_DEBUGF(4, "right (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); - if (!nodata) { - trim[1] = width - (x + 1); - done[1] = 1; - break; - } - } - - if (done[1]) - break; - } - - if (done[1]) - break; - } - - /* bottom */ - for (y = height - 1; y >= 0; y--) { - for (offset = 0; offset < 3; offset++) { - /* every third pixel */ - for (x = offset; x < width; x += 3) { - if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { - rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); - return ES_ERROR; - } - - RASTER_DEBUGF(4, "bottom (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); - if (!nodata) { - trim[2] = height - (y + 1); - done[2] = 1; - break; - } - } - - if (done[2]) - break; - } - - if (done[2]) - break; - } - - /* left */ - for (x = 0; x < width; x++) { - for (offset = 0; offset < 3; offset++) { - /* every third pixel */ - for (y = offset; y < height; y += 3) { - if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { - rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); - return ES_ERROR; - } - - RASTER_DEBUGF(4, "left (x, , value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); - if (!nodata) { - trim[3] = x; - done[3] = 1; - break; - } - } - - if (done[3]) - break; - } - - if (done[3]) - break; - } - - RASTER_DEBUGF(4, "trim = (%d, %d, %d, %d)", - trim[0], trim[1], trim[2], trim[3]); - - return ES_NONE; -} - -/** - * Get raster perimeter - * - * The perimeter is a 4 vertices (5 to be closed) - * single ring polygon bearing the raster's rotation and using - * projection coordinates. - * - * @param raster : the raster to get info from - * @param nband : the band for the perimeter. 0-based - * value less than zero means all bands - * @param **perimeter : pointer to perimeter - * - * @return ES_NONE if success, ES_ERROR if error - */ -rt_errorstate rt_raster_get_perimeter( - rt_raster raster, int nband, - LWGEOM **perimeter -) { - rt_band band = NULL; - int numband = 0; - uint16_t *_nband = NULL; - int i = 0; - int j = 0; - uint16_t _trim[4] = {0}; - uint16_t trim[4] = {0}; /* top, right, bottom, left */ - int isset[4] = {0}; - double gt[6] = {0.0}; - int srid = SRID_UNKNOWN; - - POINTARRAY *pts = NULL; - POINT4D p4d; - POINTARRAY **rings = NULL; - LWPOLY* poly = NULL; - - assert(perimeter != NULL); - - *perimeter = NULL; - - /* empty raster, no perimeter */ - if (rt_raster_is_empty(raster)) - return ES_NONE; - - /* raster metadata */ - srid = rt_raster_get_srid(raster); - rt_raster_get_geotransform_matrix(raster, gt); - numband = rt_raster_get_num_bands(raster); - - RASTER_DEBUGF(3, "rt_raster_get_perimeter: raster is %dx%d", raster->width, raster->height); - - /* nband < 0 means all bands */ - if (nband >= 0) { - if (nband >= numband) { - rterror("rt_raster_get_boundary: Band %d not found for raster", nband); - return ES_ERROR; - } - - numband = 1; - } - else - nband = -1; - - RASTER_DEBUGF(3, "rt_raster_get_perimeter: nband, numband = %d, %d", nband, numband); - - _nband = rtalloc(sizeof(uint16_t) * numband); - if (_nband == NULL) { - rterror("rt_raster_get_boundary: Could not allocate memory for band indices"); - return ES_ERROR; - } - - if (nband < 0) { - for (i = 0; i < numband; i++) - _nband[i] = i; - } - else - _nband[0] = nband; - - for (i = 0; i < numband; i++) { - band = rt_raster_get_band(raster, _nband[i]); - if (band == NULL) { - rterror("rt_raster_get_boundary: Could not get band at index %d", _nband[i]); - rtdealloc(_nband); - return ES_ERROR; - } - - /* band is nodata */ - if (rt_band_get_isnodata_flag(band) != 0) - continue; - - if (_rti_raster_get_band_perimeter(band, trim) != ES_NONE) { - rterror("rt_raster_get_boundary: Could not get band perimeter"); - rtdealloc(_nband); - return ES_ERROR; - } - - for (j = 0; j < 4; j++) { - if (!isset[j] || trim[j] < _trim[j]) { - _trim[j] = trim[j]; - isset[j] = 1; - } - } - } - - /* no longer needed */ - rtdealloc(_nband); - - /* check isset, just need to check one element */ - if (!isset[0]) { - /* return NULL as bands are empty */ - return ES_NONE; - } - - RASTER_DEBUGF(4, "trim = (%d, %d, %d, %d)", - trim[0], trim[1], trim[2], trim[3]); - - /* only one ring */ - rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); - if (!rings) { - rterror("rt_raster_get_perimeter: Could not allocate memory for polygon ring"); - return ES_ERROR; - } - rings[0] = ptarray_construct(0, 0, 5); - if (!rings[0]) { - rterror("rt_raster_get_perimeter: Could not construct point array"); - return ES_ERROR; - } - pts = rings[0]; - - /* Upper-left corner (first and last points) */ - rt_raster_cell_to_geopoint( - raster, - _trim[3], _trim[0], - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 0, &p4d); - ptarray_set_point4d(pts, 4, &p4d); - - /* Upper-right corner (we go clockwise) */ - rt_raster_cell_to_geopoint( - raster, - raster->width - _trim[1], _trim[0], - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 1, &p4d); - - /* Lower-right corner */ - rt_raster_cell_to_geopoint( - raster, - raster->width - _trim[1], raster->height - _trim[2], - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 2, &p4d); - - /* Lower-left corner */ - rt_raster_cell_to_geopoint( - raster, - _trim[3], raster->height - _trim[2], - &p4d.x, &p4d.y, - gt - ); - ptarray_set_point4d(pts, 3, &p4d); - - poly = lwpoly_construct(srid, 0, 1, rings); - *perimeter = lwpoly_as_lwgeom(poly); - - return ES_NONE; -} - -/****************************************************************************** -* rt_raster_colormap() -******************************************************************************/ - -typedef struct _rti_colormap_arg_t* _rti_colormap_arg; -struct _rti_colormap_arg_t { - rt_raster raster; - rt_band band; - - rt_colormap_entry nodataentry; - int hasnodata; - double nodataval; - - int nexpr; - rt_reclassexpr *expr; - - int npos; - uint16_t *pos; - -}; - -static _rti_colormap_arg -_rti_colormap_arg_init(rt_raster raster) { - _rti_colormap_arg arg = NULL; - - arg = rtalloc(sizeof(struct _rti_colormap_arg_t)); - if (arg == NULL) { - rterror("_rti_colormap_arg_init: Could not allocate memory for _rti_color_arg"); - return NULL; - } - - arg->band = NULL; - arg->nodataentry = NULL; - arg->hasnodata = 0; - arg->nodataval = 0; - - if (raster == NULL) - arg->raster = NULL; - /* raster provided */ - else { - arg->raster = rt_raster_clone(raster, 0); - if (arg->raster == NULL) { - rterror("_rti_colormap_arg_init: Could not create output raster"); - return NULL; - } - } - - arg->nexpr = 0; - arg->expr = NULL; - - arg->npos = 0; - arg->pos = NULL; - - return arg; -} - -static void -_rti_colormap_arg_destroy(_rti_colormap_arg arg) { - int i = 0; - - if (arg->raster != NULL) { - rt_band band = NULL; - - for (i = rt_raster_get_num_bands(arg->raster) - 1; i >= 0; i--) { - band = rt_raster_get_band(arg->raster, i); - if (band != NULL) - rt_band_destroy(band); - } - - rt_raster_destroy(arg->raster); - } - - if (arg->nexpr) { - for (i = 0; i < arg->nexpr; i++) { - if (arg->expr[i] != NULL) - rtdealloc(arg->expr[i]); - } - rtdealloc(arg->expr); - } - - if (arg->npos) - rtdealloc(arg->pos); - - rtdealloc(arg); - arg = NULL; -} - -/** - * Returns a new raster with up to four 8BUI bands (RGBA) from - * applying a colormap to the user-specified band of the - * input raster. - * - * @param raster: input raster - * @param nband: 0-based index of the band to process with colormap - * @param colormap: rt_colormap object of colormap to apply to band - * - * @return new raster or NULL on error - */ -rt_raster rt_raster_colormap( - rt_raster raster, int nband, - rt_colormap colormap -) { - _rti_colormap_arg arg = NULL; - rt_raster rtnraster = NULL; - rt_band band = NULL; - int i = 0; - int j = 0; - int k = 0; - - assert(colormap != NULL); - - /* empty raster */ - if (rt_raster_is_empty(raster)) - return NULL; - - /* no colormap entries */ - if (colormap->nentry < 1) { - rterror("rt_raster_colormap: colormap must have at least one entry"); - return NULL; - } - - /* nband is valid */ - if (!rt_raster_has_band(raster, nband)) { - rterror("rt_raster_colormap: raster has no band at index %d", nband); - return NULL; - } - - band = rt_raster_get_band(raster, nband); - if (band == NULL) { - rterror("rt_raster_colormap: Could not get band at index %d", nband); - return NULL; - } - - /* init internal variables */ - arg = _rti_colormap_arg_init(raster); - if (arg == NULL) { - rterror("rt_raster_colormap: Could not initialize internal variables"); - return NULL; - } - - /* handle NODATA */ - if (rt_band_get_hasnodata_flag(band)) { - arg->hasnodata = 1; - rt_band_get_nodata(band, &(arg->nodataval)); - } - - /* # of colors */ - if (colormap->ncolor < 1) { - rterror("rt_raster_colormap: At least one color must be provided"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - else if (colormap->ncolor > 4) { - rtinfo("More than four colors indicated. Using only the first four colors"); - colormap->ncolor = 4; - } - - /* find non-NODATA entries */ - arg->npos = 0; - arg->pos = rtalloc(sizeof(uint16_t) * colormap->nentry); - if (arg->pos == NULL) { - rterror("rt_raster_colormap: Could not allocate memory for valid entries"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - for (i = 0, j = 0; i < colormap->nentry; i++) { - /* special handling for NODATA entries */ - if (colormap->entry[i].isnodata) { - /* first NODATA entry found, use it */ - if (arg->nodataentry == NULL) - arg->nodataentry = &(colormap->entry[i]); - else - rtwarn("More than one colormap entry found for NODATA value. Only using first NOTDATA entry"); - - continue; - } - - (arg->npos)++; - arg->pos[j++] = i; - } - - /* INTERPOLATE and only one non-NODATA entry */ - if (colormap->method == CM_INTERPOLATE && arg->npos < 2) { - rtinfo("Method INTERPOLATE requires at least two non-NODATA colormap entries. Using NEAREST instead"); - colormap->method = CM_NEAREST; - } - - /* NODATA entry but band has no NODATA value */ - if (!arg->hasnodata && arg->nodataentry != NULL) { - rtinfo("Band at index %d has no NODATA value. Ignoring NODATA entry", nband); - arg->nodataentry = NULL; - } - - /* allocate expr */ - arg->nexpr = arg->npos; - - /* INTERPOLATE needs one less than the number of entries */ - if (colormap->method == CM_INTERPOLATE) - arg->nexpr -= 1; - /* EXACT requires a no matching expression */ - else if (colormap->method == CM_EXACT) - arg->nexpr += 1; - - /* NODATA entry exists, add expression */ - if (arg->nodataentry != NULL) - arg->nexpr += 1; - arg->expr = rtalloc(sizeof(rt_reclassexpr) * arg->nexpr); - if (arg->expr == NULL) { - rterror("rt_raster_colormap: Could not allocate memory for reclass expressions"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - RASTER_DEBUGF(4, "nexpr = %d", arg->nexpr); - RASTER_DEBUGF(4, "expr @ %p", arg->expr); - - for (i = 0; i < arg->nexpr; i++) { - arg->expr[i] = rtalloc(sizeof(struct rt_reclassexpr_t)); - if (arg->expr[i] == NULL) { - rterror("rt_raster_colormap: Could not allocate memory for reclass expression"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - } - - /* reclassify bands */ - /* by # of colors */ - for (i = 0; i < colormap->ncolor; i++) { - k = 0; - - /* handle NODATA entry first */ - if (arg->nodataentry != NULL) { - arg->expr[k]->src.min = arg->nodataentry->value; - arg->expr[k]->src.max = arg->nodataentry->value; - arg->expr[k]->src.inc_min = 1; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_min = 0; - arg->expr[k]->src.exc_max = 0; - - arg->expr[k]->dst.min = arg->nodataentry->color[i]; - arg->expr[k]->dst.max = arg->nodataentry->color[i]; - - arg->expr[k]->dst.inc_min = 1; - arg->expr[k]->dst.inc_max = 1; - arg->expr[k]->dst.exc_min = 0; - arg->expr[k]->dst.exc_max = 0; - - RASTER_DEBUGF(4, "NODATA expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->src.min, - arg->expr[k]->src.max, - arg->expr[k]->src.inc_min, - arg->expr[k]->src.inc_max, - arg->expr[k]->src.exc_min, - arg->expr[k]->src.exc_max - ); - RASTER_DEBUGF(4, "NODATA expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->dst.min, - arg->expr[k]->dst.max, - arg->expr[k]->dst.inc_min, - arg->expr[k]->dst.inc_max, - arg->expr[k]->dst.exc_min, - arg->expr[k]->dst.exc_max - ); - - k++; - } - - /* by non-NODATA entry */ - for (j = 0; j < arg->npos; j++) { - if (colormap->method == CM_INTERPOLATE) { - if (j == arg->npos - 1) - continue; - - arg->expr[k]->src.min = colormap->entry[arg->pos[j + 1]].value; - arg->expr[k]->src.inc_min = 1; - arg->expr[k]->src.exc_min = 0; - - arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_max = 0; - - arg->expr[k]->dst.min = colormap->entry[arg->pos[j + 1]].color[i]; - arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; - - arg->expr[k]->dst.inc_min = 1; - arg->expr[k]->dst.exc_min = 0; - - arg->expr[k]->dst.inc_max = 1; - arg->expr[k]->dst.exc_max = 0; - } - else if (colormap->method == CM_NEAREST) { - - /* NOT last entry */ - if (j != arg->npos - 1) { - arg->expr[k]->src.min = ((colormap->entry[arg->pos[j]].value - colormap->entry[arg->pos[j + 1]].value) / 2.) + colormap->entry[arg->pos[j + 1]].value; - arg->expr[k]->src.inc_min = 0; - arg->expr[k]->src.exc_min = 0; - } - /* last entry */ - else { - arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value; - arg->expr[k]->src.inc_min = 1; - arg->expr[k]->src.exc_min = 1; - } - - /* NOT first entry */ - if (j > 0) { - arg->expr[k]->src.max = arg->expr[k - 1]->src.min; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_max = 0; - } - /* first entry */ - else { - arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_max = 1; - } - - arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i]; - arg->expr[k]->dst.inc_min = 1; - arg->expr[k]->dst.exc_min = 0; - - arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; - arg->expr[k]->dst.inc_max = 1; - arg->expr[k]->dst.exc_max = 0; - } - else if (colormap->method == CM_EXACT) { - arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value; - arg->expr[k]->src.inc_min = 1; - arg->expr[k]->src.exc_min = 0; - - arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_max = 0; - - arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i]; - arg->expr[k]->dst.inc_min = 1; - arg->expr[k]->dst.exc_min = 0; - - arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; - arg->expr[k]->dst.inc_max = 1; - arg->expr[k]->dst.exc_max = 0; - } - - RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->src.min, - arg->expr[k]->src.max, - arg->expr[k]->src.inc_min, - arg->expr[k]->src.inc_max, - arg->expr[k]->src.exc_min, - arg->expr[k]->src.exc_max - ); - - RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->dst.min, - arg->expr[k]->dst.max, - arg->expr[k]->dst.inc_min, - arg->expr[k]->dst.inc_max, - arg->expr[k]->dst.exc_min, - arg->expr[k]->dst.exc_max - ); - - k++; - } - - /* EXACT has one last expression for catching all uncaught values */ - if (colormap->method == CM_EXACT) { - arg->expr[k]->src.min = 0; - arg->expr[k]->src.inc_min = 1; - arg->expr[k]->src.exc_min = 1; - - arg->expr[k]->src.max = 0; - arg->expr[k]->src.inc_max = 1; - arg->expr[k]->src.exc_max = 1; - - arg->expr[k]->dst.min = 0; - arg->expr[k]->dst.inc_min = 1; - arg->expr[k]->dst.exc_min = 0; - - arg->expr[k]->dst.max = 0; - arg->expr[k]->dst.inc_max = 1; - arg->expr[k]->dst.exc_max = 0; - - RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->src.min, - arg->expr[k]->src.max, - arg->expr[k]->src.inc_min, - arg->expr[k]->src.inc_max, - arg->expr[k]->src.exc_min, - arg->expr[k]->src.exc_max - ); - - RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", - k, - arg->expr[k]->dst.min, - arg->expr[k]->dst.max, - arg->expr[k]->dst.inc_min, - arg->expr[k]->dst.inc_max, - arg->expr[k]->dst.exc_min, - arg->expr[k]->dst.exc_max - ); - - k++; - } - - /* call rt_band_reclass */ - arg->band = rt_band_reclass(band, PT_8BUI, 0, 0, arg->expr, arg->nexpr); - if (arg->band == NULL) { - rterror("rt_raster_colormap: Could not reclassify band"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - - /* add reclassified band to raster */ - if (rt_raster_add_band(arg->raster, arg->band, rt_raster_get_num_bands(arg->raster)) < 0) { - rterror("rt_raster_colormap: Could not add reclassified band to output raster"); - _rti_colormap_arg_destroy(arg); - return NULL; - } - } - - rtnraster = arg->raster; - arg->raster = NULL; - _rti_colormap_arg_destroy(arg); - - return rtnraster; -} diff --git a/raster/rt_core/rt_band.c b/raster/rt_core/rt_band.c new file mode 100644 index 000000000..076559794 --- /dev/null +++ b/raster/rt_core/rt_band.c @@ -0,0 +1,1773 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +#include "gdal_vrt.h" + +/** + * Create an in-db rt_band with no data + * + * @param width : number of pixel columns + * @param height : number of pixel rows + * @param pixtype : pixel type for the band + * @param hasnodata : indicates if the band has nodata value + * @param nodataval : the nodata value, will be appropriately + * truncated to fit the pixtype size. + * @param data : pointer to actual band data, required to + * be aligned accordingly to + * rt_pixtype_aligment(pixtype) and big enough + * to hold raster width*height values. + * Data will NOT be copied, ownership is left + * to caller which is responsible to keep it + * allocated for the whole lifetime of the returned + * rt_band. + * + * @return an rt_band, or 0 on failure + */ +rt_band +rt_band_new_inline( + uint16_t width, uint16_t height, + rt_pixtype pixtype, + uint32_t hasnodata, double nodataval, + uint8_t* data +) { + rt_band band = NULL; + + assert(NULL != data); + + band = rtalloc(sizeof(struct rt_band_t)); + if (band == NULL) { + rterror("rt_band_new_inline: Out of memory allocating rt_band"); + return NULL; + } + + RASTER_DEBUGF(3, "Created rt_band @ %p with pixtype %s", band, rt_pixtype_name(pixtype)); + + band->pixtype = pixtype; + band->offline = 0; + band->width = width; + band->height = height; + band->hasnodata = hasnodata ? 1 : 0; + band->isnodata = FALSE; /* we don't know what is in data, so must be FALSE */ + band->nodataval = 0; + band->data.mem = data; + band->ownsdata = 0; /* we do NOT own this data!!! */ + band->raster = NULL; + + RASTER_DEBUGF(3, "Created rt_band with dimensions %d x %d", band->width, band->height); + + /* properly set nodataval as it may need to be constrained to the data type */ + if (hasnodata && rt_band_set_nodata(band, nodataval, NULL) != ES_NONE) { + rterror("rt_band_new_inline: Could not set NODATA value"); + rt_band_destroy(band); + return NULL; + } + + return band; +} + +/** + * Create an out-db rt_band + * + * @param width : number of pixel columns + * @param height : number of pixel rows + * @param pixtype : pixel type for the band + * @param hasnodata : indicates if the band has nodata value + * @param nodataval : the nodata value, will be appropriately + * truncated to fit the pixtype size. + * @param bandNum : 0-based band number in the external file + * to associate this band with. + * @param path : NULL-terminated path string pointing to the file + * containing band data. The string will NOT be + * copied, ownership is left to caller which is + * responsible to keep it allocated for the whole + * lifetime of the returned rt_band. + * + * @return an rt_band, or 0 on failure + */ +rt_band +rt_band_new_offline( + uint16_t width, uint16_t height, + rt_pixtype pixtype, + uint32_t hasnodata, double nodataval, + uint8_t bandNum, const char* path +) { + rt_band band = NULL; + int pathlen = 0; + + assert(NULL != path); + + band = rtalloc(sizeof(struct rt_band_t)); + if (band == NULL) { + rterror("rt_band_new_offline: Out of memory allocating rt_band"); + return NULL; + } + + RASTER_DEBUGF(3, "Created rt_band @ %p with pixtype %s", + band, rt_pixtype_name(pixtype) + ); + + band->pixtype = pixtype; + band->offline = 1; + band->width = width; + band->height = height; + band->hasnodata = hasnodata ? 1 : 0; + band->nodataval = 0; + band->isnodata = FALSE; /* we don't know if the offline band is NODATA */ + band->ownsdata = 0; /* offline, flag is useless as all offline data cache is owned internally */ + band->raster = NULL; + + /* properly set nodataval as it may need to be constrained to the data type */ + if (hasnodata && rt_band_set_nodata(band, nodataval, NULL) != ES_NONE) { + rterror("rt_band_new_offline: Could not set NODATA value"); + rt_band_destroy(band); + return NULL; + } + + band->data.offline.bandNum = bandNum; + + /* memory for data.offline.path is managed internally */ + pathlen = strlen(path); + band->data.offline.path = rtalloc(sizeof(char) * (pathlen + 1)); + if (band->data.offline.path == NULL) { + rterror("rt_band_new_offline: Out of memory allocating offline path"); + rt_band_destroy(band); + return NULL; + } + memcpy(band->data.offline.path, path, pathlen); + band->data.offline.path[pathlen] = '\0'; + + band->data.offline.mem = NULL; + + return band; +} + +/** + * Create a new band duplicated from source band. Memory is allocated + * for band path (if band is offline) or band data (if band is online). + * The caller is responsible for freeing the memory when the returned + * rt_band is destroyed. + * + * @param : the band to copy + * + * @return an rt_band or NULL on failure + */ +rt_band +rt_band_duplicate(rt_band band) { + rt_band rtn = NULL; + + assert(band != NULL); + + /* offline */ + if (band->offline) { + rtn = rt_band_new_offline( + band->width, band->height, + band->pixtype, + band->hasnodata, band->nodataval, + band->data.offline.bandNum, (const char *) band->data.offline.path + ); + } + /* online */ + else { + uint8_t *data = NULL; + data = rtalloc(rt_pixtype_size(band->pixtype) * band->width * band->height); + if (data == NULL) { + rterror("rt_band_duplicate: Out of memory allocating online band data"); + return NULL; + } + memcpy(data, band->data.mem, rt_pixtype_size(band->pixtype) * band->width * band->height); + + rtn = rt_band_new_inline( + band->width, band->height, + band->pixtype, + band->hasnodata, band->nodataval, + data + ); + rt_band_set_ownsdata_flag(rtn, 1); /* we DO own this data!!! */ + } + + if (rtn == NULL) { + rterror("rt_band_duplicate: Could not copy band"); + return NULL; + } + + return rtn; +} + +int +rt_band_is_offline(rt_band band) { + + assert(NULL != band); + + + return band->offline ? 1 : 0; +} + +/** + * Destroy a raster band + * + * @param band : the band to destroy + */ +void +rt_band_destroy(rt_band band) { + if (band == NULL) + return; + + RASTER_DEBUGF(3, "Destroying rt_band @ %p", band); + + /* offline band */ + if (band->offline) { + /* memory cache */ + if (band->data.offline.mem != NULL) + rtdealloc(band->data.offline.mem); + /* offline file path */ + if (band->data.offline.path != NULL) + rtdealloc(band->data.offline.path); + } + /* inline band and band owns the data */ + else if (band->data.mem != NULL && band->ownsdata) + rtdealloc(band->data.mem); + + rtdealloc(band); +} + +const char* +rt_band_get_ext_path(rt_band band) { + + assert(NULL != band); + + + if (!band->offline) { + RASTER_DEBUG(3, "rt_band_get_ext_path: Band is not offline"); + return NULL; + } + return band->data.offline.path; +} + +rt_errorstate +rt_band_get_ext_band_num(rt_band band, uint8_t *bandnum) { + assert(NULL != band); + assert(NULL != bandnum); + + *bandnum = 0; + + if (!band->offline) { + RASTER_DEBUG(3, "rt_band_get_ext_band_num: Band is not offline"); + return ES_ERROR; + } + + *bandnum = band->data.offline.bandNum; + + return ES_NONE; +} + +/** + * Get pointer to raster band data + * + * @param band : the band who's data to get + * + * @return pointer to band data or NULL if error + */ +void * +rt_band_get_data(rt_band band) { + assert(NULL != band); + + if (band->offline) { + if (band->data.offline.mem != NULL) + return band->data.offline.mem; + + if (rt_band_load_offline_data(band) != ES_NONE) + return NULL; + else + return band->data.offline.mem; + } + else + return band->data.mem; +} + +/** + * Load offline band's data. Loaded data is internally owned + * and should not be released by the caller. Data will be + * released when band is destroyed with rt_band_destroy(). + * + * @param band : the band who's data to get + * + * @return ES_NONE if success, ES_ERROR if failure + */ +rt_errorstate +rt_band_load_offline_data(rt_band band) { + GDALDatasetH hdsSrc = NULL; + int nband = 0; + VRTDatasetH hdsDst = NULL; + VRTSourcedRasterBandH hbandDst = NULL; + double gt[6] = {0.}; + double ogt[6] = {0}; + double offset[2] = {0}; + + rt_raster _rast = NULL; + rt_band _band = NULL; + int aligned = 0; + int err = ES_NONE; + + assert(band != NULL); + assert(band->raster != NULL); + + if (!band->offline) { + rterror("rt_band_load_offline_data: Band is not offline"); + return ES_ERROR; + } + else if (!strlen(band->data.offline.path)) { + rterror("rt_band_load_offline_data: Offline band does not a have a specified file"); + return ES_ERROR; + } + + rt_util_gdal_register_all(); + hdsSrc = GDALOpenShared(band->data.offline.path, GA_ReadOnly); + if (hdsSrc == NULL) { + rterror("rt_band_load_offline_data: Cannot open offline raster: %s", band->data.offline.path); + return ES_ERROR; + } + + /* # of bands */ + nband = GDALGetRasterCount(hdsSrc); + if (!nband) { + rterror("rt_band_load_offline_data: No bands found in offline raster: %s", band->data.offline.path); + GDALClose(hdsSrc); + return ES_ERROR; + } + /* bandNum is 0-based */ + else if (band->data.offline.bandNum + 1 > nband) { + rterror("rt_band_load_offline_data: Specified band %d not found in offline raster: %s", band->data.offline.bandNum, band->data.offline.path); + GDALClose(hdsSrc); + return ES_ERROR; + } + + /* get raster's geotransform */ + rt_raster_get_geotransform_matrix(band->raster, gt); + RASTER_DEBUGF(3, "Raster geotransform (%f, %f, %f, %f, %f, %f)", + gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); + + /* get offline raster's geotransform */ + if (GDALGetGeoTransform(hdsSrc, ogt) != CE_None) { + RASTER_DEBUG(4, "Using default geotransform matrix (0, 1, 0, 0, 0, -1)"); + ogt[0] = 0; + ogt[1] = 1; + ogt[2] = 0; + ogt[3] = 0; + ogt[4] = 0; + ogt[5] = -1; + } + RASTER_DEBUGF(3, "Offline geotransform (%f, %f, %f, %f, %f, %f)", + ogt[0], ogt[1], ogt[2], ogt[3], ogt[4], ogt[5]); + + /* are rasters aligned? */ + _rast = rt_raster_new(1, 1); + rt_raster_set_geotransform_matrix(_rast, ogt); + rt_raster_set_srid(_rast, band->raster->srid); + err = rt_raster_same_alignment(band->raster, _rast, &aligned, NULL); + rt_raster_destroy(_rast); + + if (err != ES_NONE) { + rterror("rt_band_load_offline_data: Could not test alignment of in-db representation of out-db raster"); + GDALClose(hdsSrc); + return ES_ERROR; + } + else if (!aligned) { + rtwarn("The in-db representation of the out-db raster is not aligned. Band data may be incorrect"); + } + + /* get offsets */ + rt_raster_geopoint_to_cell( + band->raster, + ogt[0], ogt[3], + &(offset[0]), &(offset[1]), + NULL + ); + + RASTER_DEBUGF(4, "offsets: (%f, %f)", offset[0], offset[1]); + + /* create VRT dataset */ + hdsDst = VRTCreate(band->width, band->height); + GDALSetGeoTransform(hdsDst, gt); + /* + GDALSetDescription(hdsDst, "/tmp/offline.vrt"); + */ + + /* add band as simple sources */ + GDALAddBand(hdsDst, rt_util_pixtype_to_gdal_datatype(band->pixtype), NULL); + hbandDst = (VRTSourcedRasterBandH) GDALGetRasterBand(hdsDst, 1); + + if (band->hasnodata) + GDALSetRasterNoDataValue(hbandDst, band->nodataval); + + VRTAddSimpleSource( + hbandDst, GDALGetRasterBand(hdsSrc, band->data.offline.bandNum + 1), + abs(offset[0]), abs(offset[1]), + band->width, band->height, + 0, 0, + band->width, band->height, + "near", VRT_NODATA_UNSET + ); + + /* make sure VRT reflects all changes */ + VRTFlushCache(hdsDst); + + /* convert VRT dataset to rt_raster */ + _rast = rt_raster_from_gdal_dataset(hdsDst); + + GDALClose(hdsDst); + /* XXX: need to find a way to clean up the GDALOpenShared datasets at end of transaction */ + /* GDALClose(hdsSrc); */ + + if (_rast == NULL) { + rterror("rt_band_load_offline_data: Cannot load data from offline raster: %s", band->data.offline.path); + return ES_ERROR; + } + + _band = rt_raster_get_band(_rast, 0); + if (_band == NULL) { + rterror("rt_band_load_offline_data: Cannot load data from offline raster: %s", band->data.offline.path); + rt_raster_destroy(_rast); + return ES_ERROR; + } + + /* band->data.offline.mem not NULL, free first */ + if (band->data.offline.mem != NULL) { + rtdealloc(band->data.offline.mem); + band->data.offline.mem = NULL; + } + + band->data.offline.mem = _band->data.mem; + + rtdealloc(_band); /* cannot use rt_band_destroy */ + rt_raster_destroy(_rast); + + return ES_NONE; +} + +rt_pixtype +rt_band_get_pixtype(rt_band band) { + + assert(NULL != band); + + + return band->pixtype; +} + +uint16_t +rt_band_get_width(rt_band band) { + + assert(NULL != band); + + + return band->width; +} + +uint16_t +rt_band_get_height(rt_band band) { + + assert(NULL != band); + + + return band->height; +} + +/* Get ownsdata flag */ +int +rt_band_get_ownsdata_flag(rt_band band) { + assert(NULL != band); + + return band->ownsdata ? 1 : 0; +} + +/* set ownsdata flag */ +void +rt_band_set_ownsdata_flag(rt_band band, int flag) { + assert(NULL != band); + + band->ownsdata = flag ? 1 : 0; +} + +int +rt_band_get_hasnodata_flag(rt_band band) { + assert(NULL != band); + + return band->hasnodata ? 1 : 0; +} + +void +rt_band_set_hasnodata_flag(rt_band band, int flag) { + + assert(NULL != band); + + band->hasnodata = (flag) ? 1 : 0; + + /* isnodata depends on hasnodata */ + if (!band->hasnodata && band->isnodata) { + RASTER_DEBUG(3, "Setting isnodata to FALSE as band no longer has NODATA"); + band->isnodata = 0; + } +} + +rt_errorstate +rt_band_set_isnodata_flag(rt_band band, int flag) { + assert(NULL != band); + + if (!band->hasnodata) { + /* silently permit setting isnodata flag to FALSE */ + if (!flag) + band->isnodata = 0; + else { + rterror("rt_band_set_isnodata_flag: Cannot set isnodata flag as band has no NODATA"); + return ES_ERROR; + } + } + else + band->isnodata = (flag) ? 1 : 0; + + return ES_NONE; +} + +int +rt_band_get_isnodata_flag(rt_band band) { + assert(NULL != band); + + if (band->hasnodata) + return band->isnodata ? 1 : 0; + else + return 0; +} + +/** + * Set nodata value + * + * @param band : the band to set nodata value to + * @param val : the nodata value + * @param converted : if non-zero, value was truncated/clamped/coverted + * + * @return ES_NONE or ES_ERROR + */ +rt_errorstate +rt_band_set_nodata(rt_band band, double val, int *converted) { + rt_pixtype pixtype = PT_END; + int32_t checkvalint = 0; + uint32_t checkvaluint = 0; + float checkvalfloat = 0; + double checkvaldouble = 0; + + assert(NULL != band); + + if (converted != NULL) + *converted = 0; + + pixtype = band->pixtype; + + RASTER_DEBUGF(3, "rt_band_set_nodata: setting nodata value %g with band type %s", val, rt_pixtype_name(pixtype)); + + /* return -1 on out of range */ + switch (pixtype) { + case PT_1BB: { + band->nodataval = rt_util_clamp_to_1BB(val); + checkvalint = band->nodataval; + break; + } + case PT_2BUI: { + band->nodataval = rt_util_clamp_to_2BUI(val); + checkvalint = band->nodataval; + break; + } + case PT_4BUI: { + band->nodataval = rt_util_clamp_to_4BUI(val); + checkvalint = band->nodataval; + break; + } + case PT_8BSI: { + band->nodataval = rt_util_clamp_to_8BSI(val); + checkvalint = band->nodataval; + break; + } + case PT_8BUI: { + band->nodataval = rt_util_clamp_to_8BUI(val); + checkvalint = band->nodataval; + break; + } + case PT_16BSI: { + band->nodataval = rt_util_clamp_to_16BSI(val); + checkvalint = band->nodataval; + break; + } + case PT_16BUI: { + band->nodataval = rt_util_clamp_to_16BUI(val); + checkvalint = band->nodataval; + break; + } + case PT_32BSI: { + band->nodataval = rt_util_clamp_to_32BSI(val); + checkvalint = band->nodataval; + break; + } + case PT_32BUI: { + band->nodataval = rt_util_clamp_to_32BUI(val); + checkvaluint = band->nodataval; + break; + } + case PT_32BF: { + band->nodataval = rt_util_clamp_to_32F(val); + checkvalfloat = band->nodataval; + break; + } + case PT_64BF: { + band->nodataval = val; + checkvaldouble = band->nodataval; + break; + } + default: { + rterror("rt_band_set_nodata: Unknown pixeltype %d", pixtype); + band->hasnodata = 0; + return ES_ERROR; + } + } + + RASTER_DEBUGF(3, "rt_band_set_nodata: band->hasnodata = %d", band->hasnodata); + RASTER_DEBUGF(3, "rt_band_set_nodata: band->nodataval = %f", band->nodataval); + /* the nodata value was just set, so this band has NODATA */ + band->hasnodata = 1; + + /* also set isnodata flag to false */ + band->isnodata = 0; + + if (rt_util_dbl_trunc_warning( + val, + checkvalint, checkvaluint, + checkvalfloat, checkvaldouble, + pixtype + ) && converted != NULL) { + *converted = 1; + } + + return ES_NONE; +} + +/** + * Set values of multiple pixels. Unlike rt_band_set_pixel, + * values in vals are expected to be of the band's pixel type + * as this function uses memcpy. + * + * It is important to be careful when using this function as + * the number of values being set may exceed a pixel "row". + * Remember that the band values are stored in a stream (1-D array) + * regardless of what the raster's width and height might be. + * So, setting a number of values may cross multiple pixel "rows". + * + * @param band : the band to set value to + * @param x : X coordinate (0-based) + * @param y : Y coordinate (0-based) + * @param vals : the pixel values to apply + * @param len : # of elements in vals + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate +rt_band_set_pixel_line( + rt_band band, + int x, int y, + void *vals, uint32_t len +) { + rt_pixtype pixtype = PT_END; + int size = 0; + uint8_t *data = NULL; + uint32_t offset = 0; + + assert(NULL != band); + assert(vals != NULL && len > 0); + + RASTER_DEBUGF(3, "length of values = %d", len); + + if (band->offline) { + rterror("rt_band_set_pixel_line not implemented yet for OFFDB bands"); + return ES_ERROR; + } + + pixtype = band->pixtype; + size = rt_pixtype_size(pixtype); + + if ( + x < 0 || x >= band->width || + y < 0 || y >= band->height + ) { + rterror("rt_band_set_pixel_line: Coordinates out of range (%d, %d) vs (%d, %d)", x, y, band->width, band->height); + return ES_ERROR; + } + + data = rt_band_get_data(band); + offset = x + (y * band->width); + RASTER_DEBUGF(4, "offset = %d", offset); + + /* make sure len of values to copy don't exceed end of data */ + if (len > (band->width * band->height) - offset) { + rterror("rt_band_set_pixel_line: Could not apply pixels as values length exceeds end of data"); + return ES_ERROR; + } + + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: + case PT_8BSI: { + uint8_t *ptr = data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_16BUI: { + uint16_t *ptr = (uint16_t *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_16BSI: { + int16_t *ptr = (int16_t *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_32BUI: { + uint32_t *ptr = (uint32_t *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_32BSI: { + int32_t *ptr = (int32_t *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_32BF: { + float *ptr = (float *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + case PT_64BF: { + double *ptr = (double *) data; + ptr += offset; + memcpy(ptr, vals, size * len); + break; + } + default: { + rterror("rt_band_set_pixel_line: Unknown pixeltype %d", pixtype); + return ES_ERROR; + } + } + +#if POSTGIS_DEBUG_LEVEL > 0 + { + double value; + rt_band_get_pixel(band, x, y, &value, NULL); + RASTER_DEBUGF(4, "pixel at (%d, %d) = %f", x, y, value); + } +#endif + + /* set band's isnodata flag to FALSE */ + if (rt_band_get_hasnodata_flag(band)) + rt_band_set_isnodata_flag(band, 0); + + return ES_NONE; +} + +/** + * Set single pixel's value + * + * @param band : the band to set value to + * @param x : x ordinate (0-based) + * @param y : y ordinate (0-based) + * @param val : the pixel value + * @param converted : (optional) non-zero if value truncated/clamped/converted + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate +rt_band_set_pixel( + rt_band band, + int x, int y, + double val, + int *converted +) { + rt_pixtype pixtype = PT_END; + unsigned char* data = NULL; + uint32_t offset = 0; + + int32_t checkvalint = 0; + uint32_t checkvaluint = 0; + float checkvalfloat = 0; + double checkvaldouble = 0; + + assert(NULL != band); + + if (converted != NULL) + *converted = 0; + + if (band->offline) { + rterror("rt_band_set_pixel not implemented yet for OFFDB bands"); + return ES_ERROR; + } + + pixtype = band->pixtype; + + if ( + x < 0 || x >= band->width || + y < 0 || y >= band->height + ) { + rterror("rt_band_set_pixel: Coordinates out of range"); + return ES_ERROR; + } + + /* check that clamped value isn't clamped NODATA */ + if (band->hasnodata && pixtype != PT_64BF) { + double newval; + int corrected; + + rt_band_corrected_clamped_value(band, val, &newval, &corrected); + + if (corrected) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value for pixel %d x %d has been corrected as clamped value becomes NODATA", x, y); +#endif + val = newval; + + if (converted != NULL) + *converted = 1; + } + } + + data = rt_band_get_data(band); + offset = x + (y * band->width); + + switch (pixtype) { + case PT_1BB: { + data[offset] = rt_util_clamp_to_1BB(val); + checkvalint = data[offset]; + break; + } + case PT_2BUI: { + data[offset] = rt_util_clamp_to_2BUI(val); + checkvalint = data[offset]; + break; + } + case PT_4BUI: { + data[offset] = rt_util_clamp_to_4BUI(val); + checkvalint = data[offset]; + break; + } + case PT_8BSI: { + data[offset] = rt_util_clamp_to_8BSI(val); + checkvalint = (int8_t) data[offset]; + break; + } + case PT_8BUI: { + data[offset] = rt_util_clamp_to_8BUI(val); + checkvalint = data[offset]; + break; + } + case PT_16BSI: { + int16_t *ptr = (int16_t*) data; /* we assume correct alignment */ + ptr[offset] = rt_util_clamp_to_16BSI(val); + checkvalint = (int16_t) ptr[offset]; + break; + } + case PT_16BUI: { + uint16_t *ptr = (uint16_t*) data; /* we assume correct alignment */ + ptr[offset] = rt_util_clamp_to_16BUI(val); + checkvalint = ptr[offset]; + break; + } + case PT_32BSI: { + int32_t *ptr = (int32_t*) data; /* we assume correct alignment */ + ptr[offset] = rt_util_clamp_to_32BSI(val); + checkvalint = (int32_t) ptr[offset]; + break; + } + case PT_32BUI: { + uint32_t *ptr = (uint32_t*) data; /* we assume correct alignment */ + ptr[offset] = rt_util_clamp_to_32BUI(val); + checkvaluint = ptr[offset]; + break; + } + case PT_32BF: { + float *ptr = (float*) data; /* we assume correct alignment */ + ptr[offset] = rt_util_clamp_to_32F(val); + checkvalfloat = ptr[offset]; + break; + } + case PT_64BF: { + double *ptr = (double*) data; /* we assume correct alignment */ + ptr[offset] = val; + checkvaldouble = ptr[offset]; + break; + } + default: { + rterror("rt_band_set_pixel: Unknown pixeltype %d", pixtype); + return ES_ERROR; + } + } + + /* If the stored value is not NODATA, reset the isnodata flag */ + if (!rt_band_clamped_value_is_nodata(band, val)) { + RASTER_DEBUG(3, "Band has a value that is not NODATA. Setting isnodata to FALSE"); + band->isnodata = FALSE; + } + + /* Overflow checking */ + if (rt_util_dbl_trunc_warning( + val, + checkvalint, checkvaluint, + checkvalfloat, checkvaldouble, + pixtype + ) && converted != NULL) { + *converted = 1; + } + + return ES_NONE; +} + +/** + * Get values of multiple pixels. Unlike rt_band_get_pixel, + * values in vals are of the band's pixel type so cannot be + * assumed to be double. Function uses memcpy. + * + * It is important to be careful when using this function as + * the number of values being fetched may exceed a pixel "row". + * Remember that the band values are stored in a stream (1-D array) + * regardless of what the raster's width and height might be. + * So, getting a number of values may cross multiple pixel "rows". + * + * @param band : the band to get pixel value from + * @param x : pixel column (0-based) + * @param y : pixel row (0-based) + * @param len : the number of pixels to get + * @param **vals : the pixel values + * @param *nvals : the number of pixel values being returned + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate rt_band_get_pixel_line( + rt_band band, + int x, int y, + uint16_t len, + void **vals, uint16_t *nvals +) { + uint8_t *_vals = NULL; + int pixsize = 0; + uint8_t *data = NULL; + uint32_t offset = 0; + uint16_t _nvals = 0; + int maxlen = 0; + uint8_t *ptr = NULL; + + assert(NULL != band); + assert(vals != NULL && nvals != NULL); + + /* initialize to no values */ + *nvals = 0; + + if ( + x < 0 || x >= band->width || + y < 0 || y >= band->height + ) { + rtwarn("Attempting to get pixel values with out of range raster coordinates: (%d, %d)", x, y); + return ES_ERROR; + } + + if (len < 1) + return ES_NONE; + + data = rt_band_get_data(band); + if (data == NULL) { + rterror("rt_band_get_pixel_line: Cannot get band data"); + return ES_ERROR; + } + + /* +1 for the nodata value */ + offset = x + (y * band->width); + RASTER_DEBUGF(4, "offset = %d", offset); + + pixsize = rt_pixtype_size(band->pixtype); + RASTER_DEBUGF(4, "pixsize = %d", pixsize); + + /* cap _nvals so that it doesn't overflow */ + _nvals = len; + maxlen = band->width * band->height; + + if (((int) (offset + _nvals)) > maxlen) { + _nvals = maxlen - offset; + rtwarn("Limiting returning number values to %d", _nvals); + } + RASTER_DEBUGF(4, "_nvals = %d", _nvals); + + ptr = data + (offset * pixsize); + + _vals = rtalloc(_nvals * pixsize); + if (_vals == NULL) { + rterror("rt_band_get_pixel_line: Could not allocate memory for pixel values"); + return ES_ERROR; + } + + /* copy pixels */ + memcpy(_vals, ptr, _nvals * pixsize); + + *vals = _vals; + *nvals = _nvals; + + return ES_NONE; +} + +/** + * Get pixel value. If band's isnodata flag is TRUE, value returned + * will be the band's NODATA value + * + * @param band : the band to set nodata value to + * @param x : x ordinate (0-based) + * @param y : x ordinate (0-based) + * @param *value : pixel value + * @param *nodata : 0 if pixel is not NODATA + * + * @return 0 on success, -1 on error (value out of valid range). + */ +rt_errorstate +rt_band_get_pixel( + rt_band band, + int x, int y, + double *value, + int *nodata +) { + rt_pixtype pixtype = PT_END; + uint8_t* data = NULL; + uint32_t offset = 0; + + assert(NULL != band); + assert(NULL != value); + + /* set nodata to 0 */ + if (nodata != NULL) + *nodata = 0; + + if ( + x < 0 || x >= band->width || + y < 0 || y >= band->height + ) { + rtwarn("Attempting to get pixel value with out of range raster coordinates: (%d, %d)", x, y); + return ES_ERROR; + } + + /* band is NODATA */ + if (band->isnodata) { + RASTER_DEBUG(3, "Band's isnodata flag is TRUE. Returning NODATA value"); + *value = band->nodataval; + if (nodata != NULL) *nodata = 1; + return ES_NONE; + } + + data = rt_band_get_data(band); + if (data == NULL) { + rterror("rt_band_get_pixel: Cannot get band data"); + return ES_ERROR; + } + + /* +1 for the nodata value */ + offset = x + (y * band->width); + + pixtype = band->pixtype; + + switch (pixtype) { + case PT_1BB: +#ifdef OPTIMIZE_SPACE + { + int byteOffset = offset / 8; + int bitOffset = offset % 8; + data += byteOffset; + + /* Bit to set is bitOffset into data */ + *value = getBits(data, val, 1, bitOffset); + break; + } +#endif + case PT_2BUI: +#ifdef OPTIMIZE_SPACE + { + int byteOffset = offset / 4; + int bitOffset = offset % 4; + data += byteOffset; + + /* Bits to set start at bitOffset into data */ + *value = getBits(data, val, 2, bitOffset); + break; + } +#endif + case PT_4BUI: +#ifdef OPTIMIZE_SPACE + { + int byteOffset = offset / 2; + int bitOffset = offset % 2; + data += byteOffset; + + /* Bits to set start at bitOffset into data */ + *value = getBits(data, val, 2, bitOffset); + break; + } +#endif + case PT_8BSI: { + int8_t val = data[offset]; + *value = val; + break; + } + case PT_8BUI: { + uint8_t val = data[offset]; + *value = val; + break; + } + case PT_16BSI: { + int16_t *ptr = (int16_t*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + case PT_16BUI: { + uint16_t *ptr = (uint16_t*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + case PT_32BSI: { + int32_t *ptr = (int32_t*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + case PT_32BUI: { + uint32_t *ptr = (uint32_t*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + case PT_32BF: { + float *ptr = (float*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + case PT_64BF: { + double *ptr = (double*) data; /* we assume correct alignment */ + *value = ptr[offset]; + break; + } + default: { + rterror("rt_band_get_pixel: Unknown pixeltype %d", pixtype); + return ES_ERROR; + } + } + + /* set NODATA flag */ + if (band->hasnodata && nodata != NULL) { + if (rt_band_clamped_value_is_nodata(band, *value)) + *nodata = 1; + } + + return ES_NONE; +} + +/** + * Get nearest pixel(s) with value (not NODATA) to specified pixel + * + * @param band : the band to get nearest pixel(s) from + * @param x : the column of the pixel (0-based) + * @param y : the line of the pixel (0-based) + * @param distancex : the number of pixels around the specified pixel + * along the X axis + * @param distancey : the number of pixels around the specified pixel + * along the Y axis + * @param exclude_nodata_value : if non-zero, ignore nodata values + * to check for pixels with value + * @param npixels : return set of rt_pixel object or NULL + * + * @return -1 on error, otherwise the number of rt_pixel objects + * in npixels + */ +int rt_band_get_nearest_pixel( + rt_band band, + int x, int y, + uint16_t distancex, uint16_t distancey, + int exclude_nodata_value, + rt_pixel *npixels +) { + rt_pixel npixel = NULL; + int extent[4] = {0}; + int max_extent[4] = {0}; + int d0 = 0; + int distance[2] = {0}; + uint32_t _d[2] = {0}; + uint32_t i = 0; + uint32_t j = 0; + uint32_t k = 0; + int _max = 0; + int _x = 0; + int _y = 0; + int *_min = NULL; + double pixval = 0; + double minval = 0; + uint32_t count = 0; + int isnodata = 0; + + int inextent = 0; + + assert(NULL != band); + assert(NULL != npixels); + + RASTER_DEBUG(3, "Starting"); + + /* process distance */ + distance[0] = distancex; + distance[1] = distancey; + + /* no distance, means get nearest pixels and return */ + if (!distance[0] && !distance[1]) + d0 = 1; + + RASTER_DEBUGF(4, "Selected pixel: %d x %d", x, y); + RASTER_DEBUGF(4, "Distances: %d x %d", distance[0], distance[1]); + + /* shortcuts if outside band extent */ + if ( + exclude_nodata_value && ( + (x < 0 || x > band->width) || + (y < 0 || y > band->height) + ) + ) { + /* no distances specified, jump to pixel close to extent */ + if (d0) { + if (x < 0) + x = -1; + else if (x > band->width) + x = band->width; + + if (y < 0) + y = -1; + else if (y > band->height) + y = band->height; + + RASTER_DEBUGF(4, "Moved selected pixel: %d x %d", x, y); + } + /* + distances specified + if distances won't capture extent of band, return 0 + */ + else if ( + ((x < 0 && abs(x) > distance[0]) || (x - band->width >= distance[0])) || + ((y < 0 && abs(y) > distance[1]) || (y - band->height >= distance[1])) + ) { + RASTER_DEBUG(4, "No nearest pixels possible for provided pixel and distances"); + return 0; + } + } + + /* no NODATA, exclude is FALSE */ + if (!band->hasnodata) + exclude_nodata_value = FALSE; + /* band is NODATA and excluding NODATA */ + else if (exclude_nodata_value && band->isnodata) { + RASTER_DEBUG(4, "No nearest pixels possible as band is NODATA and excluding NODATA values"); + return 0; + } + + /* determine the maximum distance to prevent an infinite loop */ + if (d0) { + int a, b; + + /* X axis */ + a = abs(x); + b = abs(x - band->width); + + if (a > b) + distance[0] = a; + else + distance[0] = b; + + /* Y axis */ + a = abs(y); + b = abs(y - band->height); + if (a > b) + distance[1] = a; + else + distance[1] = b; + + RASTER_DEBUGF(4, "Maximum distances: %d x %d", distance[0], distance[1]); + } + + /* minimum possible value for pixel type */ + minval = rt_pixtype_get_min_value(band->pixtype); + RASTER_DEBUGF(4, "pixtype: %s", rt_pixtype_name(band->pixtype)); + RASTER_DEBUGF(4, "minval: %f", minval); + + /* set variables */ + count = 0; + *npixels = NULL; + + /* maximum extent */ + max_extent[0] = x - distance[0]; /* min X */ + max_extent[1] = y - distance[1]; /* min Y */ + max_extent[2] = x + distance[0]; /* max X */ + max_extent[3] = y + distance[1]; /* max Y */ + RASTER_DEBUGF(4, "Maximum Extent: (%d, %d, %d, %d)", + max_extent[0], max_extent[1], max_extent[2], max_extent[3]); + + _d[0] = 0; + _d[1] = 0; + do { + _d[0]++; + _d[1]++; + + extent[0] = x - _d[0]; /* min x */ + extent[1] = y - _d[1]; /* min y */ + extent[2] = x + _d[0]; /* max x */ + extent[3] = y + _d[1]; /* max y */ + + RASTER_DEBUGF(4, "Processing distances: %d x %d", _d[0], _d[1]); + RASTER_DEBUGF(4, "Extent: (%d, %d, %d, %d)", + extent[0], extent[1], extent[2], extent[3]); + + for (i = 0; i < 2; i++) { + + /* by row */ + if (i < 1) + _max = extent[2] - extent[0] + 1; + /* by column */ + else + _max = extent[3] - extent[1] + 1; + _max = abs(_max); + + for (j = 0; j < 2; j++) { + /* by row */ + if (i < 1) { + _x = extent[0]; + _min = &_x; + + /* top row */ + if (j < 1) + _y = extent[1]; + /* bottom row */ + else + _y = extent[3]; + } + /* by column */ + else { + _y = extent[1] + 1; + _min = &_y; + + /* left column */ + if (j < 1) { + _x = extent[0]; + _max -= 2; + } + /* right column */ + else + _x = extent[2]; + } + + RASTER_DEBUGF(4, "_min, _max: %d, %d", *_min, _max); + for (k = 0; k < _max; k++) { + /* check that _x and _y are not outside max extent */ + if ( + _x < max_extent[0] || _x > max_extent[2] || + _y < max_extent[1] || _y > max_extent[3] + ) { + (*_min)++; + continue; + } + + /* outside band extent, set to NODATA */ + if ( + (_x < 0 || _x >= band->width) || + (_y < 0 || _y >= band->height) + ) { + /* no NODATA, set to minimum possible value */ + if (!band->hasnodata) + pixval = minval; + /* has NODATA, use NODATA */ + else + pixval = band->nodataval; + RASTER_DEBUGF(4, "NODATA pixel outside band extent: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); + inextent = 0; + isnodata = 1; + } + else { + if (rt_band_get_pixel( + band, + _x, _y, + &pixval, + &isnodata + ) != ES_NONE) { + rterror("rt_band_get_nearest_pixel: Could not get pixel value"); + if (count) rtdealloc(*npixels); + return -1; + } + RASTER_DEBUGF(4, "Pixel: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); + inextent = 1; + } + + /* use pixval? */ + if (!exclude_nodata_value || (exclude_nodata_value && !isnodata)) { + /* add pixel to result set */ + RASTER_DEBUGF(4, "Adding pixel to set of nearest pixels: (x, y, val) = (%d, %d, %f)", _x, _y, pixval); + count++; + + if (*npixels == NULL) + *npixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t) * count); + else + *npixels = (rt_pixel) rtrealloc(*npixels, sizeof(struct rt_pixel_t) * count); + if (*npixels == NULL) { + rterror("rt_band_get_nearest_pixel: Could not allocate memory for nearest pixel(s)"); + return -1; + } + + npixel = &((*npixels)[count - 1]); + npixel->x = _x; + npixel->y = _y; + npixel->value = pixval; + + /* special case for when outside band extent */ + if (!inextent && !band->hasnodata) + npixel->nodata = 1; + else + npixel->nodata = 0; + } + + (*_min)++; + } + } + } + + /* distance threshholds met */ + if (_d[0] >= distance[0] && _d[1] >= distance[1]) + break; + else if (d0 && count) + break; + } + while (1); + + RASTER_DEBUGF(3, "Nearest pixels in return: %d", count); + + return count; +} + +/** + * Search band for pixel(s) with search values + * + * @param band : the band to query for minimum and maximum pixel values + * @param exclude_nodata_value : if non-zero, ignore nodata values + * @param searchset : array of values to count + * @param searchcount : the number of search values + * @param pixels : pixels with the search value + * + * @return -1 on error, otherwise number of pixels + */ +int +rt_band_get_pixel_of_value( + rt_band band, int exclude_nodata_value, + double *searchset, int searchcount, + rt_pixel *pixels +) { + int x; + int y; + int i; + double pixval; + int err; + int count = 0; + int isnodata = 0; + int isequal = 0; + + rt_pixel pixel = NULL; + + assert(NULL != band); + assert(NULL != pixels); + assert(NULL != searchset && searchcount > 0); + + if (!band->hasnodata) + exclude_nodata_value = FALSE; + /* band is NODATA and exclude_nodata_value = TRUE, nothing to search */ + else if (exclude_nodata_value && band->isnodata) { + RASTER_DEBUG(4, "Pixels cannot be searched as band is NODATA and excluding NODATA values"); + return 0; + } + + for (x = 0; x < band->width; x++) { + for (y = 0; y < band->height; y++) { + err = rt_band_get_pixel(band, x, y, &pixval, &isnodata); + if (err != ES_NONE) { + rterror("rt_band_get_pixel_of_value: Cannot get band pixel"); + return -1; + } + else if (exclude_nodata_value && isnodata) + continue; + + for (i = 0; i < searchcount; i++) { + if (rt_pixtype_compare_clamped_values(band->pixtype, searchset[i], pixval, &isequal) != ES_NONE) { + continue; + } + + if (FLT_NEQ(pixval, searchset[i]) || !isequal) + continue; + + /* match found */ + count++; + if (*pixels == NULL) + *pixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t) * count); + else + *pixels = (rt_pixel) rtrealloc(*pixels, sizeof(struct rt_pixel_t) * count); + if (*pixels == NULL) { + rterror("rt_band_get_pixel_of_value: Could not allocate memory for pixel(s)"); + return -1; + } + + pixel = &((*pixels)[count - 1]); + pixel->x = x; + pixel->y = y; + pixel->nodata = 0; + pixel->value = pixval; + } + } + } + + return count; +} + +/** + * Get NODATA value + * + * @param band : the band whose NODATA value will be returned + * @param nodata : the band's NODATA value + * + * @return ES_NONE or ES_ERROR + */ +rt_errorstate +rt_band_get_nodata(rt_band band, double *nodata) { + assert(NULL != band); + assert(NULL != nodata); + + *nodata = band->nodataval; + + if (!band->hasnodata) { + rterror("rt_band_get_nodata: Band has no NODATA value"); + return ES_ERROR; + } + + return ES_NONE; +} + +double +rt_band_get_min_value(rt_band band) { + assert(NULL != band); + + return rt_pixtype_get_min_value(band->pixtype); +} + +int +rt_band_check_is_nodata(rt_band band) { + int i, j, err; + double pxValue; + int isnodata = 0; + + assert(NULL != band); + + /* Check if band has nodata value */ + if (!band->hasnodata) { + RASTER_DEBUG(3, "Band has no NODATA value"); + band->isnodata = FALSE; + return FALSE; + } + + pxValue = band->nodataval; + + /* Check all pixels */ + for (i = 0; i < band->width; i++) { + for (j = 0; j < band->height; j++) { + err = rt_band_get_pixel(band, i, j, &pxValue, &isnodata); + if (err != ES_NONE) { + rterror("rt_band_check_is_nodata: Cannot get band pixel"); + return FALSE; + } + else if (!isnodata) { + band->isnodata = FALSE; + return FALSE; + } + } + } + + band->isnodata = TRUE; + return TRUE; +} + +/** + * Compare clamped value to band's clamped NODATA value. + * + * @param band : the band whose NODATA value will be used for comparison + * @param val : the value to compare to the NODATA value + * + * @return 2 if unclamped value is unclamped NODATA + * 1 if clamped value is clamped NODATA + * 0 if clamped value is NOT clamped NODATA + */ +int +rt_band_clamped_value_is_nodata(rt_band band, double val) { + int isequal = 0; + + assert(NULL != band); + + /* no NODATA, so never equal */ + if (!band->hasnodata) + return 0; + + /* value is exactly NODATA */ + if (FLT_EQ(val, band->nodataval)) + return 2; + + /* ignore error from rt_pixtype_compare_clamped_values */ + rt_pixtype_compare_clamped_values( + band->pixtype, + val, band->nodataval, + &isequal + ); + + return isequal ? 1 : 0; +} + +/** + * Correct value when clamped value is equal to clamped NODATA value. + * Correction does NOT occur if unclamped value is exactly unclamped + * NODATA value. + * + * @param band : the band whose NODATA value will be used for comparison + * @param val : the value to compare to the NODATA value and correct + * @param *newval : pointer to corrected value + * @param *corrected : (optional) non-zero if val was corrected + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_band_corrected_clamped_value( + rt_band band, + double val, + double *newval, int *corrected +) { + double minval = 0.; + + assert(NULL != band); + assert(NULL != newval); + + if (corrected != NULL) + *corrected = 0; + + /* no need to correct if clamped values IS NOT clamped NODATA */ + if (rt_band_clamped_value_is_nodata(band, val) != 1) { + *newval = val; + return ES_NONE; + } + + minval = rt_pixtype_get_min_value(band->pixtype); + *newval = val; + + switch (band->pixtype) { + case PT_1BB: + *newval = !band->nodataval; + break; + case PT_2BUI: + if (rt_util_clamp_to_2BUI(val) == rt_util_clamp_to_2BUI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_4BUI: + if (rt_util_clamp_to_4BUI(val) == rt_util_clamp_to_4BUI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_8BSI: + if (rt_util_clamp_to_8BSI(val) == rt_util_clamp_to_8BSI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_8BUI: + if (rt_util_clamp_to_8BUI(val) == rt_util_clamp_to_8BUI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_16BSI: + if (rt_util_clamp_to_16BSI(val) == rt_util_clamp_to_16BSI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_16BUI: + if (rt_util_clamp_to_16BUI(val) == rt_util_clamp_to_16BUI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_32BSI: + if (rt_util_clamp_to_32BSI(val) == rt_util_clamp_to_32BSI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_32BUI: + if (rt_util_clamp_to_32BUI(val) == rt_util_clamp_to_32BUI(minval)) + (*newval)++; + else + (*newval)--; + break; + case PT_32BF: + if (FLT_EQ(rt_util_clamp_to_32F(val), rt_util_clamp_to_32F(minval))) + *newval += FLT_EPSILON; + else + *newval -= FLT_EPSILON; + break; + case PT_64BF: + break; + default: + rterror("rt_band_corrected_clamped_value: Unknown pixeltype %d", band->pixtype); + return ES_ERROR; + } + + if (corrected != NULL) + *corrected = 1; + + return ES_NONE; +} + diff --git a/raster/rt_core/rt_context.c b/raster/rt_core/rt_context.c new file mode 100644 index 000000000..11862aafa --- /dev/null +++ b/raster/rt_core/rt_context.c @@ -0,0 +1,303 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include /* for va_list, va_start etc */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* rt_context +******************************************************************************/ + +/* Functions definitions */ +void * init_rt_allocator(size_t size); +void * init_rt_reallocator(void * mem, size_t size); +void init_rt_deallocator(void * mem); +void init_rt_errorreporter(const char * fmt, va_list ap); +void init_rt_warnreporter(const char * fmt, va_list ap); +void init_rt_inforeporter(const char * fmt, va_list ap); + +/* + * Default allocators + * + * We include some default allocators that use malloc/free/realloc + * along with stdout/stderr since this is the most common use case + * + */ +void * +default_rt_allocator(size_t size) +{ + void *mem = malloc(size); + return mem; +} + +void * +default_rt_reallocator(void *mem, size_t size) +{ + void *ret = realloc(mem, size); + return ret; +} + +void +default_rt_deallocator(void *mem) +{ + free(mem); +} + +void +default_rt_error_handler(const char *fmt, va_list ap) { + + static const char *label = "ERROR: "; + char newfmt[1024] = {0}; + snprintf(newfmt, 1024, "%s%s\n", label, fmt); + newfmt[1023] = '\0'; + + vprintf(newfmt, ap); + + va_end(ap); +} + +void +default_rt_warning_handler(const char *fmt, va_list ap) { + + static const char *label = "WARNING: "; + char newfmt[1024] = {0}; + snprintf(newfmt, 1024, "%s%s\n", label, fmt); + newfmt[1023] = '\0'; + + vprintf(newfmt, ap); + + va_end(ap); +} + +void +default_rt_info_handler(const char *fmt, va_list ap) { + + static const char *label = "INFO: "; + char newfmt[1024] = {0}; + snprintf(newfmt, 1024, "%s%s\n", label, fmt); + newfmt[1023] = '\0'; + + vprintf(newfmt, ap); + + va_end(ap); +} + +/** + * Struct definition here + */ +struct rt_context_t { + rt_allocator alloc; + rt_reallocator realloc; + rt_deallocator dealloc; + rt_message_handler err; + rt_message_handler warn; + rt_message_handler info; +}; + +/* Static variable, to be used for all rt_core functions */ +static struct rt_context_t ctx_t = { + .alloc = init_rt_allocator, + .realloc = init_rt_reallocator, + .dealloc = init_rt_deallocator, + .err = init_rt_errorreporter, + .warn = init_rt_warnreporter, + .info = init_rt_inforeporter +}; + + +/** + * This function is normally called by rt_init_allocators when no special memory + * management is needed. Useful in raster core testing and in the (future) + * loader, when we need to use raster core functions but we don't have + * PostgreSQL backend behind. We must take care of memory by ourselves in those + * situations + */ +void +rt_install_default_allocators(void) +{ + ctx_t.alloc = default_rt_allocator; + ctx_t.realloc = default_rt_reallocator; + ctx_t.dealloc = default_rt_deallocator; + ctx_t.err = default_rt_error_handler; + ctx_t.info = default_rt_info_handler; + ctx_t.warn = default_rt_warning_handler; +} + + +/** + * This function is called by rt_init_allocators when the PostgreSQL backend is + * taking care of the memory and we want to use palloc family + */ +void +rt_set_handlers(rt_allocator allocator, rt_reallocator reallocator, + rt_deallocator deallocator, rt_message_handler error_handler, + rt_message_handler info_handler, rt_message_handler warning_handler) { + + ctx_t.alloc = allocator; + ctx_t.realloc = reallocator; + ctx_t.dealloc = deallocator; + + ctx_t.err = error_handler; + ctx_t.info = info_handler; + ctx_t.warn = warning_handler; +} + +/** + * Initialisation allocators + * + * These are used the first time any of the allocators are called to enable + * executables/libraries that link into raster to be able to set up their own + * allocators. This is mainly useful for older PostgreSQL versions that don't + * have functions that are called upon startup. + **/ +void * +init_rt_allocator(size_t size) +{ + rt_init_allocators(); + + return ctx_t.alloc(size); +} + +void +init_rt_deallocator(void *mem) +{ + rt_init_allocators(); + + ctx_t.dealloc(mem); +} + + +void * +init_rt_reallocator(void *mem, size_t size) +{ + rt_init_allocators(); + + return ctx_t.realloc(mem, size); +} + +void +init_rt_inforeporter(const char *fmt, va_list ap) +{ + rt_init_allocators(); + + (*ctx_t.info)(fmt, ap); +} + +void +init_rt_warnreporter(const char *fmt, va_list ap) +{ + rt_init_allocators(); + + (*ctx_t.warn)(fmt, ap); +} + + +void +init_rt_errorreporter(const char *fmt, va_list ap) +{ + rt_init_allocators(); + + (*ctx_t.err)(fmt, ap); + +} + + + +/** + * Raster core memory management functions. + * + * They use the functions defined by the caller. + */ +void * +rtalloc(size_t size) { + void * mem = ctx_t.alloc(size); + RASTER_DEBUGF(5, "rtalloc called: %d@%p", size, mem); + return mem; +} + + +void * +rtrealloc(void * mem, size_t size) { + void * result = ctx_t.realloc(mem, size); + RASTER_DEBUGF(5, "rtrealloc called: %d@%p", size, result); + return result; +} + +void +rtdealloc(void * mem) { + ctx_t.dealloc(mem); + RASTER_DEBUG(5, "rtdealloc called"); +} + +/** + * Raster core error and info handlers + * + * Since variadic functions cannot pass their parameters directly, we need + * wrappers for these functions to convert the arguments into a va_list + * structure. + */ +void +rterror(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*ctx_t.err)(fmt, ap); + + va_end(ap); +} + +void +rtinfo(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*ctx_t.info)(fmt, ap); + + va_end(ap); +} + + +void +rtwarn(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*ctx_t.warn)(fmt, ap); + + va_end(ap); +} diff --git a/raster/rt_core/rt_geometry.c b/raster/rt_core/rt_geometry.c new file mode 100644 index 000000000..4991a3505 --- /dev/null +++ b/raster/rt_core/rt_geometry.c @@ -0,0 +1,1183 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* rt_raster_perimeter() +******************************************************************************/ + +static rt_errorstate +_rti_raster_get_band_perimeter(rt_band band, uint16_t *trim) { + uint16_t width = 0; + uint16_t height = 0; + int x = 0; + int y = 0; + int offset = 0; + int done[4] = {0}; + double value = 0; + int nodata = 0; + + assert(band != NULL); + assert(band->raster != NULL); + assert(trim != NULL); + + memset(trim, 0, sizeof(uint16_t) * 4); + + width = rt_band_get_width(band); + height = rt_band_get_height(band); + + /* top */ + for (y = 0; y < height; y++) { + for (offset = 0; offset < 3; offset++) { + /* every third pixel */ + for (x = offset; x < width; x += 3) { + if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { + rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); + return ES_ERROR; + } + + RASTER_DEBUGF(4, "top (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); + if (!nodata) { + trim[0] = y; + done[0] = 1; + break; + } + } + + if (done[0]) + break; + } + + if (done[0]) + break; + } + + /* right */ + for (x = width - 1; x >= 0; x--) { + for (offset = 0; offset < 3; offset++) { + /* every third pixel */ + for (y = offset; y < height; y += 3) { + if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { + rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); + return ES_ERROR; + } + + RASTER_DEBUGF(4, "right (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); + if (!nodata) { + trim[1] = width - (x + 1); + done[1] = 1; + break; + } + } + + if (done[1]) + break; + } + + if (done[1]) + break; + } + + /* bottom */ + for (y = height - 1; y >= 0; y--) { + for (offset = 0; offset < 3; offset++) { + /* every third pixel */ + for (x = offset; x < width; x += 3) { + if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { + rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); + return ES_ERROR; + } + + RASTER_DEBUGF(4, "bottom (x, y, value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); + if (!nodata) { + trim[2] = height - (y + 1); + done[2] = 1; + break; + } + } + + if (done[2]) + break; + } + + if (done[2]) + break; + } + + /* left */ + for (x = 0; x < width; x++) { + for (offset = 0; offset < 3; offset++) { + /* every third pixel */ + for (y = offset; y < height; y += 3) { + if (rt_band_get_pixel(band, x, y, &value, &nodata) != ES_NONE) { + rterror("_rti_raster_get_band_perimeter: Could not get band pixel"); + return ES_ERROR; + } + + RASTER_DEBUGF(4, "left (x, , value, nodata) = (%d, %d, %f, %d)", x, y, value, nodata); + if (!nodata) { + trim[3] = x; + done[3] = 1; + break; + } + } + + if (done[3]) + break; + } + + if (done[3]) + break; + } + + RASTER_DEBUGF(4, "trim = (%d, %d, %d, %d)", + trim[0], trim[1], trim[2], trim[3]); + + return ES_NONE; +} + +/** + * Get raster perimeter + * + * The perimeter is a 4 vertices (5 to be closed) + * single ring polygon bearing the raster's rotation and using + * projection coordinates. + * + * @param raster : the raster to get info from + * @param nband : the band for the perimeter. 0-based + * value less than zero means all bands + * @param **perimeter : pointer to perimeter + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_get_perimeter( + rt_raster raster, int nband, + LWGEOM **perimeter +) { + rt_band band = NULL; + int numband = 0; + uint16_t *_nband = NULL; + int i = 0; + int j = 0; + uint16_t _trim[4] = {0}; + uint16_t trim[4] = {0}; /* top, right, bottom, left */ + int isset[4] = {0}; + double gt[6] = {0.0}; + int srid = SRID_UNKNOWN; + + POINTARRAY *pts = NULL; + POINT4D p4d; + POINTARRAY **rings = NULL; + LWPOLY* poly = NULL; + + assert(perimeter != NULL); + + *perimeter = NULL; + + /* empty raster, no perimeter */ + if (rt_raster_is_empty(raster)) + return ES_NONE; + + /* raster metadata */ + srid = rt_raster_get_srid(raster); + rt_raster_get_geotransform_matrix(raster, gt); + numband = rt_raster_get_num_bands(raster); + + RASTER_DEBUGF(3, "rt_raster_get_perimeter: raster is %dx%d", raster->width, raster->height); + + /* nband < 0 means all bands */ + if (nband >= 0) { + if (nband >= numband) { + rterror("rt_raster_get_boundary: Band %d not found for raster", nband); + return ES_ERROR; + } + + numband = 1; + } + else + nband = -1; + + RASTER_DEBUGF(3, "rt_raster_get_perimeter: nband, numband = %d, %d", nband, numband); + + _nband = rtalloc(sizeof(uint16_t) * numband); + if (_nband == NULL) { + rterror("rt_raster_get_boundary: Could not allocate memory for band indices"); + return ES_ERROR; + } + + if (nband < 0) { + for (i = 0; i < numband; i++) + _nband[i] = i; + } + else + _nband[0] = nband; + + for (i = 0; i < numband; i++) { + band = rt_raster_get_band(raster, _nband[i]); + if (band == NULL) { + rterror("rt_raster_get_boundary: Could not get band at index %d", _nband[i]); + rtdealloc(_nband); + return ES_ERROR; + } + + /* band is nodata */ + if (rt_band_get_isnodata_flag(band) != 0) + continue; + + if (_rti_raster_get_band_perimeter(band, trim) != ES_NONE) { + rterror("rt_raster_get_boundary: Could not get band perimeter"); + rtdealloc(_nband); + return ES_ERROR; + } + + for (j = 0; j < 4; j++) { + if (!isset[j] || trim[j] < _trim[j]) { + _trim[j] = trim[j]; + isset[j] = 1; + } + } + } + + /* no longer needed */ + rtdealloc(_nband); + + /* check isset, just need to check one element */ + if (!isset[0]) { + /* return NULL as bands are empty */ + return ES_NONE; + } + + RASTER_DEBUGF(4, "trim = (%d, %d, %d, %d)", + trim[0], trim[1], trim[2], trim[3]); + + /* only one ring */ + rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); + if (!rings) { + rterror("rt_raster_get_perimeter: Could not allocate memory for polygon ring"); + return ES_ERROR; + } + rings[0] = ptarray_construct(0, 0, 5); + if (!rings[0]) { + rterror("rt_raster_get_perimeter: Could not construct point array"); + return ES_ERROR; + } + pts = rings[0]; + + /* Upper-left corner (first and last points) */ + rt_raster_cell_to_geopoint( + raster, + _trim[3], _trim[0], + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 0, &p4d); + ptarray_set_point4d(pts, 4, &p4d); + + /* Upper-right corner (we go clockwise) */ + rt_raster_cell_to_geopoint( + raster, + raster->width - _trim[1], _trim[0], + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 1, &p4d); + + /* Lower-right corner */ + rt_raster_cell_to_geopoint( + raster, + raster->width - _trim[1], raster->height - _trim[2], + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 2, &p4d); + + /* Lower-left corner */ + rt_raster_cell_to_geopoint( + raster, + _trim[3], raster->height - _trim[2], + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 3, &p4d); + + poly = lwpoly_construct(srid, 0, 1, rings); + *perimeter = lwpoly_as_lwgeom(poly); + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_surface() +******************************************************************************/ + +/** + * Get a raster as a surface (multipolygon). If a band is specified, + * those pixels with value (not NODATA) contribute to the area + * of the output multipolygon. + * + * @param raster : the raster to convert to a multipolygon + * @param nband : the 0-based band of raster rast to use + * if value is less than zero, bands are ignored. + * @param *surface : raster as a surface (multipolygon). + * if all pixels are NODATA, NULL is set + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate rt_raster_surface(rt_raster raster, int nband, LWMPOLY **surface) { + rt_band band = NULL; + LWGEOM *mpoly = NULL; + LWGEOM *tmp = NULL; + LWGEOM *clone = NULL; + rt_geomval gv = NULL; + int gvcount = 0; + GEOSGeometry *gc = NULL; + GEOSGeometry *gunion = NULL; + GEOSGeometry **geoms = NULL; + int geomscount = 0; + int i = 0; + + assert(surface != NULL); + + /* init *surface to NULL */ + *surface = NULL; + + /* raster is empty, surface = NULL */ + if (rt_raster_is_empty(raster)) + return ES_NONE; + + /* if nband < 0, return the convex hull as a multipolygon */ + if (nband < 0) { + /* + lwgeom_as_multi() only does a shallow clone internally + so input and output geometries may share memory + hence the deep clone of the output geometry for returning + is the only way to guarentee the memory isn't shared + */ + if (rt_raster_get_convex_hull(raster, &tmp) != ES_NONE) { + rterror("rt_raster_surface: Could not get convex hull of raster"); + return ES_ERROR; + } + mpoly = lwgeom_as_multi(tmp); + clone = lwgeom_clone_deep(mpoly); + lwgeom_free(tmp); + lwgeom_free(mpoly); + + *surface = lwgeom_as_lwmpoly(clone); + return ES_NONE; + } + /* check that nband is valid */ + else if (nband >= rt_raster_get_num_bands(raster)) { + rterror("rt_raster_surface: The band index %d is invalid", nband); + return ES_ERROR; + } + + /* get band */ + band = rt_raster_get_band(raster, nband); + if (band == NULL) { + rterror("rt_raster_surface: Error getting band %d from raster", nband); + return ES_ERROR; + } + + /* band does not have a NODATA flag, return convex hull */ + if (!rt_band_get_hasnodata_flag(band)) { + /* + lwgeom_as_multi() only does a shallow clone internally + so input and output geometries may share memory + hence the deep clone of the output geometry for returning + is the only way to guarentee the memory isn't shared + */ + if (rt_raster_get_convex_hull(raster, &tmp) != ES_NONE) { + rterror("rt_raster_surface: Could not get convex hull of raster"); + return ES_ERROR; + } + mpoly = lwgeom_as_multi(tmp); + clone = lwgeom_clone_deep(mpoly); + lwgeom_free(tmp); + lwgeom_free(mpoly); + + *surface = lwgeom_as_lwmpoly(clone); + return ES_NONE; + } + /* band is NODATA, return NULL */ + else if (rt_band_get_isnodata_flag(band)) { + RASTER_DEBUG(3, "Band is NODATA. Returning NULL"); + return ES_NONE; + } + + /* initialize GEOS */ + initGEOS(lwnotice, lwgeom_geos_error); + + /* use gdal polygonize */ + gv = rt_raster_gdal_polygonize(raster, nband, 1, &gvcount); + /* no polygons returned */ + if (gvcount < 1) { + RASTER_DEBUG(3, "All pixels of band are NODATA. Returning NULL"); + if (gv != NULL) rtdealloc(gv); + return ES_NONE; + } + /* more than 1 polygon */ + else if (gvcount > 1) { + /* convert LWPOLY to GEOSGeometry */ + geomscount = gvcount; + geoms = rtalloc(sizeof(GEOSGeometry *) * geomscount); + if (geoms == NULL) { + rterror("rt_raster_surface: Could not allocate memory for pixel polygons as GEOSGeometry"); + for (i = 0; i < gvcount; i++) lwpoly_free(gv[i].geom); + rtdealloc(gv); + return ES_ERROR; + } + for (i = 0; i < gvcount; i++) { +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(lwpoly_as_lwgeom(gv[i].geom), WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "geom %d = %s", i, wkt); + rtdealloc(wkt); + } +#endif + + geoms[i] = LWGEOM2GEOS(lwpoly_as_lwgeom(gv[i].geom)); + lwpoly_free(gv[i].geom); + } + rtdealloc(gv); + + /* create geometry collection */ +#if POSTGIS_GEOS_VERSION >= 33 + gc = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, geoms, geomscount); +#else + gc = GEOSGeom_createCollection(GEOS_MULTIPOLYGON, geoms, geomscount); +#endif + + if (gc == NULL) { +#if POSTGIS_GEOS_VERSION >= 33 + rterror("rt_raster_surface: Could not create GEOS GEOMETRYCOLLECTION from set of pixel polygons"); +#else + rterror("rt_raster_surface: Could not create GEOS MULTIPOLYGON from set of pixel polygons"); +#endif + + for (i = 0; i < geomscount; i++) + GEOSGeom_destroy(geoms[i]); + rtdealloc(geoms); + return ES_ERROR; + } + + /* run the union */ +#if POSTGIS_GEOS_VERSION >= 33 + gunion = GEOSUnaryUnion(gc); +#else + gunion = GEOSUnionCascaded(gc); +#endif + GEOSGeom_destroy(gc); + rtdealloc(geoms); + + if (gunion == NULL) { +#if POSTGIS_GEOS_VERSION >= 33 + rterror("rt_raster_surface: Could not union the pixel polygons using GEOSUnaryUnion()"); +#else + rterror("rt_raster_surface: Could not union the pixel polygons using GEOSUnionCascaded()"); +#endif + return ES_ERROR; + } + + /* convert union result from GEOSGeometry to LWGEOM */ + mpoly = GEOS2LWGEOM(gunion, 0); + + /* + is geometry valid? + if not, try to make valid + */ + do { + LWGEOM *mpolyValid = NULL; + +#if POSTGIS_GEOS_VERSION < 33 + break; +#endif + + if (GEOSisValid(gunion)) + break; + + /* make geometry valid */ + mpolyValid = lwgeom_make_valid(mpoly); + if (mpolyValid == NULL) { + rtwarn("Cannot fix invalid geometry"); + break; + } + + lwgeom_free(mpoly); + mpoly = mpolyValid; + } + while (0); + + GEOSGeom_destroy(gunion); + } + else { + mpoly = lwpoly_as_lwgeom(gv[0].geom); + rtdealloc(gv); + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "geom 0 = %s", wkt); + rtdealloc(wkt); + } +#endif + } + + /* specify SRID */ + lwgeom_set_srid(mpoly, rt_raster_get_srid(raster)); + + if (mpoly != NULL) { + /* convert to multi */ + if (!lwgeom_is_collection(mpoly)) { + tmp = mpoly; + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "before multi = %s", wkt); + rtdealloc(wkt); + } +#endif + + RASTER_DEBUGF(4, "mpoly @ %p", mpoly); + + /* + lwgeom_as_multi() only does a shallow clone internally + so input and output geometries may share memory + hence the deep clone of the output geometry for returning + is the only way to guarentee the memory isn't shared + */ + mpoly = lwgeom_as_multi(tmp); + clone = lwgeom_clone_deep(mpoly); + lwgeom_free(tmp); + lwgeom_free(mpoly); + mpoly = clone; + + RASTER_DEBUGF(4, "mpoly @ %p", mpoly); + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "after multi = %s", wkt); + rtdealloc(wkt); + } +#endif + } + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(mpoly, WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "returning geometry = %s", wkt); + rtdealloc(wkt); + } +#endif + + *surface = lwgeom_as_lwmpoly(mpoly); + return ES_NONE; + } + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_pixel_as_polygon() +******************************************************************************/ + +/** + * Get a raster pixel as a polygon. + * + * The pixel shape is a 4 vertices (5 to be closed) single + * ring polygon bearing the raster's rotation + * and using projection coordinates + * + * @param raster : the raster to get pixel from + * @param x : the column number + * @param y : the row number + * + * @return the pixel polygon, or NULL on error. + * + */ +LWPOLY* +rt_raster_pixel_as_polygon(rt_raster rast, int x, int y) +{ + double scale_x, scale_y; + double skew_x, skew_y; + double ul_x, ul_y; + int srid; + POINTARRAY **points; + POINT4D p, p0; + LWPOLY *poly; + + assert(rast != NULL); + + scale_x = rt_raster_get_x_scale(rast); + scale_y = rt_raster_get_y_scale(rast); + skew_x = rt_raster_get_x_skew(rast); + skew_y = rt_raster_get_y_skew(rast); + ul_x = rt_raster_get_x_offset(rast); + ul_y = rt_raster_get_y_offset(rast); + srid = rt_raster_get_srid(rast); + + points = rtalloc(sizeof(POINTARRAY *)*1); + points[0] = ptarray_construct(0, 0, 5); + + p0.x = scale_x * x + skew_x * y + ul_x; + p0.y = scale_y * y + skew_y * x + ul_y; + ptarray_set_point4d(points[0], 0, &p0); + + p.x = p0.x + scale_x; + p.y = p0.y + skew_y; + ptarray_set_point4d(points[0], 1, &p); + + p.x = p0.x + scale_x + skew_x; + p.y = p0.y + scale_y + skew_y; + ptarray_set_point4d(points[0], 2, &p); + + p.x = p0.x + skew_x; + p.y = p0.y + scale_y; + ptarray_set_point4d(points[0], 3, &p); + + /* close it */ + ptarray_set_point4d(points[0], 4, &p0); + + poly = lwpoly_construct(srid, NULL, 1, points); + + return poly; +} + +/****************************************************************************** +* rt_raster_get_convex_hull() +******************************************************************************/ + +/** + * Get raster's convex hull. + * + * The convex hull is typically a 4 vertices (5 to be closed) + * single ring polygon bearing the raster's rotation and using + * projection coordinates. + * + * @param raster : the raster to get info from + * @param **hull : pointer to convex hull + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_get_convex_hull(rt_raster raster, LWGEOM **hull) { + double gt[6] = {0.0}; + int srid = SRID_UNKNOWN; + + POINTARRAY *pts = NULL; + POINT4D p4d; + + assert(hull != NULL); + *hull = NULL; + + /* raster is NULL, convex hull is NULL */ + if (raster == NULL) + return ES_NONE; + + /* raster metadata */ + srid = rt_raster_get_srid(raster); + rt_raster_get_geotransform_matrix(raster, gt); + + RASTER_DEBUGF(3, "rt_raster_get_convex_hull: raster is %dx%d", raster->width, raster->height); + + /* return point or line since at least one of the two dimensions is 0 */ + if ((!raster->width) || (!raster->height)) { + p4d.x = gt[0]; + p4d.y = gt[3]; + + /* return point */ + if (!raster->width && !raster->height) { + LWPOINT *point = lwpoint_make2d(srid, p4d.x, p4d.y); + *hull = lwpoint_as_lwgeom(point); + } + /* return linestring */ + else { + LWLINE *line = NULL; + pts = ptarray_construct_empty(0, 0, 2); + + /* first point of line */ + ptarray_append_point(pts, &p4d, LW_TRUE); + + /* second point of line */ + if (rt_raster_cell_to_geopoint( + raster, + rt_raster_get_width(raster), rt_raster_get_height(raster), + &p4d.x, &p4d.y, + gt + ) != ES_NONE) { + rterror("rt_raster_get_convex_hull: Could not get second point for linestring"); + return ES_ERROR; + } + ptarray_append_point(pts, &p4d, LW_TRUE); + line = lwline_construct(srid, NULL, pts); + + *hull = lwline_as_lwgeom(line); + } + + return ES_NONE; + } + else { + POINTARRAY **rings = NULL; + LWPOLY* poly = NULL; + + /* only one ring */ + rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); + if (!rings) { + rterror("rt_raster_get_convex_hull: Could not allocate memory for polygon ring"); + return ES_ERROR; + } + rings[0] = ptarray_construct(0, 0, 5); + /* TODO: handle error on ptarray construction */ + /* XXX jorgearevalo: the error conditions aren't managed in ptarray_construct */ + if (!rings[0]) { + rterror("rt_raster_get_convex_hull: Could not construct point array"); + return ES_ERROR; + } + pts = rings[0]; + + /* Upper-left corner (first and last points) */ + p4d.x = gt[0]; + p4d.y = gt[3]; + ptarray_set_point4d(pts, 0, &p4d); + ptarray_set_point4d(pts, 4, &p4d); + + /* Upper-right corner (we go clockwise) */ + rt_raster_cell_to_geopoint( + raster, + raster->width, 0, + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 1, &p4d); + + /* Lower-right corner */ + rt_raster_cell_to_geopoint( + raster, + raster->width, raster->height, + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 2, &p4d); + + /* Lower-left corner */ + rt_raster_cell_to_geopoint( + raster, + 0, raster->height, + &p4d.x, &p4d.y, + gt + ); + ptarray_set_point4d(pts, 3, &p4d); + + poly = lwpoly_construct(srid, 0, 1, rings); + *hull = lwpoly_as_lwgeom(poly); + } + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_gdal_polygonize() +******************************************************************************/ + +/** + * Returns a set of "geomval" value, one for each group of pixel + * sharing the same value for the provided band. + * + * A "geomval" value is a complex type composed of a geometry + * in LWPOLY representation (one for each group of pixel sharing + * the same value) and the value associated with this geometry. + * + * @param raster : the raster to get info from. + * @param nband : the band to polygonize. 0-based + * @param exclude_nodata_value : if non-zero, ignore nodata values + * to check for pixels with value + * + * @return A set of "geomval" values, one for each group of pixels + * sharing the same value for the provided band. The returned values are + * LWPOLY geometries. + */ +rt_geomval +rt_raster_gdal_polygonize( + rt_raster raster, int nband, + int exclude_nodata_value, + int *pnElements +) { + CPLErr cplerr = CE_None; + char *pszQuery; + long j; + OGRSFDriverH ogr_drv = NULL; + GDALDriverH gdal_drv = NULL; + GDALDatasetH memdataset = NULL; + GDALRasterBandH gdal_band = NULL; + OGRDataSourceH memdatasource = NULL; + rt_geomval pols = NULL; + OGRLayerH hLayer = NULL; + OGRFeatureH hFeature = NULL; + OGRGeometryH hGeom = NULL; + OGRFieldDefnH hFldDfn = NULL; + unsigned char *wkb = NULL; + int wkbsize = 0; + LWGEOM *lwgeom = NULL; + int nFeatureCount = 0; + rt_band band = NULL; + int iPixVal = -1; + double dValue = 0.0; + int iBandHasNodataValue = FALSE; + double dBandNoData = 0.0; + + /* for checking that a geometry is valid */ + GEOSGeometry *ggeom = NULL; + int isValid; + LWGEOM *lwgeomValid = NULL; + + uint32_t bandNums[1] = {nband}; + int excludeNodataValues[1] = {exclude_nodata_value}; + + /* checks */ + assert(NULL != raster); + assert(NULL != pnElements); + + RASTER_DEBUG(2, "In rt_raster_gdal_polygonize"); + + *pnElements = 0; + + /******************************* + * Get band + *******************************/ + band = rt_raster_get_band(raster, nband); + if (NULL == band) { + rterror("rt_raster_gdal_polygonize: Error getting band %d from raster", nband); + return NULL; + } + + if (exclude_nodata_value) { + + /* band is NODATA */ + if (rt_band_get_isnodata_flag(band)) { + RASTER_DEBUG(3, "Band is NODATA. Returning null"); + *pnElements = 0; + return NULL; + } + + iBandHasNodataValue = rt_band_get_hasnodata_flag(band); + if (iBandHasNodataValue) + rt_band_get_nodata(band, &dBandNoData); + else + exclude_nodata_value = FALSE; + } + + /***************************************************** + * Convert raster to GDAL MEM dataset + *****************************************************/ + memdataset = rt_raster_to_gdal_mem(raster, NULL, bandNums, excludeNodataValues, 1, &gdal_drv); + if (NULL == memdataset) { + rterror("rt_raster_gdal_polygonize: Couldn't convert raster to GDAL MEM dataset"); + return NULL; + } + + /***************************** + * Register ogr mem driver + *****************************/ + OGRRegisterAll(); + + RASTER_DEBUG(3, "creating OGR MEM vector"); + + /***************************************************** + * Create an OGR in-memory vector for layers + *****************************************************/ + ogr_drv = OGRGetDriverByName("Memory"); + memdatasource = OGR_Dr_CreateDataSource(ogr_drv, "", NULL); + if (NULL == memdatasource) { + rterror("rt_raster_gdal_polygonize: Couldn't create a OGR Datasource to store pols"); + GDALClose(memdataset); + return NULL; + } + + /* Can MEM driver create new layers? */ + if (!OGR_DS_TestCapability(memdatasource, ODsCCreateLayer)) { + rterror("rt_raster_gdal_polygonize: MEM driver can't create new layers, aborting"); + + /* xxx jorgearevalo: what should we do now? */ + GDALClose(memdataset); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + RASTER_DEBUG(3, "polygonizying GDAL MEM raster band"); + + /***************************** + * Polygonize the raster band + *****************************/ + + /** + * From GDALPolygonize function header: "Polygon features will be + * created on the output layer, with polygon geometries representing + * the polygons". So,the WKB geometry type should be "wkbPolygon" + **/ + hLayer = OGR_DS_CreateLayer(memdatasource, "PolygonizedLayer", NULL, wkbPolygon, NULL); + + if (NULL == hLayer) { + rterror("rt_raster_gdal_polygonize: Couldn't create layer to store polygons"); + + GDALClose(memdataset); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + /** + * Create a new field in the layer, to store the px value + */ + + /* First, create a field definition to create the field */ + hFldDfn = OGR_Fld_Create("PixelValue", OFTReal); + + /* Second, create the field */ + if (OGR_L_CreateField(hLayer, hFldDfn, TRUE) != OGRERR_NONE) { + rtwarn("Couldn't create a field in OGR Layer. The polygons generated won't be able to store the pixel value"); + iPixVal = -1; + } + else { + /* Index to the new field created in the layer */ + iPixVal = 0; + } + + /* Get GDAL raster band */ + gdal_band = GDALGetRasterBand(memdataset, 1); + if (NULL == gdal_band) { + rterror("rt_raster_gdal_polygonize: Couldn't get GDAL band to polygonize"); + + GDALClose(memdataset); + OGR_Fld_Destroy(hFldDfn); + OGR_DS_DeleteLayer(memdatasource, 0); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + /** + * We don't need a raster mask band. Each band has a nodata value. + **/ +#ifdef GDALFPOLYGONIZE + cplerr = GDALFPolygonize(gdal_band, NULL, hLayer, iPixVal, NULL, NULL, NULL); +#else + cplerr = GDALPolygonize(gdal_band, NULL, hLayer, iPixVal, NULL, NULL, NULL); +#endif + + if (cplerr != CE_None) { + rterror("rt_raster_gdal_polygonize: Could not polygonize GDAL band"); + + GDALClose(memdataset); + OGR_Fld_Destroy(hFldDfn); + OGR_DS_DeleteLayer(memdatasource, 0); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + /** + * Optimization: Apply a OGR SQL filter to the layer to select the + * features different from NODATA value. + * + * Thanks to David Zwarg. + **/ + if (iBandHasNodataValue) { + pszQuery = (char *) rtalloc(50 * sizeof (char)); + sprintf(pszQuery, "PixelValue != %f", dBandNoData ); + OGRErr e = OGR_L_SetAttributeFilter(hLayer, pszQuery); + if (e != OGRERR_NONE) { + rtwarn("Error filtering NODATA values for band. All values will be treated as data values"); + } + } + else { + pszQuery = NULL; + } + + /********************************************************************* + * Transform OGR layers to WKB polygons + * XXX jorgearevalo: GDALPolygonize does not set the coordinate system + * on the output layer. Application code should do this when the layer + * is created, presumably matching the raster coordinate system. + *********************************************************************/ + nFeatureCount = OGR_L_GetFeatureCount(hLayer, TRUE); + + /* Allocate memory for pols */ + pols = (rt_geomval) rtalloc(nFeatureCount * sizeof(struct rt_geomval_t)); + + if (NULL == pols) { + rterror("rt_raster_gdal_polygonize: Could not allocate memory for geomval set"); + + GDALClose(memdataset); + OGR_Fld_Destroy(hFldDfn); + OGR_DS_DeleteLayer(memdatasource, 0); + if (NULL != pszQuery) + rtdealloc(pszQuery); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + /* initialize GEOS */ + initGEOS(lwnotice, lwgeom_geos_error); + + RASTER_DEBUGF(3, "storing polygons (%d)", nFeatureCount); + + /* Reset feature reading to start in the first feature */ + OGR_L_ResetReading(hLayer); + + for (j = 0; j < nFeatureCount; j++) { + hFeature = OGR_L_GetNextFeature(hLayer); + dValue = OGR_F_GetFieldAsDouble(hFeature, iPixVal); + + hGeom = OGR_F_GetGeometryRef(hFeature); + wkbsize = OGR_G_WkbSize(hGeom); + + /* allocate wkb buffer */ + wkb = rtalloc(sizeof(unsigned char) * wkbsize); + if (wkb == NULL) { + rterror("rt_raster_gdal_polygonize: Could not allocate memory for WKB buffer"); + + OGR_F_Destroy(hFeature); + GDALClose(memdataset); + OGR_Fld_Destroy(hFldDfn); + OGR_DS_DeleteLayer(memdatasource, 0); + if (NULL != pszQuery) + rtdealloc(pszQuery); + OGRReleaseDataSource(memdatasource); + + return NULL; + } + + /* export WKB with LSB byte order */ + OGR_G_ExportToWkb(hGeom, wkbNDR, wkb); + + /* convert WKB to LWGEOM */ + lwgeom = lwgeom_from_wkb(wkb, wkbsize, LW_PARSER_CHECK_NONE); + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = NULL; + OGR_G_ExportToWkt(hGeom, &wkt); + RASTER_DEBUGF(4, "GDAL wkt = %s", wkt); + CPLFree(wkt); + + d_print_binary_hex("GDAL wkb", wkb, wkbsize); + } +#endif + + /* cleanup unnecessary stuff */ + rtdealloc(wkb); + wkb = NULL; + wkbsize = 0; + + OGR_F_Destroy(hFeature); + + /* specify SRID */ + lwgeom_set_srid(lwgeom, rt_raster_get_srid(raster)); + + /* + is geometry valid? + if not, try to make valid + */ + do { +#if POSTGIS_GEOS_VERSION < 33 + /* nothing can be done if the geometry was invalid if GEOS < 3.3 */ + break; +#endif + + ggeom = (GEOSGeometry *) LWGEOM2GEOS(lwgeom); + if (ggeom == NULL) { + rtwarn("Cannot test geometry for validity"); + break; + } + + isValid = GEOSisValid(ggeom); + + GEOSGeom_destroy(ggeom); + ggeom = NULL; + + /* geometry is valid */ + if (isValid) + break; + + RASTER_DEBUG(3, "fixing invalid geometry"); + + /* make geometry valid */ + lwgeomValid = lwgeom_make_valid(lwgeom); + if (lwgeomValid == NULL) { + rtwarn("Cannot fix invalid geometry"); + break; + } + + lwgeom_free(lwgeom); + lwgeom = lwgeomValid; + } + while (0); + + /* save lwgeom */ + pols[j].geom = lwgeom_as_lwpoly(lwgeom); + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *wkt = lwgeom_to_wkt(lwgeom, WKT_ISO, DBL_DIG, NULL); + RASTER_DEBUGF(4, "LWGEOM wkt = %s", wkt); + rtdealloc(wkt); + + size_t lwwkbsize = 0; + uint8_t *lwwkb = lwgeom_to_wkb(lwgeom, WKB_ISO | WKB_NDR, &lwwkbsize); + if (lwwkbsize) { + d_print_binary_hex("LWGEOM wkb", lwwkb, lwwkbsize); + rtdealloc(lwwkb); + } + } +#endif + + /* set pixel value */ + pols[j].val = dValue; + } + + *pnElements = nFeatureCount; + + RASTER_DEBUG(3, "destroying GDAL MEM raster"); + GDALClose(memdataset); + + RASTER_DEBUG(3, "destroying OGR MEM vector"); + OGR_Fld_Destroy(hFldDfn); + OGR_DS_DeleteLayer(memdatasource, 0); + if (NULL != pszQuery) rtdealloc(pszQuery); + OGRReleaseDataSource(memdatasource); + + return pols; +} + diff --git a/raster/rt_core/rt_mapalgebra.c b/raster/rt_core/rt_mapalgebra.c new file mode 100644 index 000000000..818f2bb24 --- /dev/null +++ b/raster/rt_core/rt_mapalgebra.c @@ -0,0 +1,1850 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* rt_band_reclass() +******************************************************************************/ + +/** + * Returns new band with values reclassified + * + * @param srcband : the band who's values will be reclassified + * @param pixtype : pixel type of the new band + * @param hasnodata : indicates if the band has a nodata value + * @param nodataval : nodata value for the new band + * @param exprset : array of rt_reclassexpr structs + * @param exprcount : number of elements in expr + * + * @return a new rt_band or NULL on error + */ +rt_band +rt_band_reclass( + rt_band srcband, rt_pixtype pixtype, + uint32_t hasnodata, double nodataval, + rt_reclassexpr *exprset, int exprcount +) { + rt_band band = NULL; + uint32_t width = 0; + uint32_t height = 0; + int numval = 0; + int memsize = 0; + void *mem = NULL; + uint32_t src_hasnodata = 0; + double src_nodataval = 0.0; + int isnodata = 0; + + int rtn; + uint32_t x; + uint32_t y; + int i; + double or = 0; + double ov = 0; + double nr = 0; + double nv = 0; + int do_nv = 0; + rt_reclassexpr expr = NULL; + + assert(NULL != srcband); + assert(NULL != exprset && exprcount > 0); + RASTER_DEBUGF(4, "exprcount = %d", exprcount); + RASTER_DEBUGF(4, "exprset @ %p", exprset); + + /* source nodata */ + src_hasnodata = rt_band_get_hasnodata_flag(srcband); + if (src_hasnodata) + rt_band_get_nodata(srcband, &src_nodataval); + + /* size of memory block to allocate */ + width = rt_band_get_width(srcband); + height = rt_band_get_height(srcband); + numval = width * height; + memsize = rt_pixtype_size(pixtype) * numval; + mem = (int *) rtalloc(memsize); + if (!mem) { + rterror("rt_band_reclass: Could not allocate memory for band"); + return 0; + } + + /* initialize to zero */ + if (!hasnodata) { + memset(mem, 0, memsize); + } + /* initialize to nodataval */ + else { + int32_t checkvalint = 0; + uint32_t checkvaluint = 0; + double checkvaldouble = 0; + float checkvalfloat = 0; + + switch (pixtype) { + case PT_1BB: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_1BB(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_2BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_2BUI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_4BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_4BUI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_8BSI: + { + int8_t *ptr = mem; + int8_t clamped_initval = rt_util_clamp_to_8BSI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_8BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_8BUI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_16BSI: + { + int16_t *ptr = mem; + int16_t clamped_initval = rt_util_clamp_to_16BSI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_16BUI: + { + uint16_t *ptr = mem; + uint16_t clamped_initval = rt_util_clamp_to_16BUI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_32BSI: + { + int32_t *ptr = mem; + int32_t clamped_initval = rt_util_clamp_to_32BSI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_32BUI: + { + uint32_t *ptr = mem; + uint32_t clamped_initval = rt_util_clamp_to_32BUI(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvaluint = ptr[0]; + break; + } + case PT_32BF: + { + float *ptr = mem; + float clamped_initval = rt_util_clamp_to_32F(nodataval); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalfloat = ptr[0]; + break; + } + case PT_64BF: + { + double *ptr = mem; + for (i = 0; i < numval; i++) + ptr[i] = nodataval; + checkvaldouble = ptr[0]; + break; + } + default: + { + rterror("rt_band_reclass: Unknown pixeltype %d", pixtype); + rtdealloc(mem); + return 0; + } + } + + /* Overflow checking */ + rt_util_dbl_trunc_warning( + nodataval, + checkvalint, checkvaluint, + checkvalfloat, checkvaldouble, + pixtype + ); + } + RASTER_DEBUGF(3, "rt_band_reclass: width = %d height = %d", width, height); + + band = rt_band_new_inline(width, height, pixtype, hasnodata, nodataval, mem); + if (!band) { + rterror("rt_band_reclass: Could not create new band"); + rtdealloc(mem); + return 0; + } + rt_band_set_ownsdata_flag(band, 1); /* we DO own this data!!! */ + RASTER_DEBUGF(3, "rt_band_reclass: new band @ %p", band); + + for (x = 0; x < width; x++) { + for (y = 0; y < height; y++) { + rtn = rt_band_get_pixel(srcband, x, y, &ov, &isnodata); + + /* error getting value, skip */ + if (rtn != ES_NONE) { + RASTER_DEBUGF(3, "Cannot get value at %d, %d", x, y); + continue; + } + + do { + do_nv = 0; + + /* no data*/ + if (hasnodata && isnodata) { + do_nv = 1; + break; + } + + for (i = 0; i < exprcount; i++) { + expr = exprset[i]; + + /* ov matches min and max*/ + if ( + FLT_EQ(expr->src.min, ov) && + FLT_EQ(expr->src.max, ov) + ) { + do_nv = 1; + break; + } + + /* process min */ + if (( + expr->src.exc_min && ( + expr->src.min > ov || + FLT_EQ(expr->src.min, ov) + )) || ( + expr->src.inc_min && ( + expr->src.min < ov || + FLT_EQ(expr->src.min, ov) + )) || ( + expr->src.min < ov + )) { + /* process max */ + if (( + expr->src.exc_max && ( + ov > expr->src.max || + FLT_EQ(expr->src.max, ov) + )) || ( + expr->src.inc_max && ( + ov < expr->src.max || + FLT_EQ(expr->src.max, ov) + )) || ( + ov < expr->src.max + )) { + do_nv = 1; + break; + } + } + } + } + while (0); + + /* no expression matched, do not continue */ + if (!do_nv) continue; + RASTER_DEBUGF(3, "Using exprset[%d] unless NODATA", i); + + /* converting a value from one range to another range + OldRange = (OldMax - OldMin) + NewRange = (NewMax - NewMin) + NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin + */ + + /* NODATA */ + if (hasnodata && isnodata) { + nv = nodataval; + } + /* + "src" min and max is the same, prevent division by zero + set nv to "dst" min, which should be the same as "dst" max + */ + else if (FLT_EQ(expr->src.max, expr->src.min)) { + nv = expr->dst.min; + } + else { + or = expr->src.max - expr->src.min; + nr = expr->dst.max - expr->dst.min; + nv = (((ov - expr->src.min) * nr) / or) + expr->dst.min; + + /* if dst range is from high to low */ + if (expr->dst.min > expr->dst.max) { + if (nv > expr->dst.min) + nv = expr->dst.min; + else if (nv < expr->dst.max) + nv = expr->dst.max; + } + /* if dst range is from low to high */ + else { + if (nv < expr->dst.min) + nv = expr->dst.min; + else if (nv > expr->dst.max) + nv = expr->dst.max; + } + } + + /* round the value for integers */ + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BSI: + case PT_8BUI: + case PT_16BSI: + case PT_16BUI: + case PT_32BSI: + case PT_32BUI: + nv = round(nv); + break; + default: + break; + } + + RASTER_DEBUGF(4, "(%d, %d) ov: %f or: %f - %f nr: %f - %f nv: %f" + , x + , y + , ov + , (NULL != expr) ? expr->src.min : 0 + , (NULL != expr) ? expr->src.max : 0 + , (NULL != expr) ? expr->dst.min : 0 + , (NULL != expr) ? expr->dst.max : 0 + , nv + ); + if (rt_band_set_pixel(band, x, y, nv, NULL) != ES_NONE) { + rterror("rt_band_reclass: Could not assign value to new band"); + rt_band_destroy(band); + rtdealloc(mem); + return 0; + } + + expr = NULL; + } + } + + return band; +} + +/****************************************************************************** +* rt_raster_iterator() +******************************************************************************/ + +typedef struct _rti_iterator_arg_t* _rti_iterator_arg; +struct _rti_iterator_arg_t { + int count; + + rt_raster *raster; + int *isempty; + double **offset; + int *width; + int *height; + + struct { + rt_band *rtband; + int *hasnodata; + int *isnodata; + double *nodataval; + double *minval; + } band; + + struct { + uint16_t x; + uint16_t y; + } distance; + + struct { + uint32_t rows; + uint32_t columns; + } dimension; + + struct { + double **values; + int **nodata; + } empty; + + rt_iterator_arg arg; +}; + +static _rti_iterator_arg +_rti_iterator_arg_init() { + _rti_iterator_arg _param; + + _param = rtalloc(sizeof(struct _rti_iterator_arg_t)); + if (_param == NULL) { + rterror("_rti_iterator_arg_init: Could not allocate memory for _rti_iterator_arg"); + return NULL; + } + + _param->count = 0; + + _param->raster = NULL; + _param->isempty = NULL; + _param->offset = NULL; + _param->width = NULL; + _param->height = NULL; + + _param->band.rtband = NULL; + _param->band.hasnodata = NULL; + _param->band.isnodata = NULL; + _param->band.nodataval = NULL; + _param->band.minval = NULL; + + _param->distance.x = 0; + _param->distance.y = 0; + + _param->dimension.rows = 0; + _param->dimension.columns = 0; + + _param->empty.values = NULL; + _param->empty.nodata = NULL; + + _param->arg = NULL; + + return _param; +} + +static void +_rti_iterator_arg_destroy(_rti_iterator_arg _param) { + int i = 0; + + if (_param->raster != NULL) + rtdealloc(_param->raster); + if (_param->isempty != NULL) + rtdealloc(_param->isempty); + if (_param->width != NULL) + rtdealloc(_param->width); + if (_param->height != NULL) + rtdealloc(_param->height); + + if (_param->band.rtband != NULL) + rtdealloc(_param->band.rtband); + if (_param->band.hasnodata != NULL) + rtdealloc(_param->band.hasnodata); + if (_param->band.isnodata != NULL) + rtdealloc(_param->band.isnodata); + if (_param->band.nodataval != NULL) + rtdealloc(_param->band.nodataval); + if (_param->band.minval != NULL) + rtdealloc(_param->band.minval); + + if (_param->offset != NULL) { + for (i = 0; i < _param->count; i++) { + if (_param->offset[i] == NULL) + continue; + rtdealloc(_param->offset[i]); + } + rtdealloc(_param->offset); + } + + if (_param->empty.values != NULL) { + for (i = 0; i < _param->dimension.rows; i++) { + if (_param->empty.values[i] == NULL) + continue; + rtdealloc(_param->empty.values[i]); + } + rtdealloc(_param->empty.values); + } + if (_param->empty.nodata != NULL) { + for (i = 0; i < _param->dimension.rows; i++) { + if (_param->empty.nodata[i] == NULL) + continue; + rtdealloc(_param->empty.nodata[i]); + } + rtdealloc(_param->empty.nodata); + } + + if (_param->arg != NULL) { + if (_param->arg->values != NULL) + rtdealloc(_param->arg->values); + if (_param->arg->nodata != NULL) + rtdealloc(_param->arg->nodata); + if (_param->arg->src_pixel != NULL) { + for (i = 0; i < _param->count; i++) { + if (_param->arg->src_pixel[i] == NULL) + continue; + rtdealloc(_param->arg->src_pixel[i]); + } + + rtdealloc(_param->arg->src_pixel); + } + + rtdealloc(_param->arg); + } + + rtdealloc(_param); +} + +static int +_rti_iterator_arg_populate( + _rti_iterator_arg _param, + rt_iterator itrset, uint16_t itrcount, + uint16_t distancex, uint16_t distancey, + int *allnull, int *allempty +) { + int i = 0; + int hasband = 0; + + _param->count = itrcount; + _param->distance.x = distancex; + _param->distance.y = distancey; + _param->dimension.columns = distancex * 2 + 1; + _param->dimension.rows = distancey * 2 + 1; + + /* allocate memory for children */ + _param->raster = rtalloc(sizeof(rt_raster) * itrcount); + _param->isempty = rtalloc(sizeof(int) * itrcount); + _param->width = rtalloc(sizeof(int) * itrcount); + _param->height = rtalloc(sizeof(int) * itrcount); + + _param->offset = rtalloc(sizeof(double *) * itrcount); + + _param->band.rtband = rtalloc(sizeof(rt_band) * itrcount); + _param->band.hasnodata = rtalloc(sizeof(int) * itrcount); + _param->band.isnodata = rtalloc(sizeof(int) * itrcount); + _param->band.nodataval = rtalloc(sizeof(double) * itrcount); + _param->band.minval = rtalloc(sizeof(double) * itrcount); + + if ( + _param->raster == NULL || + _param->isempty == NULL || + _param->width == NULL || + _param->height == NULL || + _param->offset == NULL || + _param->band.rtband == NULL || + _param->band.hasnodata == NULL || + _param->band.isnodata == NULL || + _param->band.nodataval == NULL || + _param->band.minval == NULL + ) { + rterror("_rti_iterator_arg_populate: Could not allocate memory for children of _rti_iterator_arg"); + return 0; + } + + *allnull = 0; + *allempty = 0; + + /* + check input rasters + not empty, band # is valid + copy raster pointers and set flags + */ + for (i = 0; i < itrcount; i++) { + /* initialize elements */ + _param->raster[i] = NULL; + _param->isempty[i] = 0; + _param->width[i] = 0; + _param->height[i] = 0; + + _param->offset[i] = NULL; + + _param->band.rtband[i] = NULL; + _param->band.hasnodata[i] = 0; + _param->band.isnodata[i] = 0; + _param->band.nodataval[i] = 0; + _param->band.minval[i] = 0; + + /* set isempty */ + if (itrset[i].raster == NULL) { + _param->isempty[i] = 1; + + (*allnull)++; + (*allempty)++; + + continue; + } + else if (rt_raster_is_empty(itrset[i].raster)) { + _param->isempty[i] = 1; + + (*allempty)++; + + continue; + } + + /* check band number */ + hasband = rt_raster_has_band(itrset[i].raster, itrset[i].nband); + if (!hasband) { + if (!itrset[i].nbnodata) { + rterror("_rti_iterator_arg_populate: Band %d not found for raster %d", itrset[i].nband, i); + return 0; + } + else { + RASTER_DEBUGF(4, "Band %d not found for raster %d. Using NODATA", itrset[i].nband, i); + } + } + + _param->raster[i] = itrset[i].raster; + if (hasband) { + _param->band.rtband[i] = rt_raster_get_band(itrset[i].raster, itrset[i].nband); + if (_param->band.rtband[i] == NULL) { + rterror("_rti_iterator_arg_populate: Could not get band %d for raster %d", itrset[i].nband, i); + return 0; + } + + /* hasnodata */ + _param->band.hasnodata[i] = rt_band_get_hasnodata_flag(_param->band.rtband[i]); + + /* hasnodata = TRUE */ + if (_param->band.hasnodata[i]) { + /* nodataval */ + rt_band_get_nodata(_param->band.rtband[i], &(_param->band.nodataval[i])); + + /* isnodata */ + _param->band.isnodata[i] = rt_band_get_isnodata_flag(_param->band.rtband[i]); + } + /* hasnodata = FALSE */ + else { + /* minval */ + _param->band.minval[i] = rt_band_get_min_value(_param->band.rtband[i]); + } + } + + /* width, height */ + _param->width[i] = rt_raster_get_width(_param->raster[i]); + _param->height[i] = rt_raster_get_height(_param->raster[i]); + + /* init offset */ + _param->offset[i] = rtalloc(sizeof(double) * 2); + if (_param->offset[i] == NULL) { + rterror("_rti_iterator_arg_populate: Could not allocate memory for offsets"); + return 0; + } + } + + return 1; +} + +static int +_rti_iterator_arg_empty_init(_rti_iterator_arg _param) { + int x = 0; + int y = 0; + + _param->empty.values = rtalloc(sizeof(double *) * _param->dimension.rows); + _param->empty.nodata = rtalloc(sizeof(int *) * _param->dimension.rows); + if (_param->empty.values == NULL || _param->empty.nodata == NULL) { + rterror("_rti_iterator_arg_empty_init: Could not allocate memory for empty values and NODATA"); + return 0; + } + + for (y = 0; y < _param->dimension.rows; y++) { + _param->empty.values[y] = rtalloc(sizeof(double) * _param->dimension.columns); + _param->empty.nodata[y] = rtalloc(sizeof(int) * _param->dimension.columns); + + if (_param->empty.values[y] == NULL || _param->empty.nodata[y] == NULL) { + rterror("_rti_iterator_arg_empty_init: Could not allocate memory for elements of empty values and NODATA"); + return 0; + } + + for (x = 0; x < _param->dimension.columns; x++) { + _param->empty.values[y][x] = 0; + _param->empty.nodata[y][x] = 1; + } + } + + return 1; +} + +static int +_rti_iterator_arg_callback_init(_rti_iterator_arg _param) { + int i = 0; + + _param->arg = rtalloc(sizeof(struct rt_iterator_arg_t)); + if (_param->arg == NULL) { + rterror("_rti_iterator_arg_callback_init: Could not allocate memory for rt_iterator_arg"); + return 0; + } + + _param->arg->values = NULL; + _param->arg->nodata = NULL; + _param->arg->src_pixel = NULL; + + /* initialize argument components */ + _param->arg->values = rtalloc(sizeof(double **) * _param->count); + _param->arg->nodata = rtalloc(sizeof(int **) * _param->count); + _param->arg->src_pixel = rtalloc(sizeof(int *) * _param->count); + if (_param->arg->values == NULL || _param->arg->nodata == NULL || _param->arg->src_pixel == NULL) { + rterror("_rti_iterator_arg_callback_init: Could not allocate memory for element of rt_iterator_arg"); + return 0; + } + memset(_param->arg->values, 0, sizeof(double **) * _param->count); + memset(_param->arg->nodata, 0, sizeof(int **) * _param->count); + + /* initialize pos */ + for (i = 0; i < _param->count; i++) { + + _param->arg->src_pixel[i] = rtalloc(sizeof(int) * 2); + if (_param->arg->src_pixel[i] == NULL) { + rterror("_rti_iterator_arg_callback_init: Could not allocate memory for position elements of rt_iterator_arg"); + return 0; + } + memset(_param->arg->src_pixel[i], 0, sizeof(int) * 2); + } + + _param->arg->rasters = _param->count; + _param->arg->rows = _param->dimension.rows; + _param->arg->columns = _param->dimension.columns; + + _param->arg->dst_pixel[0] = 0; + _param->arg->dst_pixel[1] = 0; + + return 1; +} + +static void +_rti_iterator_arg_callback_clean(_rti_iterator_arg _param) { + int i = 0; + int y = 0; + + for (i = 0; i < _param->count; i++) { + RASTER_DEBUGF(5, "empty at @ %p", _param->empty.values); + RASTER_DEBUGF(5, "values at @ %p", _param->arg->values[i]); + + if (_param->arg->values[i] == _param->empty.values) { + _param->arg->values[i] = NULL; + _param->arg->nodata[i] = NULL; + + continue; + } + + for (y = 0; y < _param->dimension.rows; y++) { + rtdealloc(_param->arg->values[i][y]); + rtdealloc(_param->arg->nodata[i][y]); + } + + rtdealloc(_param->arg->values[i]); + rtdealloc(_param->arg->nodata[i]); + + _param->arg->values[i] = NULL; + _param->arg->nodata[i] = NULL; + } +} + +/** + * n-raster iterator. + * The raster returned should be freed by the caller + * + * @param itrset : set of rt_iterator objects. + * @param itrcount : number of objects in itrset. + * @param extenttype : type of extent for the output raster. + * @param customextent : raster specifying custom extent. + * is only used if extenttype is ET_CUSTOM. + * @param pixtype : the desired pixel type of the output raster's band. + * @param hasnodata : indicates if the band has nodata value + * @param nodataval : the nodata value, will be appropriately + * truncated to fit the pixtype size. + * @param distancex : the number of pixels around the specified pixel + * along the X axis + * @param distancey : the number of pixels around the specified pixel + * along the Y axis + * @param userarg : pointer to any argument that is passed as-is to callback. + * @param callback : callback function for actual processing of pixel values. + * @param *rtnraster : return one band raster from iterator process + * + * The callback function _must_ have the following signature. + * + * int FNAME(rt_iterator_arg arg, void *userarg, double *value, int *nodata) + * + * The callback function _must_ return zero (error) or non-zero (success) + * indicating whether the function ran successfully. + * The parameters passed to the callback function are as follows. + * + * - rt_iterator_arg arg: struct containing pixel values, NODATA flags and metadata + * - void *userarg: NULL or calling function provides to rt_raster_iterator() for use by callback function + * - double *value: value of pixel to be burned by rt_raster_iterator() + * - int *nodata: flag (0 or 1) indicating that pixel to be burned is NODATA + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate +rt_raster_iterator( + rt_iterator itrset, uint16_t itrcount, + rt_extenttype extenttype, rt_raster customextent, + rt_pixtype pixtype, + uint8_t hasnodata, double nodataval, + uint16_t distancex, uint16_t distancey, + void *userarg, + int (*callback)( + rt_iterator_arg arg, + void *userarg, + double *value, + int *nodata + ), + rt_raster *rtnraster +) { + /* output raster */ + rt_raster rtnrast = NULL; + /* output raster's band */ + rt_band rtnband = NULL; + + /* working raster */ + rt_raster rast = NULL; + + _rti_iterator_arg _param = NULL; + int allnull = 0; + int allempty = 0; + int aligned = 0; + double offset[4] = {0.}; + rt_pixel npixels; + + int i = 0; + int status = 0; + int inextent = 0; + int x = 0; + int y = 0; + int _x = 0; + int _y = 0; + + int _width = 0; + int _height = 0; + + double minval; + double value; + int isnodata; + int nodata; + + RASTER_DEBUG(3, "Starting..."); + + assert(itrset != NULL && itrcount > 0); + assert(rtnraster != NULL); + + /* init rtnraster to NULL */ + *rtnraster = NULL; + + /* check that callback function is not NULL */ + if (callback == NULL) { + rterror("rt_raster_iterator: Callback function not provided"); + return ES_ERROR; + } + + /* check that custom extent is provided if extenttype = ET_CUSTOM */ + if (extenttype == ET_CUSTOM && rt_raster_is_empty(customextent)) { + rterror("rt_raster_iterator: Custom extent cannot be empty if extent type is ET_CUSTOM"); + return ES_ERROR; + } + + /* check that pixtype != PT_END */ + if (pixtype == PT_END) { + rterror("rt_raster_iterator: Pixel type cannot be PT_END"); + return ES_ERROR; + } + + /* initialize _param */ + if ((_param = _rti_iterator_arg_init()) == NULL) { + rterror("rt_raster_iterator: Could not initialize internal variables"); + return ES_ERROR; + } + + /* fill _param */ + if (!_rti_iterator_arg_populate(_param, itrset, itrcount, distancex, distancey, &allnull, &allempty)) { + rterror("rt_raster_iterator: Could not populate for internal variables"); + _rti_iterator_arg_destroy(_param); + return ES_ERROR; + } + + /* shortcut if all null, return NULL */ + if (allnull == itrcount) { + RASTER_DEBUG(3, "all rasters are NULL, returning NULL"); + + _rti_iterator_arg_destroy(_param); + + return ES_NONE; + } + /* shortcut if all empty, return empty raster */ + else if (allempty == itrcount) { + RASTER_DEBUG(3, "all rasters are empty, returning empty raster"); + + _rti_iterator_arg_destroy(_param); + + rtnrast = rt_raster_new(0, 0); + if (rtnrast == NULL) { + rterror("rt_raster_iterator: Could not create empty raster"); + return ES_ERROR; + } + rt_raster_set_scale(rtnrast, 0, 0); + + *rtnraster = rtnrast; + return ES_NONE; + } + + /* check that all rasters are aligned */ + RASTER_DEBUG(3, "checking alignment of all rasters"); + rast = NULL; + + /* find raster to use as reference */ + /* use custom if provided */ + if (extenttype == ET_CUSTOM) { + RASTER_DEBUG(4, "using custom extent as reference raster"); + rast = customextent; + } + /* use first valid one in _param->raster */ + else { + for (i = 0; i < itrcount; i++) { + if (!_param->isempty[i]) { + RASTER_DEBUGF(4, "using raster at index %d as reference raster", i); + rast = _param->raster[i]; + break; + } + } + } + + /* no rasters found, SHOULD NEVER BE HERE! */ + if (rast == NULL) { + rterror("rt_raster_iterator: Could not find reference raster to use for alignment tests"); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + + do { + aligned = 1; + + /* check custom first if set. also skip if rasters are the same */ + if (extenttype == ET_CUSTOM && rast != customextent) { + if (rt_raster_same_alignment(rast, customextent, &aligned, NULL) != ES_NONE) { + rterror("rt_raster_iterator: Could not test for alignment between reference raster and custom extent"); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + + RASTER_DEBUGF(5, "custom extent alignment: %d", aligned); + if (!aligned) + break; + } + + for (i = 0; i < itrcount; i++) { + /* skip NULL rasters and if rasters are the same */ + if (_param->isempty[i] || rast == _param->raster[i]) + continue; + + if (rt_raster_same_alignment(rast, _param->raster[i], &aligned, NULL) != ES_NONE) { + rterror("rt_raster_iterator: Could not test for alignment between reference raster and raster %d", i); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + RASTER_DEBUGF(5, "raster at index %d alignment: %d", i, aligned); + + /* abort checking since a raster isn't aligned */ + if (!aligned) + break; + } + } + while (0); + + /* not aligned, error */ + if (!aligned) { + rterror("rt_raster_iterator: The set of rasters provided (custom extent included, if appropriate) do not have the same alignment"); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + + /* use extenttype to build output raster (no bands though) */ + i = -1; + switch (extenttype) { + case ET_INTERSECTION: + case ET_UNION: + /* make copy of first "real" raster */ + rtnrast = rtalloc(sizeof(struct rt_raster_t)); + if (rtnrast == NULL) { + rterror("rt_raster_iterator: Could not allocate memory for output raster"); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + + for (i = 0; i < itrcount; i++) { + if (!_param->isempty[i]) { + memcpy(rtnrast, _param->raster[i], sizeof(struct rt_raster_serialized_t)); + break; + } + } + rtnrast->numBands = 0; + rtnrast->bands = NULL; + + /* get extent of output raster */ + rast = NULL; + for (i = i + 1; i < itrcount; i++) { + if (_param->isempty[i]) + continue; + + status = rt_raster_from_two_rasters(rtnrast, _param->raster[i], extenttype, &rast, NULL); + rtdealloc(rtnrast); + + if (rast == NULL || status != ES_NONE) { + rterror("rt_raster_iterator: Could not compute %s extent of rasters", + extenttype == ET_UNION ? "union" : "intersection" + ); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + else if (rt_raster_is_empty(rast)) { + rtinfo("rt_raster_iterator: Computed raster for %s extent is empty", + extenttype == ET_UNION ? "union" : "intersection" + ); + + _rti_iterator_arg_destroy(_param); + + *rtnraster = rast; + return ES_NONE; + } + + rtnrast = rast; + rast = NULL; + } + + break; + /* + first, second and last have similar checks + and continue into custom + */ + case ET_FIRST: + i = 0; + case ET_SECOND: + if (i < 0) { + if (itrcount < 2) + i = 0; + else + i = 1; + } + case ET_LAST: + if (i < 0) i = itrcount - 1; + + /* input raster is null, return NULL */ + if (_param->raster[i] == NULL) { + RASTER_DEBUGF(3, "returning NULL as %s raster is NULL and extent type is ET_%s", + (i == 0 ? "first" : (i == 1 ? "second" : "last")), + (i == 0 ? "FIRST" : (i == 1 ? "SECOND" : "LAST")) + ); + + _rti_iterator_arg_destroy(_param); + + return ES_NONE; + } + /* input raster is empty, return empty raster */ + else if (_param->isempty[i]) { + RASTER_DEBUGF(3, "returning empty raster as %s raster is empty and extent type is ET_%s", + (i == 0 ? "first" : (i == 1 ? "second" : "last")), + (i == 0 ? "FIRST" : (i == 1 ? "SECOND" : "LAST")) + ); + + _rti_iterator_arg_destroy(_param); + + rtnrast = rt_raster_new(0, 0); + if (rtnrast == NULL) { + rterror("rt_raster_iterator: Could not create empty raster"); + return ES_ERROR; + } + rt_raster_set_scale(rtnrast, 0, 0); + + *rtnraster = rtnrast; + return ES_NONE; + } + /* copy the custom extent raster */ + case ET_CUSTOM: + rtnrast = rtalloc(sizeof(struct rt_raster_t)); + if (rtnrast == NULL) { + rterror("rt_raster_iterator: Could not allocate memory for output raster"); + + _rti_iterator_arg_destroy(_param); + + return ES_ERROR; + } + + switch (extenttype) { + case ET_CUSTOM: + memcpy(rtnrast, customextent, sizeof(struct rt_raster_serialized_t)); + break; + /* first, second, last */ + default: + memcpy(rtnrast, _param->raster[i], sizeof(struct rt_raster_serialized_t)); + break; + } + rtnrast->numBands = 0; + rtnrast->bands = NULL; + break; + } + + _width = rt_raster_get_width(rtnrast); + _height = rt_raster_get_height(rtnrast); + + RASTER_DEBUGF(4, "rtnrast (width, height, ulx, uly, scalex, scaley, skewx, skewy, srid) = (%d, %d, %f, %f, %f, %f, %f, %f, %d)", + _width, + _height, + rt_raster_get_x_offset(rtnrast), + rt_raster_get_y_offset(rtnrast), + rt_raster_get_x_scale(rtnrast), + rt_raster_get_y_scale(rtnrast), + rt_raster_get_x_skew(rtnrast), + rt_raster_get_y_skew(rtnrast), + rt_raster_get_srid(rtnrast) + ); + + /* init values and NODATA for use with empty rasters */ + if (!_rti_iterator_arg_empty_init(_param)) { + rterror("rt_raster_iterator: Could not initialize empty values and NODATA"); + + _rti_iterator_arg_destroy(_param); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + /* create output band */ + if (rt_raster_generate_new_band( + rtnrast, + pixtype, + nodataval, + hasnodata, nodataval, + 0 + ) < 0) { + rterror("rt_raster_iterator: Could not add new band to output raster"); + + _rti_iterator_arg_destroy(_param); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + /* get output band */ + rtnband = rt_raster_get_band(rtnrast, 0); + if (rtnband == NULL) { + rterror("rt_raster_iterator: Could not get new band from output raster"); + + _rti_iterator_arg_destroy(_param); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + /* output band's minimum value */ + minval = rt_band_get_min_value(rtnband); + + /* initialize argument for callback function */ + if (!_rti_iterator_arg_callback_init(_param)) { + rterror("rt_raster_iterator: Could not initialize callback function argument"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + /* fill _param->offset */ + for (i = 0; i < itrcount; i++) { + if (_param->isempty[i]) + continue; + + status = rt_raster_from_two_rasters(rtnrast, _param->raster[i], ET_FIRST, &rast, offset); + rtdealloc(rast); + if (status != ES_NONE) { + rterror("rt_raster_iterator: Could not compute raster offsets"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + _param->offset[i][0] = offset[2]; + _param->offset[i][1] = offset[3]; + RASTER_DEBUGF(4, "rast %d offset: %f %f", i, offset[2], offset[3]); + } + + /* loop over each pixel (POI) of output raster */ + /* _x,_y are for output raster */ + /* x,y are for input raster */ + for (_y = 0; _y < _height; _y++) { + for (_x = 0; _x < _width; _x++) { + RASTER_DEBUGF(4, "iterating output pixel (x, y) = (%d, %d)", _x, _y); + _param->arg->dst_pixel[0] = _x; + _param->arg->dst_pixel[1] = _y; + + /* loop through each input raster */ + for (i = 0; i < itrcount; i++) { + RASTER_DEBUGF(4, "raster %d", i); + + /* + empty raster + OR band does not exist and flag set to use NODATA + OR band is NODATA + */ + if ( + _param->isempty[i] || + (_param->band.rtband[i] == NULL && itrset[i].nbnodata) || + _param->band.isnodata[i] + ) { + RASTER_DEBUG(4, "empty raster, band does not exist or band is NODATA. using empty values and NODATA"); + + x = _x; + y = _y; + + _param->arg->values[i] = _param->empty.values; + _param->arg->nodata[i] = _param->empty.nodata; + + continue; + } + + /* input raster's X,Y */ + x = _x - (int) _param->offset[i][0]; + y = _y - (int) _param->offset[i][1]; + RASTER_DEBUGF(4, "source pixel (x, y) = (%d, %d)", x, y); + + _param->arg->src_pixel[i][0] = x; + _param->arg->src_pixel[i][1] = y; + + /* neighborhood */ + npixels = NULL; + status = 0; + if (distancex > 0 && distancey > 0) { + RASTER_DEBUG(4, "getting neighborhood"); + + status = rt_band_get_nearest_pixel( + _param->band.rtband[i], + x, y, + distancex, distancey, + 1, + &npixels + ); + if (status < 0) { + rterror("rt_raster_iterator: Could not get pixel neighborhood"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + } + + /* get value of POI */ + /* get pixel's value */ + if ( + (x >= 0 && x < _param->width[i]) && + (y >= 0 && y < _param->height[i]) + ) { + RASTER_DEBUG(4, "getting value of POI"); + if (rt_band_get_pixel( + _param->band.rtband[i], + x, y, + &value, + &isnodata + ) != ES_NONE) { + rterror("rt_raster_iterator: Could not get the pixel value of band"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + inextent = 1; + } + /* outside band extent, set to NODATA */ + else { + RASTER_DEBUG(4, "Outside band extent, setting value to NODATA"); + /* has NODATA, use NODATA */ + if (_param->band.hasnodata[i]) + value = _param->band.nodataval[i]; + /* no NODATA, use min possible value */ + else + value = _param->band.minval[i]; + + inextent = 0; + isnodata = 1; + } + + /* add pixel to neighborhood */ + status++; + if (status > 1) + npixels = (rt_pixel) rtrealloc(npixels, sizeof(struct rt_pixel_t) * status); + else + npixels = (rt_pixel) rtalloc(sizeof(struct rt_pixel_t)); + + if (npixels == NULL) { + rterror("rt_raster_iterator: Could not reallocate memory for neighborhood"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + npixels[status - 1].x = x; + npixels[status - 1].y = y; + npixels[status - 1].nodata = 1; + npixels[status - 1].value = value; + + /* set nodata flag */ + if ((!_param->band.hasnodata[i] && inextent) || !isnodata) { + npixels[status - 1].nodata = 0; + } + RASTER_DEBUGF(4, "value, nodata: %f, %d", value, npixels[status - 1].nodata); + + /* convert set of rt_pixel to 2D array */ + status = rt_pixel_set_to_array( + npixels, status, + x, y, + distancex, distancey, + &(_param->arg->values[i]), + &(_param->arg->nodata[i]), + NULL, NULL + ); + rtdealloc(npixels); + if (status != ES_NONE) { + rterror("rt_raster_iterator: Could not create 2D array of neighborhood"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + } + + /* callback */ + RASTER_DEBUG(4, "calling callback function"); + value = 0; + nodata = 0; + status = callback(_param->arg, userarg, &value, &nodata); + + /* free memory from callback */ + _rti_iterator_arg_callback_clean(_param); + + /* handle callback status */ + if (status == 0) { + rterror("rt_raster_iterator: Callback function returned an error"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + + /* burn value to pixel */ + status = 0; + if (!nodata) { + status = rt_band_set_pixel(rtnband, _x, _y, value, NULL); + RASTER_DEBUGF(4, "burning pixel (%d, %d) with value: %f", _x, _y, value); + } + else if (!hasnodata) { + status = rt_band_set_pixel(rtnband, _x, _y, minval, NULL); + RASTER_DEBUGF(4, "burning pixel (%d, %d) with minval: %f", _x, _y, minval); + } + else { + RASTER_DEBUGF(4, "NOT burning pixel (%d, %d)", _x, _y); + } + if (status != ES_NONE) { + rterror("rt_raster_iterator: Could not set pixel value"); + + _rti_iterator_arg_destroy(_param); + rt_band_destroy(rtnband); + rt_raster_destroy(rtnrast); + + return ES_ERROR; + } + } + } + + /* lots of cleanup */ + _rti_iterator_arg_destroy(_param); + + *rtnraster = rtnrast; + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_colormap() +******************************************************************************/ + +typedef struct _rti_colormap_arg_t* _rti_colormap_arg; +struct _rti_colormap_arg_t { + rt_raster raster; + rt_band band; + + rt_colormap_entry nodataentry; + int hasnodata; + double nodataval; + + int nexpr; + rt_reclassexpr *expr; + + int npos; + uint16_t *pos; + +}; + +static _rti_colormap_arg +_rti_colormap_arg_init(rt_raster raster) { + _rti_colormap_arg arg = NULL; + + arg = rtalloc(sizeof(struct _rti_colormap_arg_t)); + if (arg == NULL) { + rterror("_rti_colormap_arg_init: Could not allocate memory for _rti_color_arg"); + return NULL; + } + + arg->band = NULL; + arg->nodataentry = NULL; + arg->hasnodata = 0; + arg->nodataval = 0; + + if (raster == NULL) + arg->raster = NULL; + /* raster provided */ + else { + arg->raster = rt_raster_clone(raster, 0); + if (arg->raster == NULL) { + rterror("_rti_colormap_arg_init: Could not create output raster"); + return NULL; + } + } + + arg->nexpr = 0; + arg->expr = NULL; + + arg->npos = 0; + arg->pos = NULL; + + return arg; +} + +static void +_rti_colormap_arg_destroy(_rti_colormap_arg arg) { + int i = 0; + + if (arg->raster != NULL) { + rt_band band = NULL; + + for (i = rt_raster_get_num_bands(arg->raster) - 1; i >= 0; i--) { + band = rt_raster_get_band(arg->raster, i); + if (band != NULL) + rt_band_destroy(band); + } + + rt_raster_destroy(arg->raster); + } + + if (arg->nexpr) { + for (i = 0; i < arg->nexpr; i++) { + if (arg->expr[i] != NULL) + rtdealloc(arg->expr[i]); + } + rtdealloc(arg->expr); + } + + if (arg->npos) + rtdealloc(arg->pos); + + rtdealloc(arg); + arg = NULL; +} + +/** + * Returns a new raster with up to four 8BUI bands (RGBA) from + * applying a colormap to the user-specified band of the + * input raster. + * + * @param raster: input raster + * @param nband: 0-based index of the band to process with colormap + * @param colormap: rt_colormap object of colormap to apply to band + * + * @return new raster or NULL on error + */ +rt_raster rt_raster_colormap( + rt_raster raster, int nband, + rt_colormap colormap +) { + _rti_colormap_arg arg = NULL; + rt_raster rtnraster = NULL; + rt_band band = NULL; + int i = 0; + int j = 0; + int k = 0; + + assert(colormap != NULL); + + /* empty raster */ + if (rt_raster_is_empty(raster)) + return NULL; + + /* no colormap entries */ + if (colormap->nentry < 1) { + rterror("rt_raster_colormap: colormap must have at least one entry"); + return NULL; + } + + /* nband is valid */ + if (!rt_raster_has_band(raster, nband)) { + rterror("rt_raster_colormap: raster has no band at index %d", nband); + return NULL; + } + + band = rt_raster_get_band(raster, nband); + if (band == NULL) { + rterror("rt_raster_colormap: Could not get band at index %d", nband); + return NULL; + } + + /* init internal variables */ + arg = _rti_colormap_arg_init(raster); + if (arg == NULL) { + rterror("rt_raster_colormap: Could not initialize internal variables"); + return NULL; + } + + /* handle NODATA */ + if (rt_band_get_hasnodata_flag(band)) { + arg->hasnodata = 1; + rt_band_get_nodata(band, &(arg->nodataval)); + } + + /* # of colors */ + if (colormap->ncolor < 1) { + rterror("rt_raster_colormap: At least one color must be provided"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + else if (colormap->ncolor > 4) { + rtinfo("More than four colors indicated. Using only the first four colors"); + colormap->ncolor = 4; + } + + /* find non-NODATA entries */ + arg->npos = 0; + arg->pos = rtalloc(sizeof(uint16_t) * colormap->nentry); + if (arg->pos == NULL) { + rterror("rt_raster_colormap: Could not allocate memory for valid entries"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + for (i = 0, j = 0; i < colormap->nentry; i++) { + /* special handling for NODATA entries */ + if (colormap->entry[i].isnodata) { + /* first NODATA entry found, use it */ + if (arg->nodataentry == NULL) + arg->nodataentry = &(colormap->entry[i]); + else + rtwarn("More than one colormap entry found for NODATA value. Only using first NOTDATA entry"); + + continue; + } + + (arg->npos)++; + arg->pos[j++] = i; + } + + /* INTERPOLATE and only one non-NODATA entry */ + if (colormap->method == CM_INTERPOLATE && arg->npos < 2) { + rtinfo("Method INTERPOLATE requires at least two non-NODATA colormap entries. Using NEAREST instead"); + colormap->method = CM_NEAREST; + } + + /* NODATA entry but band has no NODATA value */ + if (!arg->hasnodata && arg->nodataentry != NULL) { + rtinfo("Band at index %d has no NODATA value. Ignoring NODATA entry", nband); + arg->nodataentry = NULL; + } + + /* allocate expr */ + arg->nexpr = arg->npos; + + /* INTERPOLATE needs one less than the number of entries */ + if (colormap->method == CM_INTERPOLATE) + arg->nexpr -= 1; + /* EXACT requires a no matching expression */ + else if (colormap->method == CM_EXACT) + arg->nexpr += 1; + + /* NODATA entry exists, add expression */ + if (arg->nodataentry != NULL) + arg->nexpr += 1; + arg->expr = rtalloc(sizeof(rt_reclassexpr) * arg->nexpr); + if (arg->expr == NULL) { + rterror("rt_raster_colormap: Could not allocate memory for reclass expressions"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + RASTER_DEBUGF(4, "nexpr = %d", arg->nexpr); + RASTER_DEBUGF(4, "expr @ %p", arg->expr); + + for (i = 0; i < arg->nexpr; i++) { + arg->expr[i] = rtalloc(sizeof(struct rt_reclassexpr_t)); + if (arg->expr[i] == NULL) { + rterror("rt_raster_colormap: Could not allocate memory for reclass expression"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + } + + /* reclassify bands */ + /* by # of colors */ + for (i = 0; i < colormap->ncolor; i++) { + k = 0; + + /* handle NODATA entry first */ + if (arg->nodataentry != NULL) { + arg->expr[k]->src.min = arg->nodataentry->value; + arg->expr[k]->src.max = arg->nodataentry->value; + arg->expr[k]->src.inc_min = 1; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_min = 0; + arg->expr[k]->src.exc_max = 0; + + arg->expr[k]->dst.min = arg->nodataentry->color[i]; + arg->expr[k]->dst.max = arg->nodataentry->color[i]; + + arg->expr[k]->dst.inc_min = 1; + arg->expr[k]->dst.inc_max = 1; + arg->expr[k]->dst.exc_min = 0; + arg->expr[k]->dst.exc_max = 0; + + RASTER_DEBUGF(4, "NODATA expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->src.min, + arg->expr[k]->src.max, + arg->expr[k]->src.inc_min, + arg->expr[k]->src.inc_max, + arg->expr[k]->src.exc_min, + arg->expr[k]->src.exc_max + ); + RASTER_DEBUGF(4, "NODATA expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->dst.min, + arg->expr[k]->dst.max, + arg->expr[k]->dst.inc_min, + arg->expr[k]->dst.inc_max, + arg->expr[k]->dst.exc_min, + arg->expr[k]->dst.exc_max + ); + + k++; + } + + /* by non-NODATA entry */ + for (j = 0; j < arg->npos; j++) { + if (colormap->method == CM_INTERPOLATE) { + if (j == arg->npos - 1) + continue; + + arg->expr[k]->src.min = colormap->entry[arg->pos[j + 1]].value; + arg->expr[k]->src.inc_min = 1; + arg->expr[k]->src.exc_min = 0; + + arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_max = 0; + + arg->expr[k]->dst.min = colormap->entry[arg->pos[j + 1]].color[i]; + arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; + + arg->expr[k]->dst.inc_min = 1; + arg->expr[k]->dst.exc_min = 0; + + arg->expr[k]->dst.inc_max = 1; + arg->expr[k]->dst.exc_max = 0; + } + else if (colormap->method == CM_NEAREST) { + + /* NOT last entry */ + if (j != arg->npos - 1) { + arg->expr[k]->src.min = ((colormap->entry[arg->pos[j]].value - colormap->entry[arg->pos[j + 1]].value) / 2.) + colormap->entry[arg->pos[j + 1]].value; + arg->expr[k]->src.inc_min = 0; + arg->expr[k]->src.exc_min = 0; + } + /* last entry */ + else { + arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value; + arg->expr[k]->src.inc_min = 1; + arg->expr[k]->src.exc_min = 1; + } + + /* NOT first entry */ + if (j > 0) { + arg->expr[k]->src.max = arg->expr[k - 1]->src.min; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_max = 0; + } + /* first entry */ + else { + arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_max = 1; + } + + arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i]; + arg->expr[k]->dst.inc_min = 1; + arg->expr[k]->dst.exc_min = 0; + + arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; + arg->expr[k]->dst.inc_max = 1; + arg->expr[k]->dst.exc_max = 0; + } + else if (colormap->method == CM_EXACT) { + arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value; + arg->expr[k]->src.inc_min = 1; + arg->expr[k]->src.exc_min = 0; + + arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_max = 0; + + arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i]; + arg->expr[k]->dst.inc_min = 1; + arg->expr[k]->dst.exc_min = 0; + + arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i]; + arg->expr[k]->dst.inc_max = 1; + arg->expr[k]->dst.exc_max = 0; + } + + RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->src.min, + arg->expr[k]->src.max, + arg->expr[k]->src.inc_min, + arg->expr[k]->src.inc_max, + arg->expr[k]->src.exc_min, + arg->expr[k]->src.exc_max + ); + + RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->dst.min, + arg->expr[k]->dst.max, + arg->expr[k]->dst.inc_min, + arg->expr[k]->dst.inc_max, + arg->expr[k]->dst.exc_min, + arg->expr[k]->dst.exc_max + ); + + k++; + } + + /* EXACT has one last expression for catching all uncaught values */ + if (colormap->method == CM_EXACT) { + arg->expr[k]->src.min = 0; + arg->expr[k]->src.inc_min = 1; + arg->expr[k]->src.exc_min = 1; + + arg->expr[k]->src.max = 0; + arg->expr[k]->src.inc_max = 1; + arg->expr[k]->src.exc_max = 1; + + arg->expr[k]->dst.min = 0; + arg->expr[k]->dst.inc_min = 1; + arg->expr[k]->dst.exc_min = 0; + + arg->expr[k]->dst.max = 0; + arg->expr[k]->dst.inc_max = 1; + arg->expr[k]->dst.exc_max = 0; + + RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->src.min, + arg->expr[k]->src.max, + arg->expr[k]->src.inc_min, + arg->expr[k]->src.inc_max, + arg->expr[k]->src.exc_min, + arg->expr[k]->src.exc_max + ); + + RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)", + k, + arg->expr[k]->dst.min, + arg->expr[k]->dst.max, + arg->expr[k]->dst.inc_min, + arg->expr[k]->dst.inc_max, + arg->expr[k]->dst.exc_min, + arg->expr[k]->dst.exc_max + ); + + k++; + } + + /* call rt_band_reclass */ + arg->band = rt_band_reclass(band, PT_8BUI, 0, 0, arg->expr, arg->nexpr); + if (arg->band == NULL) { + rterror("rt_raster_colormap: Could not reclassify band"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + + /* add reclassified band to raster */ + if (rt_raster_add_band(arg->raster, arg->band, rt_raster_get_num_bands(arg->raster)) < 0) { + rterror("rt_raster_colormap: Could not add reclassified band to output raster"); + _rti_colormap_arg_destroy(arg); + return NULL; + } + } + + rtnraster = arg->raster; + arg->raster = NULL; + _rti_colormap_arg_destroy(arg); + + return rtnraster; +} diff --git a/raster/rt_core/rt_pixel.c b/raster/rt_core/rt_pixel.c new file mode 100644 index 000000000..35d880d2e --- /dev/null +++ b/raster/rt_core/rt_pixel.c @@ -0,0 +1,385 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* rt_pixeltype +******************************************************************************/ + +int +rt_pixtype_size(rt_pixtype pixtype) { + int pixbytes = -1; + + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BSI: + case PT_8BUI: + pixbytes = 1; + break; + case PT_16BSI: + case PT_16BUI: + pixbytes = 2; + break; + case PT_32BSI: + case PT_32BUI: + case PT_32BF: + pixbytes = 4; + break; + case PT_64BF: + pixbytes = 8; + break; + default: + rterror("rt_pixtype_size: Unknown pixeltype %d", pixtype); + pixbytes = -1; + break; + } + + RASTER_DEBUGF(3, "Pixel type = %s and size = %d bytes", + rt_pixtype_name(pixtype), pixbytes); + + return pixbytes; +} + +int +rt_pixtype_alignment(rt_pixtype pixtype) { + return rt_pixtype_size(pixtype); +} + +rt_pixtype +rt_pixtype_index_from_name(const char* pixname) { + assert(pixname && strlen(pixname) > 0); + + if (strcmp(pixname, "1BB") == 0) + return PT_1BB; + else if (strcmp(pixname, "2BUI") == 0) + return PT_2BUI; + else if (strcmp(pixname, "4BUI") == 0) + return PT_4BUI; + else if (strcmp(pixname, "8BSI") == 0) + return PT_8BSI; + else if (strcmp(pixname, "8BUI") == 0) + return PT_8BUI; + else if (strcmp(pixname, "16BSI") == 0) + return PT_16BSI; + else if (strcmp(pixname, "16BUI") == 0) + return PT_16BUI; + else if (strcmp(pixname, "32BSI") == 0) + return PT_32BSI; + else if (strcmp(pixname, "32BUI") == 0) + return PT_32BUI; + else if (strcmp(pixname, "32BF") == 0) + return PT_32BF; + else if (strcmp(pixname, "64BF") == 0) + return PT_64BF; + + return PT_END; +} + +const char* +rt_pixtype_name(rt_pixtype pixtype) { + switch (pixtype) { + case PT_1BB: + return "1BB"; + case PT_2BUI: + return "2BUI"; + case PT_4BUI: + return "4BUI"; + case PT_8BSI: + return "8BSI"; + case PT_8BUI: + return "8BUI"; + case PT_16BSI: + return "16BSI"; + case PT_16BUI: + return "16BUI"; + case PT_32BSI: + return "32BSI"; + case PT_32BUI: + return "32BUI"; + case PT_32BF: + return "32BF"; + case PT_64BF: + return "64BF"; + default: + rterror("rt_pixtype_name: Unknown pixeltype %d", pixtype); + return "Unknown"; + } +} + +/** + * Return minimum value possible for pixel type + * + * @param pixtype : the pixel type to get minimum possible value for + * + * @return the minimum possible value for the pixel type. + */ +double +rt_pixtype_get_min_value(rt_pixtype pixtype) { + switch (pixtype) { + case PT_1BB: { + return (double) rt_util_clamp_to_1BB((double) CHAR_MIN); + } + case PT_2BUI: { + return (double) rt_util_clamp_to_2BUI((double) CHAR_MIN); + } + case PT_4BUI: { + return (double) rt_util_clamp_to_4BUI((double) CHAR_MIN); + } + case PT_8BUI: { + return (double) rt_util_clamp_to_8BUI((double) CHAR_MIN); + } + case PT_8BSI: { + return (double) rt_util_clamp_to_8BSI((double) SCHAR_MIN); + } + case PT_16BSI: { + return (double) rt_util_clamp_to_16BSI((double) SHRT_MIN); + } + case PT_16BUI: { + return (double) rt_util_clamp_to_16BUI((double) SHRT_MIN); + } + case PT_32BSI: { + return (double) rt_util_clamp_to_32BSI((double) INT_MIN); + } + case PT_32BUI: { + return (double) rt_util_clamp_to_32BUI((double) INT_MIN); + } + case PT_32BF: { + return (double) -FLT_MAX; + } + case PT_64BF: { + return (double) -DBL_MAX; + } + default: { + rterror("rt_pixtype_get_min_value: Unknown pixeltype %d", pixtype); + return (double) rt_util_clamp_to_8BUI((double) CHAR_MIN); + } + } +} + +/** + * Returns 1 if clamped values are equal, 0 if not equal, -1 if error + * + * @param pixtype : the pixel type to clamp the provided values + * @param val : value to compare to reference value + * @param refval : reference value to be compared with + * @param isequal : non-zero if clamped values are equal, 0 otherwise + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate rt_pixtype_compare_clamped_values( + rt_pixtype pixtype, + double val, double refval, + int *isequal +) { + assert(isequal != NULL); + *isequal = 0; + + switch (pixtype) { + case PT_1BB: + if (rt_util_clamp_to_1BB(val) == rt_util_clamp_to_1BB(refval)) + *isequal = 1; + break; + case PT_2BUI: + if (rt_util_clamp_to_2BUI(val) == rt_util_clamp_to_2BUI(refval)) + *isequal = 1; + break; + case PT_4BUI: + if (rt_util_clamp_to_4BUI(val) == rt_util_clamp_to_4BUI(refval)) + *isequal = 1; + break; + case PT_8BSI: + if (rt_util_clamp_to_8BSI(val) == rt_util_clamp_to_8BSI(refval)) + *isequal = 1; + break; + case PT_8BUI: + if (rt_util_clamp_to_8BUI(val) == rt_util_clamp_to_8BUI(refval)) + *isequal = 1; + break; + case PT_16BSI: + if (rt_util_clamp_to_16BSI(val) == rt_util_clamp_to_16BSI(refval)) + *isequal = 1; + break; + case PT_16BUI: + if (rt_util_clamp_to_16BUI(val) == rt_util_clamp_to_16BUI(refval)) + *isequal = 1; + break; + case PT_32BSI: + if (rt_util_clamp_to_32BSI(val) == rt_util_clamp_to_32BSI(refval)) + *isequal = 1; + break; + case PT_32BUI: + if (rt_util_clamp_to_32BUI(val) == rt_util_clamp_to_32BUI(refval)) + *isequal = 1; + break; + case PT_32BF: + if (FLT_EQ(rt_util_clamp_to_32F(val), rt_util_clamp_to_32F(refval))) + *isequal = 1; + break; + case PT_64BF: + if (FLT_EQ(val, refval)) + *isequal = 1; + break; + default: + rterror("rt_pixtype_compare_clamped_values: Unknown pixeltype %d", pixtype); + return ES_ERROR; + } + + return ES_NONE; +} + +/****************************************************************************** +* rt_pixel +******************************************************************************/ + +/* + * Convert an array of rt_pixel objects to two 2D arrays of value and NODATA. + * The dimensions of the returned 2D array are [Y][X], going by row Y and + * then column X. + * + * @param npixel : array of rt_pixel objects + * @param count : number of elements in npixel + * @param x : the column of the center pixel (0-based) + * @param y : the line of the center pixel (0-based) + * @param distancex : the number of pixels around the specified pixel + * along the X axis + * @param distancey : the number of pixels around the specified pixel + * along the Y axis + * @param value : pointer to pointer for 2D value array + * @param nodata : pointer to pointer for 2D NODATA array + * @param dimx : size of value and nodata along the X axis + * @param dimy : size of value and nodata along the Y axis + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate rt_pixel_set_to_array( + rt_pixel npixel, int count, + int x, int y, + uint16_t distancex, uint16_t distancey, + double ***value, + int ***nodata, + int *dimx, int *dimy +) { + uint32_t i; + uint32_t j; + uint32_t dim[2] = {0}; + double **values = NULL; + int **nodatas = NULL; + int zero[2] = {0}; + int _x; + int _y; + + assert(npixel != NULL && count > 0); + assert(value != NULL); + assert(nodata != NULL); + + /* dimensions */ + dim[0] = distancex * 2 + 1; + dim[1] = distancey * 2 + 1; + RASTER_DEBUGF(4, "dimensions = %d x %d", dim[0], dim[1]); + + /* establish 2D arrays (Y axis) */ + values = rtalloc(sizeof(double *) * dim[1]); + nodatas = rtalloc(sizeof(int *) * dim[1]); + + if (values == NULL || nodatas == NULL) { + rterror("rt_pixel_set_to_array: Could not allocate memory for 2D array"); + return ES_ERROR; + } + + /* initialize X axis */ + for (i = 0; i < dim[1]; i++) { + values[i] = rtalloc(sizeof(double) * dim[0]); + nodatas[i] = rtalloc(sizeof(int) * dim[0]); + + if (values[i] == NULL || nodatas[i] == NULL) { + rterror("rt_pixel_set_to_array: Could not allocate memory for dimension of 2D array"); + + if (values[i] == NULL) { + for (j = 0; j < i; j++) { + rtdealloc(values[j]); + rtdealloc(nodatas[j]); + } + } + else { + for (j = 0; j <= i; j++) { + rtdealloc(values[j]); + if (j < i) + rtdealloc(nodatas[j]); + } + } + + rtdealloc(values); + rtdealloc(nodatas); + + return ES_ERROR; + } + + /* set values to 0 */ + memset(values[i], 0, sizeof(double) * dim[0]); + + /* set nodatas to 1 */ + for (j = 0; j < dim[0]; j++) + nodatas[i][j] = 1; + } + + /* get 0,0 of grid */ + zero[0] = x - distancex; + zero[1] = y - distancey; + + /* populate 2D arrays */ + for (i = 0; i < count; i++) { + if (npixel[i].nodata) + continue; + + _x = npixel[i].x - zero[0]; + _y = npixel[i].y - zero[1]; + + RASTER_DEBUGF(4, "absolute x,y: %d x %d", npixel[i].x, npixel[i].y); + RASTER_DEBUGF(4, "relative x,y: %d x %d", _x, _y); + + values[_y][_x] = npixel[i].value; + nodatas[_y][_x] = 0; + + RASTER_DEBUGF(4, "(x, y, nodata, value) = (%d, %d, %d, %f)", _x, _y, nodatas[_y][_x], values[_y][_x]); + } + + *value = &(*values); + *nodata = &(*nodatas); + if (dimx != NULL) + *dimx = dim[0]; + if (dimy != NULL) + *dimy = dim[1]; + + return ES_NONE; +} diff --git a/raster/rt_core/rt_raster.c b/raster/rt_core/rt_raster.c new file mode 100644 index 000000000..869137b4c --- /dev/null +++ b/raster/rt_core/rt_raster.c @@ -0,0 +1,3751 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +#include + +/** + * Construct a raster with given dimensions. + * + * Transform will be set to identity. + * Will contain no bands. + * + * @param width : number of pixel columns + * @param height : number of pixel rows + * + * @return an rt_raster or NULL if out of memory + */ +rt_raster +rt_raster_new(uint32_t width, uint32_t height) { + rt_raster ret = NULL; + + ret = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); + if (!ret) { + rterror("rt_raster_new: Out of virtual memory creating an rt_raster"); + return NULL; + } + + RASTER_DEBUGF(3, "Created rt_raster @ %p", ret); + + if (width > 65535 || height > 65535) { + rterror("rt_raster_new: Dimensions requested exceed the maximum (65535 x 65535) permitted for a raster"); + rt_raster_destroy(ret); + return NULL; + } + + ret->width = width; + ret->height = height; + ret->scaleX = 1; + ret->scaleY = -1; + ret->ipX = 0.0; + ret->ipY = 0.0; + ret->skewX = 0.0; + ret->skewY = 0.0; + ret->srid = SRID_UNKNOWN; + + ret->numBands = 0; + ret->bands = NULL; + + return ret; +} + +void +rt_raster_destroy(rt_raster raster) { + if (raster == NULL) + return; + + RASTER_DEBUGF(3, "Destroying rt_raster @ %p", raster); + + if (raster->bands) + rtdealloc(raster->bands); + + rtdealloc(raster); +} + +static void +_rt_raster_geotransform_warn_offline_band(rt_raster raster) { + int numband = 0; + int i = 0; + rt_band band = NULL; + + if (raster == NULL) + return; + + numband = rt_raster_get_num_bands(raster); + if (numband < 1) + return; + + for (i = 0; i < numband; i++) { + band = rt_raster_get_band(raster, i); + if (NULL == band) + continue; + + if (!rt_band_is_offline(band)) + continue; + + rtwarn("Changes made to raster geotransform matrix may affect out-db band data. Returned band data may be incorrect"); + break; + } +} + +uint16_t +rt_raster_get_width(rt_raster raster) { + + assert(NULL != raster); + + return raster->width; +} + +uint16_t +rt_raster_get_height(rt_raster raster) { + + assert(NULL != raster); + + return raster->height; +} + +void +rt_raster_set_scale( + rt_raster raster, + double scaleX, double scaleY +) { + assert(NULL != raster); + + raster->scaleX = scaleX; + raster->scaleY = scaleY; + + _rt_raster_geotransform_warn_offline_band(raster); +} + +double +rt_raster_get_x_scale(rt_raster raster) { + + + assert(NULL != raster); + + return raster->scaleX; +} + +double +rt_raster_get_y_scale(rt_raster raster) { + + + assert(NULL != raster); + + return raster->scaleY; +} + +void +rt_raster_set_skews( + rt_raster raster, + double skewX, double skewY +) { + assert(NULL != raster); + + raster->skewX = skewX; + raster->skewY = skewY; + + _rt_raster_geotransform_warn_offline_band(raster); +} + +double +rt_raster_get_x_skew(rt_raster raster) { + + + assert(NULL != raster); + + return raster->skewX; +} + +double +rt_raster_get_y_skew(rt_raster raster) { + + + assert(NULL != raster); + + return raster->skewY; +} + +void +rt_raster_set_offsets( + rt_raster raster, + double x, double y +) { + + assert(NULL != raster); + + raster->ipX = x; + raster->ipY = y; + + _rt_raster_geotransform_warn_offline_band(raster); +} + +double +rt_raster_get_x_offset(rt_raster raster) { + + + assert(NULL != raster); + + return raster->ipX; +} + +double +rt_raster_get_y_offset(rt_raster raster) { + + + assert(NULL != raster); + + return raster->ipY; +} + +void +rt_raster_get_phys_params(rt_raster rast, + double *i_mag, double *j_mag, double *theta_i, double *theta_ij) +{ + double o11, o12, o21, o22 ; /* geotransform coefficients */ + + if (rast == NULL) return ; + if ( (i_mag==NULL) || (j_mag==NULL) || (theta_i==NULL) || (theta_ij==NULL)) + return ; + + /* retrieve coefficients from raster */ + o11 = rt_raster_get_x_scale(rast) ; + o12 = rt_raster_get_x_skew(rast) ; + o21 = rt_raster_get_y_skew(rast) ; + o22 = rt_raster_get_y_scale(rast) ; + + rt_raster_calc_phys_params(o11, o12, o21, o22, i_mag, j_mag, theta_i, theta_ij); +} + +void +rt_raster_calc_phys_params(double xscale, double xskew, double yskew, double yscale, + double *i_mag, double *j_mag, double *theta_i, double *theta_ij) + +{ + double theta_test ; + + if ( (i_mag==NULL) || (j_mag==NULL) || (theta_i==NULL) || (theta_ij==NULL)) + return ; + + /* pixel size in the i direction */ + *i_mag = sqrt(xscale*xscale + yskew*yskew) ; + + /* pixel size in the j direction */ + *j_mag = sqrt(xskew*xskew + yscale*yscale) ; + + /* Rotation + * ======== + * Two steps: + * 1] calculate the magnitude of the angle between the x axis and + * the i basis vector. + * 2] Calculate the sign of theta_i based on the angle between the y axis + * and the i basis vector. + */ + *theta_i = acos(xscale/(*i_mag)) ; /* magnitude */ + theta_test = acos(yskew/(*i_mag)) ; /* sign */ + if (theta_test < M_PI_2){ + *theta_i = -(*theta_i) ; + } + + + /* Angular separation of basis vectors + * =================================== + * Two steps: + * 1] calculate the magnitude of the angle between the j basis vector and + * the i basis vector. + * 2] Calculate the sign of theta_ij based on the angle between the + * perpendicular of the i basis vector and the j basis vector. + */ + *theta_ij = acos(((xscale*xskew) + (yskew*yscale))/((*i_mag)*(*j_mag))) ; + theta_test = acos( ((-yskew*xskew)+(xscale*yscale)) / + ((*i_mag)*(*j_mag))); + if (theta_test > M_PI_2) { + *theta_ij = -(*theta_ij) ; + } +} + +void +rt_raster_set_phys_params(rt_raster rast,double i_mag, double j_mag, double theta_i, double theta_ij) +{ + double o11, o12, o21, o22 ; /* calculated geotransform coefficients */ + int success ; + + if (rast == NULL) return ; + + success = rt_raster_calc_gt_coeff(i_mag, j_mag, theta_i, theta_ij, + &o11, &o12, &o21, &o22) ; + + if (success) { + rt_raster_set_scale(rast, o11, o22) ; + rt_raster_set_skews(rast, o12, o21) ; + } +} + +int +rt_raster_calc_gt_coeff(double i_mag, double j_mag, double theta_i, double theta_ij, + double *xscale, double *xskew, double *yskew, double *yscale) +{ + double f ; /* reflection flag 1.0 or -1.0 */ + double k_i ; /* shearing coefficient */ + double s_i, s_j ; /* scaling coefficients */ + double cos_theta_i, sin_theta_i ; + + if ( (xscale==NULL) || (xskew==NULL) || (yskew==NULL) || (yscale==NULL)) { + return 0; + } + + if ( (theta_ij == 0.0) || (theta_ij == M_PI)) { + return 0; + } + + /* Reflection across the i axis */ + f=1.0 ; + if (theta_ij < 0) { + f = -1.0; + } + + /* scaling along i axis */ + s_i = i_mag ; + + /* shearing parallel to i axis */ + k_i = tan(f*M_PI_2 - theta_ij) ; + + /* scaling along j axis */ + s_j = j_mag / (sqrt(k_i*k_i + 1)) ; + + /* putting it altogether */ + cos_theta_i = cos(theta_i) ; + sin_theta_i = sin(theta_i) ; + *xscale = s_i * cos_theta_i ; + *xskew = k_i * s_j * f * cos_theta_i + s_j * f * sin_theta_i ; + *yskew = -s_i * sin_theta_i ; + *yscale = -k_i * s_j * f * sin_theta_i + s_j * f * cos_theta_i ; + return 1; +} + +int32_t +rt_raster_get_srid(rt_raster raster) { + assert(NULL != raster); + + return clamp_srid(raster->srid); +} + +void +rt_raster_set_srid(rt_raster raster, int32_t srid) { + assert(NULL != raster); + + raster->srid = clamp_srid(srid); + + _rt_raster_geotransform_warn_offline_band(raster); +} + +int +rt_raster_get_num_bands(rt_raster raster) { + + + assert(NULL != raster); + + return raster->numBands; +} + +rt_band +rt_raster_get_band(rt_raster raster, int n) { + assert(NULL != raster); + + if (n >= raster->numBands || n < 0) + return NULL; + + return raster->bands[n]; +} + +/****************************************************************************** +* rt_raster_add_band() +******************************************************************************/ + +/** + * Add band data to a raster. + * + * @param raster : the raster to add a band to + * @param band : the band to add, ownership left to caller. + * Band dimensions are required to match with raster ones. + * @param index : the position where to insert the new band (0 based) + * + * @return identifier (position) for the just-added raster, or -1 on error + */ +int +rt_raster_add_band(rt_raster raster, rt_band band, int index) { + rt_band *oldbands = NULL; + rt_band oldband = NULL; + rt_band tmpband = NULL; + uint16_t i = 0; + + assert(NULL != raster); + assert(NULL != band); + + RASTER_DEBUGF(3, "Adding band %p to raster %p", band, raster); + + if (band->width != raster->width || band->height != raster->height) { + rterror("rt_raster_add_band: Can't add a %dx%d band to a %dx%d raster", + band->width, band->height, raster->width, raster->height); + return -1; + } + + if (index > raster->numBands) + index = raster->numBands; + + if (index < 0) + index = 0; + + oldbands = raster->bands; + + RASTER_DEBUGF(3, "Oldbands at %p", oldbands); + + raster->bands = (rt_band*) rtrealloc(raster->bands, + sizeof (rt_band)*(raster->numBands + 1) + ); + + RASTER_DEBUG(3, "Checking bands"); + + if (NULL == raster->bands) { + rterror("rt_raster_add_band: Out of virtual memory " + "reallocating band pointers"); + raster->bands = oldbands; + return -1; + } + + RASTER_DEBUGF(4, "realloc returned %p", raster->bands); + + for (i = 0; i <= raster->numBands; ++i) { + if (i == index) { + oldband = raster->bands[i]; + raster->bands[i] = band; + } else if (i > index) { + tmpband = raster->bands[i]; + raster->bands[i] = oldband; + oldband = tmpband; + } + } + + band->raster = raster; + + raster->numBands++; + + RASTER_DEBUGF(4, "Raster now has %d bands", raster->numBands); + + return index; +} + +/****************************************************************************** +* rt_raster_generate_new_band() +******************************************************************************/ + +/** + * Generate a new inline band and add it to a raster. + * Memory is allocated in this function for band data. + * + * @param raster : the raster to add a band to + * @param pixtype : the pixel type for the new band + * @param initialvalue : initial value for pixels + * @param hasnodata : indicates if the band has a nodata value + * @param nodatavalue : nodata value for the new band + * @param index : position to add the new band in the raster + * + * @return identifier (position) for the just-added raster, or -1 on error + */ +int +rt_raster_generate_new_band( + rt_raster raster, rt_pixtype pixtype, + double initialvalue, uint32_t hasnodata, double nodatavalue, + int index +) { + rt_band band = NULL; + int width = 0; + int height = 0; + int numval = 0; + int datasize = 0; + int oldnumbands = 0; + int numbands = 0; + void * mem = NULL; + int32_t checkvalint = 0; + uint32_t checkvaluint = 0; + double checkvaldouble = 0; + float checkvalfloat = 0; + int i; + + + assert(NULL != raster); + + /* Make sure index is in a valid range */ + oldnumbands = rt_raster_get_num_bands(raster); + if (index < 0) + index = 0; + else if (index > oldnumbands + 1) + index = oldnumbands + 1; + + /* Determine size of memory block to allocate and allocate it */ + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + numval = width * height; + datasize = rt_pixtype_size(pixtype) * numval; + + mem = (int *)rtalloc(datasize); + if (!mem) { + rterror("rt_raster_generate_new_band: Could not allocate memory for band"); + return -1; + } + + if (FLT_EQ(initialvalue, 0.0)) + memset(mem, 0, datasize); + else { + switch (pixtype) + { + case PT_1BB: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_1BB(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_2BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_2BUI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_4BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_4BUI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_8BSI: + { + int8_t *ptr = mem; + int8_t clamped_initval = rt_util_clamp_to_8BSI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_8BUI: + { + uint8_t *ptr = mem; + uint8_t clamped_initval = rt_util_clamp_to_8BUI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_16BSI: + { + int16_t *ptr = mem; + int16_t clamped_initval = rt_util_clamp_to_16BSI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_16BUI: + { + uint16_t *ptr = mem; + uint16_t clamped_initval = rt_util_clamp_to_16BUI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_32BSI: + { + int32_t *ptr = mem; + int32_t clamped_initval = rt_util_clamp_to_32BSI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalint = ptr[0]; + break; + } + case PT_32BUI: + { + uint32_t *ptr = mem; + uint32_t clamped_initval = rt_util_clamp_to_32BUI(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvaluint = ptr[0]; + break; + } + case PT_32BF: + { + float *ptr = mem; + float clamped_initval = rt_util_clamp_to_32F(initialvalue); + for (i = 0; i < numval; i++) + ptr[i] = clamped_initval; + checkvalfloat = ptr[0]; + break; + } + case PT_64BF: + { + double *ptr = mem; + for (i = 0; i < numval; i++) + ptr[i] = initialvalue; + checkvaldouble = ptr[0]; + break; + } + default: + { + rterror("rt_raster_generate_new_band: Unknown pixeltype %d", pixtype); + rtdealloc(mem); + return -1; + } + } + } + + /* Overflow checking */ + rt_util_dbl_trunc_warning( + initialvalue, + checkvalint, checkvaluint, + checkvalfloat, checkvaldouble, + pixtype + ); + + band = rt_band_new_inline(width, height, pixtype, hasnodata, nodatavalue, mem); + if (! band) { + rterror("rt_raster_generate_new_band: Could not add band to raster. Aborting"); + rtdealloc(mem); + return -1; + } + rt_band_set_ownsdata_flag(band, 1); /* we DO own this data!!! */ + index = rt_raster_add_band(raster, band, index); + numbands = rt_raster_get_num_bands(raster); + if (numbands == oldnumbands || index == -1) { + rterror("rt_raster_generate_new_band: Could not add band to raster. Aborting"); + rt_band_destroy(band); + } + + /* set isnodata if hasnodata = TRUE and initial value = nodatavalue */ + if (hasnodata && FLT_EQ(initialvalue, nodatavalue)) + rt_band_set_isnodata_flag(band, 1); + + return index; +} + +/** + * Get 6-element array of raster inverse geotransform matrix + * + * @param raster : the raster to get matrix of + * @param gt : optional input parameter, 6-element geotransform matrix + * @param igt : output parameter, 6-element inverse geotransform matrix + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_get_inverse_geotransform_matrix( + rt_raster raster, + double *gt, double *igt +) { + double _gt[6] = {0}; + + assert((raster != NULL || gt != NULL)); + assert(igt != NULL); + + if (gt == NULL) + rt_raster_get_geotransform_matrix(raster, _gt); + else + memcpy(_gt, gt, sizeof(double) * 6); + + if (!GDALInvGeoTransform(_gt, igt)) { + rterror("rt_raster_get_inverse_geotransform_matrix: Could not compute inverse geotransform matrix"); + return ES_ERROR; + } + + return ES_NONE; +} + +/** + * Get 6-element array of raster geotransform matrix + * + * @param raster : the raster to get matrix of + * @param gt : output parameter, 6-element geotransform matrix + * + */ +void +rt_raster_get_geotransform_matrix(rt_raster raster, + double *gt) { + assert(NULL != raster); + assert(NULL != gt); + + gt[0] = raster->ipX; + gt[1] = raster->scaleX; + gt[2] = raster->skewX; + gt[3] = raster->ipY; + gt[4] = raster->skewY; + gt[5] = raster->scaleY; +} + +/** + * Set raster's geotransform using 6-element array + * + * @param raster : the raster to set matrix of + * @param gt : intput parameter, 6-element geotransform matrix + * + */ +void +rt_raster_set_geotransform_matrix(rt_raster raster, + double *gt) { + assert(NULL != raster); + assert(NULL != gt); + + raster->ipX = gt[0]; + raster->scaleX = gt[1]; + raster->skewX = gt[2]; + raster->ipY = gt[3]; + raster->skewY = gt[4]; + raster->scaleY = gt[5]; + + _rt_raster_geotransform_warn_offline_band(raster); +} + +/** + * Convert an xr, yr raster point to an xw, yw point on map + * + * @param raster : the raster to get info from + * @param xr : the pixel's column + * @param yr : the pixel's row + * @param xw : output parameter, X ordinate of the geographical point + * @param yw : output parameter, Y ordinate of the geographical point + * @param gt : input/output parameter, 3x2 geotransform matrix + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_cell_to_geopoint( + rt_raster raster, + double xr, double yr, + double *xw, double *yw, + double *gt +) { + double _gt[6] = {0}; + + assert(NULL != raster); + assert(NULL != xw && NULL != yw); + + if (NULL != gt) + memcpy(_gt, gt, sizeof(double) * 6); + + /* scale of matrix is not set */ + if ( + FLT_EQ(_gt[1], 0) || + FLT_EQ(_gt[5], 0) + ) { + rt_raster_get_geotransform_matrix(raster, _gt); + } + + RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", + _gt[0], + _gt[1], + _gt[2], + _gt[3], + _gt[4], + _gt[5] + ); + + GDALApplyGeoTransform(_gt, xr, yr, xw, yw); + RASTER_DEBUGF(4, "GDALApplyGeoTransform (c -> g) for (%f, %f) = (%f, %f)", + xr, yr, *xw, *yw); + + return ES_NONE; +} + +/** + * Convert an xw,yw map point to a xr,yr raster point + * + * @param raster : the raster to get info from + * @param xw : X ordinate of the geographical point + * @param yw : Y ordinate of the geographical point + * @param xr : output parameter, the pixel's column + * @param yr : output parameter, the pixel's row + * @param igt : input/output parameter, inverse geotransform matrix + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_geopoint_to_cell( + rt_raster raster, + double xw, double yw, + double *xr, double *yr, + double *igt +) { + double _igt[6] = {0}; + double rnd = 0; + + assert(NULL != raster); + assert(NULL != xr && NULL != yr); + + if (igt != NULL) + memcpy(_igt, igt, sizeof(double) * 6); + + /* matrix is not set */ + if ( + FLT_EQ(_igt[0], 0.) && + FLT_EQ(_igt[1], 0.) && + FLT_EQ(_igt[2], 0.) && + FLT_EQ(_igt[3], 0.) && + FLT_EQ(_igt[4], 0.) && + FLT_EQ(_igt[5], 0.) + ) { + if (rt_raster_get_inverse_geotransform_matrix(raster, NULL, _igt) != ES_NONE) { + rterror("rt_raster_geopoint_to_cell: Could not get inverse geotransform matrix"); + return ES_ERROR; + } + } + + GDALApplyGeoTransform(_igt, xw, yw, xr, yr); + RASTER_DEBUGF(4, "GDALApplyGeoTransform (g -> c) for (%f, %f) = (%f, %f)", + xw, yw, *xr, *yr); + + rnd = ROUND(*xr, 0); + if (FLT_EQ(rnd, *xr)) + *xr = rnd; + else + *xr = floor(*xr); + + rnd = ROUND(*yr, 0); + if (FLT_EQ(rnd, *yr)) + *yr = rnd; + else + *yr = floor(*yr); + + RASTER_DEBUGF(4, "Corrected GDALApplyGeoTransform (g -> c) for (%f, %f) = (%f, %f)", + xw, yw, *xr, *yr); + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_get_envelope() +******************************************************************************/ + +/** + * Get raster's envelope. + * + * The envelope is the minimum bounding rectangle of the raster + * + * @param raster : the raster to get envelope of + * @param env : pointer to rt_envelope + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_get_envelope( + rt_raster raster, + rt_envelope *env +) { + int i; + int rtn; + int set = 0; + double _r[2] = {0.}; + double _w[2] = {0.}; + double _gt[6] = {0.}; + + assert(raster != NULL); + assert(env != NULL); + + rt_raster_get_geotransform_matrix(raster, _gt); + + for (i = 0; i < 4; i++) { + switch (i) { + case 0: + _r[0] = 0; + _r[1] = 0; + break; + case 1: + _r[0] = 0; + _r[1] = raster->height; + break; + case 2: + _r[0] = raster->width; + _r[1] = raster->height; + break; + case 3: + _r[0] = raster->width; + _r[1] = 0; + break; + } + + rtn = rt_raster_cell_to_geopoint( + raster, + _r[0], _r[1], + &(_w[0]), &(_w[1]), + _gt + ); + if (rtn != ES_NONE) { + rterror("rt_raster_get_envelope: Could not compute spatial coordinates for raster pixel"); + return ES_ERROR; + } + + if (!set) { + set = 1; + env->MinX = _w[0]; + env->MaxX = _w[0]; + env->MinY = _w[1]; + env->MaxY = _w[1]; + } + else { + if (_w[0] < env->MinX) + env->MinX = _w[0]; + else if (_w[0] > env->MaxX) + env->MaxX = _w[0]; + + if (_w[1] < env->MinY) + env->MinY = _w[1]; + else if (_w[1] > env->MaxY) + env->MaxY = _w[1]; + } + } + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_compute_skewed_raster() +******************************************************************************/ + +/* + * Compute skewed extent that covers unskewed extent. + * + * @param envelope : unskewed extent of type rt_envelope + * @param skew : pointer to 2-element array (x, y) of skew + * @param scale : pointer to 2-element array (x, y) of scale + * @param tolerance : value between 0 and 1 where the smaller the tolerance + * results in an extent approaching the "minimum" skewed extent. + * If value <= 0, tolerance = 0.1. If value > 1, tolerance = 1. + * + * @return skewed raster who's extent covers unskewed extent, NULL on error + */ +rt_raster +rt_raster_compute_skewed_raster( + rt_envelope extent, + double *skew, + double *scale, + double tolerance +) { + uint32_t run = 0; + uint32_t max_run = 1; + double dbl_run = 0; + + int rtn; + int covers = 0; + rt_raster raster; + double _gt[6] = {0}; + double _igt[6] = {0}; + int _d[2] = {1, -1}; + int _dlast = 0; + int _dlastpos = 0; + double _w[2] = {0}; + double _r[2] = {0}; + double _xy[2] = {0}; + int i; + int j; + int x; + int y; + + LWGEOM *geom = NULL; + GEOSGeometry *sgeom = NULL; + GEOSGeometry *ngeom = NULL; + + if ( + (tolerance < 0.) || + FLT_EQ(tolerance, 0.) + ) { + tolerance = 0.1; + } + else if (tolerance > 1.) + tolerance = 1; + + dbl_run = tolerance; + while (dbl_run < 10) { + dbl_run *= 10.; + max_run *= 10; + } + + /* scale must be provided */ + if (scale == NULL) + return NULL; + for (i = 0; i < 2; i++) { + if (FLT_EQ(scale[i], 0)) { + rterror("rt_raster_compute_skewed_raster: Scale cannot be zero"); + return 0; + } + + if (i < 1) + _gt[1] = fabs(scale[i] * tolerance); + else + _gt[5] = fabs(scale[i] * tolerance); + } + /* conform scale-y to be negative */ + _gt[5] *= -1; + + /* skew not provided or skew is zero, return raster of correct dim and spatial attributes */ + if ( + (skew == NULL) || ( + FLT_EQ(skew[0], 0) && + FLT_EQ(skew[1], 0) + ) + ) { + int _dim[2] = { + (int) fmax((fabs(extent.MaxX - extent.MinX) + (fabs(scale[0]) / 2.)) / fabs(scale[0]), 1), + (int) fmax((fabs(extent.MaxY - extent.MinY) + (fabs(scale[1]) / 2.)) / fabs(scale[1]), 1) + }; + + raster = rt_raster_new(_dim[0], _dim[1]); + if (raster == NULL) { + rterror("rt_raster_compute_skewed_raster: Could not create output raster"); + return NULL; + } + + rt_raster_set_offsets(raster, extent.MinX, extent.MaxY); + rt_raster_set_scale(raster, fabs(scale[0]), -1 * fabs(scale[1])); + rt_raster_set_skews(raster, skew[0], skew[1]); + + return raster; + } + + /* direction to shift upper-left corner */ + if (skew[0] > 0.) + _d[0] = -1; + if (skew[1] < 0.) + _d[1] = 1; + + /* geotransform */ + _gt[0] = extent.UpperLeftX; + _gt[2] = skew[0] * tolerance; + _gt[3] = extent.UpperLeftY; + _gt[4] = skew[1] * tolerance; + + RASTER_DEBUGF(4, "Initial geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5] + ); + RASTER_DEBUGF(4, "Delta: %d, %d", _d[0], _d[1]); + + /* simple raster */ + if ((raster = rt_raster_new(1, 1)) == NULL) { + rterror("rt_raster_compute_skewed_raster: Out of memory allocating extent raster"); + return NULL; + } + rt_raster_set_geotransform_matrix(raster, _gt); + + /* get inverse geotransform matrix */ + if (!GDALInvGeoTransform(_gt, _igt)) { + rterror("rt_raster_compute_skewed_raster: Could not compute inverse geotransform matrix"); + rt_raster_destroy(raster); + return NULL; + } + RASTER_DEBUGF(4, "Inverse geotransform: %f, %f, %f, %f, %f, %f", + _igt[0], _igt[1], _igt[2], _igt[3], _igt[4], _igt[5] + ); + + /* shift along axis */ + for (i = 0; i < 2; i++) { + covers = 0; + run = 0; + + RASTER_DEBUGF(3, "Shifting along %s axis", i < 1 ? "X" : "Y"); + + do { + + /* prevent possible infinite loop */ + if (run > max_run) { + rterror("rt_raster_compute_skewed_raster: Could not compute skewed extent due to check preventing infinite loop"); + rt_raster_destroy(raster); + return NULL; + } + + /* + check the four corners that they are covered along the specific axis + pixel column should be >= 0 + */ + for (j = 0; j < 4; j++) { + switch (j) { + /* upper-left */ + case 0: + _xy[0] = extent.MinX; + _xy[1] = extent.MaxY; + break; + /* lower-left */ + case 1: + _xy[0] = extent.MinX; + _xy[1] = extent.MinY; + break; + /* lower-right */ + case 2: + _xy[0] = extent.MaxX; + _xy[1] = extent.MinY; + break; + /* upper-right */ + case 3: + _xy[0] = extent.MaxX; + _xy[1] = extent.MaxY; + break; + } + + rtn = rt_raster_geopoint_to_cell( + raster, + _xy[0], _xy[1], + &(_r[0]), &(_r[1]), + _igt + ); + if (rtn != ES_NONE) { + rterror("rt_raster_compute_skewed_raster: Could not compute raster pixel for spatial coordinates"); + rt_raster_destroy(raster); + return NULL; + } + + RASTER_DEBUGF(4, "Point %d at cell %d x %d", j, (int) _r[0], (int) _r[1]); + + /* raster doesn't cover point */ + if ((int) _r[i] < 0) { + RASTER_DEBUGF(4, "Point outside of skewed extent: %d", j); + covers = 0; + + if (_dlastpos != j) { + _dlast = (int) _r[i]; + _dlastpos = j; + } + else if ((int) _r[i] < _dlast) { + RASTER_DEBUG(4, "Point going in wrong direction. Reversing direction"); + _d[i] *= -1; + _dlastpos = -1; + run = 0; + } + + break; + } + + covers++; + } + + if (!covers) { + x = 0; + y = 0; + if (i < 1) + x = _d[i] * fabs(_r[i]); + else + y = _d[i] * fabs(_r[i]); + + rtn = rt_raster_cell_to_geopoint( + raster, + x, y, + &(_w[0]), &(_w[1]), + _gt + ); + if (rtn != ES_NONE) { + rterror("rt_raster_compute_skewed_raster: Could not compute spatial coordinates for raster pixel"); + rt_raster_destroy(raster); + return NULL; + } + + /* adjust ul */ + if (i < 1) + _gt[0] = _w[i]; + else + _gt[3] = _w[i]; + rt_raster_set_geotransform_matrix(raster, _gt); + RASTER_DEBUGF(4, "Shifted geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5] + ); + + /* get inverse geotransform matrix */ + if (!GDALInvGeoTransform(_gt, _igt)) { + rterror("rt_raster_compute_skewed_raster: Could not compute inverse geotransform matrix"); + rt_raster_destroy(raster); + return NULL; + } + RASTER_DEBUGF(4, "Inverse geotransform: %f, %f, %f, %f, %f, %f", + _igt[0], _igt[1], _igt[2], _igt[3], _igt[4], _igt[5] + ); + } + + run++; + } + while (!covers); + } + + /* covers test */ + rtn = rt_raster_geopoint_to_cell( + raster, + extent.MaxX, extent.MinY, + &(_r[0]), &(_r[1]), + _igt + ); + if (rtn != ES_NONE) { + rterror("rt_raster_compute_skewed_raster: Could not compute raster pixel for spatial coordinates"); + rt_raster_destroy(raster); + return NULL; + } + + RASTER_DEBUGF(4, "geopoint %f x %f at cell %d x %d", extent.MaxX, extent.MinY, (int) _r[0], (int) _r[1]); + + raster->width = _r[0]; + raster->height = _r[1]; + + /* initialize GEOS */ + initGEOS(lwnotice, lwgeom_geos_error); + + /* create reference LWPOLY */ + { + LWPOLY *npoly = rt_util_envelope_to_lwpoly(extent); + if (npoly == NULL) { + rterror("rt_raster_compute_skewed_raster: Could not build extent's geometry for covers test"); + rt_raster_destroy(raster); + return NULL; + } + + ngeom = (GEOSGeometry *) LWGEOM2GEOS(lwpoly_as_lwgeom(npoly)); + lwpoly_free(npoly); + } + + do { + covers = 0; + + /* construct sgeom from raster */ + if ((rt_raster_get_convex_hull(raster, &geom) != ES_NONE) || geom == NULL) { + rterror("rt_raster_compute_skewed_raster: Could not build skewed extent's geometry for covers test"); + GEOSGeom_destroy(ngeom); + rt_raster_destroy(raster); + return NULL; + } + + sgeom = (GEOSGeometry *) LWGEOM2GEOS(geom); + lwgeom_free(geom); + + covers = GEOSRelatePattern(sgeom, ngeom, "******FF*"); + GEOSGeom_destroy(sgeom); + + if (covers == 2) { + rterror("rt_raster_compute_skewed_raster: Could not run covers test"); + GEOSGeom_destroy(ngeom); + rt_raster_destroy(raster); + return NULL; + } + + if (covers) + break; + + raster->width++; + raster->height++; + } + while (!covers); + + RASTER_DEBUGF(4, "Skewed extent does cover normal extent with dimensions %d x %d", raster->width, raster->height); + + raster->width = (int) ((((double) raster->width) * fabs(_gt[1]) + fabs(scale[0] / 2.)) / fabs(scale[0])); + raster->height = (int) ((((double) raster->height) * fabs(_gt[5]) + fabs(scale[1] / 2.)) / fabs(scale[1])); + _gt[1] = fabs(scale[0]); + _gt[5] = -1 * fabs(scale[1]); + _gt[2] = skew[0]; + _gt[4] = skew[1]; + rt_raster_set_geotransform_matrix(raster, _gt); + + /* minimize width/height */ + for (i = 0; i < 2; i++) { + covers = 1; + do { + if (i < 1) + raster->width--; + else + raster->height--; + + /* construct sgeom from raster */ + if ((rt_raster_get_convex_hull(raster, &geom) != ES_NONE) || geom == NULL) { + rterror("rt_raster_compute_skewed_raster: Could not build skewed extent's geometry for minimizing dimensions"); + GEOSGeom_destroy(ngeom); + rt_raster_destroy(raster); + return NULL; + } + + sgeom = (GEOSGeometry *) LWGEOM2GEOS(geom); + lwgeom_free(geom); + + covers = GEOSRelatePattern(sgeom, ngeom, "******FF*"); + GEOSGeom_destroy(sgeom); + + if (covers == 2) { + rterror("rt_raster_compute_skewed_raster: Could not run covers test for minimizing dimensions"); + GEOSGeom_destroy(ngeom); + rt_raster_destroy(raster); + return NULL; + } + + if (!covers) { + if (i < 1) + raster->width++; + else + raster->height++; + + break; + } + } + while (covers); + } + + GEOSGeom_destroy(ngeom); + + return raster; +} + +/** + * Return TRUE if the raster is empty. i.e. is NULL, width = 0 or height = 0 + * + * @param raster : the raster to get info from + * + * @return TRUE if the raster is empty, FALSE otherwise + */ +int +rt_raster_is_empty(rt_raster raster) { + return (NULL == raster || raster->height <= 0 || raster->width <= 0); +} + +/** + * Return TRUE if the raster has a band of this number. + * + * @param raster : the raster to get info from + * @param nband : the band number. 0-based + * + * @return TRUE if the raster has a band of this number, FALSE otherwise + */ +int +rt_raster_has_band(rt_raster raster, int nband) { + return !(NULL == raster || nband >= raster->numBands || nband < 0); +} + +/****************************************************************************** +* rt_raster_copy_band() +******************************************************************************/ + +/** + * Copy one band from one raster to another. Bands are duplicated from + * fromrast to torast using rt_band_duplicate. The caller will need + * to ensure that the copied band's data or path remains allocated + * for the lifetime of the copied bands. + * + * @param torast : raster to copy band to + * @param fromrast : raster to copy band from + * @param fromindex : index of band in source raster, 0-based + * @param toindex : index of new band in destination raster, 0-based + * + * @return The band index of the second raster where the new band is copied. + * -1 if error + */ +int +rt_raster_copy_band( + rt_raster torast, rt_raster fromrast, + int fromindex, int toindex +) { + rt_band srcband = NULL; + rt_band dstband = NULL; + + assert(NULL != torast); + assert(NULL != fromrast); + + /* Check raster dimensions */ + if (torast->height != fromrast->height || torast->width != fromrast->width) { + rtwarn("rt_raster_copy_band: Attempting to add a band with different width or height"); + return -1; + } + + /* Check bands limits */ + if (fromrast->numBands < 1) { + rtwarn("rt_raster_copy_band: Second raster has no band"); + return -1; + } + else if (fromindex < 0) { + rtwarn("rt_raster_copy_band: Band index for second raster < 0. Defaulted to 0"); + fromindex = 0; + } + else if (fromindex >= fromrast->numBands) { + rtwarn("rt_raster_copy_band: Band index for second raster > number of bands, truncated from %u to %u", fromindex, fromrast->numBands - 1); + fromindex = fromrast->numBands - 1; + } + + if (toindex < 0) { + rtwarn("rt_raster_copy_band: Band index for first raster < 0. Defaulted to 0"); + toindex = 0; + } + else if (toindex > torast->numBands) { + rtwarn("rt_raster_copy_band: Band index for first raster > number of bands, truncated from %u to %u", toindex, torast->numBands); + toindex = torast->numBands; + } + + /* Get band from source raster */ + srcband = rt_raster_get_band(fromrast, fromindex); + + /* duplicate band */ + dstband = rt_band_duplicate(srcband); + + /* Add band to the second raster */ + return rt_raster_add_band(torast, dstband, toindex); +} + +/****************************************************************************** +* rt_raster_from_band() +******************************************************************************/ + +/** + * Construct a new rt_raster from an existing rt_raster and an array + * of band numbers + * + * @param raster : the source raster + * @param bandNums : array of band numbers to extract from source raster + * and add to the new raster (0 based) + * @param count : number of elements in bandNums + * + * @return a new rt_raster or NULL on error + */ +rt_raster +rt_raster_from_band(rt_raster raster, uint32_t *bandNums, int count) { + rt_raster rast = NULL; + int i = 0; + int j = 0; + int idx; + int32_t flag; + double gt[6] = {0.}; + + assert(NULL != raster); + assert(NULL != bandNums); + + RASTER_DEBUGF(3, "rt_raster_from_band: source raster has %d bands", + rt_raster_get_num_bands(raster)); + + /* create new raster */ + rast = rt_raster_new(raster->width, raster->height); + if (NULL == rast) { + rterror("rt_raster_from_band: Out of memory allocating new raster"); + return NULL; + } + + /* copy raster attributes */ + rt_raster_get_geotransform_matrix(raster, gt); + rt_raster_set_geotransform_matrix(rast, gt); + + /* srid */ + rt_raster_set_srid(rast, raster->srid); + + /* copy bands */ + for (i = 0; i < count; i++) { + idx = bandNums[i]; + flag = rt_raster_copy_band(rast, raster, idx, i); + + if (flag < 0) { + rterror("rt_raster_from_band: Could not copy band"); + for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); + rt_raster_destroy(rast); + return NULL; + } + + RASTER_DEBUGF(3, "rt_raster_from_band: band created at index %d", + flag); + } + + RASTER_DEBUGF(3, "rt_raster_from_band: new raster has %d bands", + rt_raster_get_num_bands(rast)); + return rast; +} + +/****************************************************************************** +* rt_raster_replace_band() +******************************************************************************/ + +/** + * Replace band at provided index with new band + * + * @param raster: raster of band to be replaced + * @param band : new band to add to raster + * @param index : index of band to replace (0-based) + * + * @return NULL on error or replaced band + */ +rt_band +rt_raster_replace_band(rt_raster raster, rt_band band, int index) { + rt_band oldband = NULL; + assert(NULL != raster); + assert(NULL != band); + + if (band->width != raster->width || band->height != raster->height) { + rterror("rt_raster_replace_band: Band does not match raster's dimensions: %dx%d band to %dx%d raster", + band->width, band->height, raster->width, raster->height); + return 0; + } + + if (index >= raster->numBands || index < 0) { + rterror("rt_raster_replace_band: Band index is not valid"); + return 0; + } + + oldband = rt_raster_get_band(raster, index); + RASTER_DEBUGF(3, "rt_raster_replace_band: old band at %p", oldband); + RASTER_DEBUGF(3, "rt_raster_replace_band: new band at %p", band); + + raster->bands[index] = band; + RASTER_DEBUGF(3, "rt_raster_replace_band: new band at %p", raster->bands[index]); + + band->raster = raster; + oldband->raster = NULL; + + return oldband; +} + +/****************************************************************************** +* rt_raster_clone() +******************************************************************************/ + +/** + * Clone an existing raster + * + * @param raster : raster to clone + * @param deep : flag indicating if bands should be cloned + * + * @return a new rt_raster or NULL on error + */ +rt_raster +rt_raster_clone(rt_raster raster, uint8_t deep) { + rt_raster rtn = NULL; + double gt[6] = {0}; + + assert(NULL != raster); + + if (deep) { + int numband = rt_raster_get_num_bands(raster); + uint32_t *nband = NULL; + int i = 0; + + nband = rtalloc(sizeof(uint32_t) * numband); + if (nband == NULL) { + rterror("rt_raster_clone: Could not allocate memory for deep clone"); + return NULL; + } + for (i = 0; i < numband; i++) + nband[i] = i; + + rtn = rt_raster_from_band(raster, nband, numband); + rtdealloc(nband); + + return rtn; + } + + rtn = rt_raster_new( + rt_raster_get_width(raster), + rt_raster_get_height(raster) + ); + if (rtn == NULL) { + rterror("rt_raster_clone: Could not create cloned raster"); + return NULL; + } + + rt_raster_get_geotransform_matrix(raster, gt); + rt_raster_set_geotransform_matrix(rtn, gt); + rt_raster_set_srid(rtn, rt_raster_get_srid(raster)); + + return rtn; +} + +/****************************************************************************** +* rt_raster_to_gdal() +******************************************************************************/ + +/** + * Return formatted GDAL raster from raster + * + * @param raster : the raster to convert + * @param srs : the raster's coordinate system in OGC WKT + * @param format : format to convert to. GDAL driver short name + * @param options : list of format creation options. array of strings + * @param gdalsize : will be set to the size of returned bytea + * + * @return formatted GDAL raster. the calling function is responsible + * for freeing the returned data using CPLFree() + */ +uint8_t* +rt_raster_to_gdal(rt_raster raster, const char *srs, + char *format, char **options, uint64_t *gdalsize) { + GDALDriverH src_drv = NULL; + GDALDatasetH src_ds = NULL; + + vsi_l_offset rtn_lenvsi; + uint64_t rtn_len = 0; + + GDALDriverH rtn_drv = NULL; + GDALDatasetH rtn_ds = NULL; + uint8_t *rtn = NULL; + + assert(NULL != raster); + assert(NULL != gdalsize); + + /* any supported format is possible */ + rt_util_gdal_register_all(); + RASTER_DEBUG(3, "loaded all supported GDAL formats"); + + /* output format not specified */ + if (format == NULL || !strlen(format)) + format = "GTiff"; + RASTER_DEBUGF(3, "output format is %s", format); + + /* load raster into a GDAL MEM raster */ + src_ds = rt_raster_to_gdal_mem(raster, srs, NULL, NULL, 0, &src_drv); + if (NULL == src_ds) { + rterror("rt_raster_to_gdal: Could not convert raster to GDAL MEM format"); + return 0; + } + + /* load driver */ + rtn_drv = GDALGetDriverByName(format); + if (NULL == rtn_drv) { + rterror("rt_raster_to_gdal: Could not load the output GDAL driver"); + GDALClose(src_ds); + return 0; + } + RASTER_DEBUG(3, "Output driver loaded"); + + /* convert GDAL MEM raster to output format */ + RASTER_DEBUG(3, "Copying GDAL MEM raster to memory file in output format"); + rtn_ds = GDALCreateCopy( + rtn_drv, + "/vsimem/out.dat", /* should be fine assuming this is in a process */ + src_ds, + FALSE, /* should copy be strictly equivelent? */ + options, /* format options */ + NULL, /* progress function */ + NULL /* progress data */ + ); + if (NULL == rtn_ds) { + rterror("rt_raster_to_gdal: Could not create the output GDAL dataset"); + GDALClose(src_ds); + return 0; + } + + /* close source dataset */ + GDALClose(src_ds); + RASTER_DEBUG(3, "Closed GDAL MEM raster"); + + RASTER_DEBUGF(4, "dataset SRS: %s", GDALGetProjectionRef(rtn_ds)); + + /* close dataset, this also flushes any pending writes */ + GDALClose(rtn_ds); + RASTER_DEBUG(3, "Closed GDAL output raster"); + + RASTER_DEBUG(3, "Done copying GDAL MEM raster to memory file in output format"); + + /* from memory file to buffer */ + RASTER_DEBUG(3, "Copying GDAL memory file to buffer"); + rtn = VSIGetMemFileBuffer("/vsimem/out.dat", &rtn_lenvsi, TRUE); + RASTER_DEBUG(3, "Done copying GDAL memory file to buffer"); + if (NULL == rtn) { + rterror("rt_raster_to_gdal: Could not create the output GDAL raster"); + return 0; + } + + rtn_len = (uint64_t) rtn_lenvsi; + *gdalsize = rtn_len; + + return rtn; +} + +/****************************************************************************** +* rt_raster_gdal_drivers() +******************************************************************************/ + +/** + * Returns a set of available GDAL drivers + * + * @param drv_count : number of GDAL drivers available + * @param cancc : if non-zero, filter drivers to only those + * with support for CreateCopy and VirtualIO + * + * @return set of "gdaldriver" values of available GDAL drivers + */ +rt_gdaldriver +rt_raster_gdal_drivers(uint32_t *drv_count, uint8_t cancc) { + const char *state; + const char *txt; + int txt_len; + GDALDriverH *drv = NULL; + rt_gdaldriver rtn = NULL; + int count; + int i; + uint32_t j; + + assert(drv_count != NULL); + + rt_util_gdal_register_all(); + count = GDALGetDriverCount(); + rtn = (rt_gdaldriver) rtalloc(count * sizeof(struct rt_gdaldriver_t)); + if (NULL == rtn) { + rterror("rt_raster_gdal_drivers: Could not allocate memory for gdaldriver structure"); + return 0; + } + + for (i = 0, j = 0; i < count; i++) { + drv = GDALGetDriver(i); + + if (cancc) { + /* CreateCopy support */ + state = GDALGetMetadataItem(drv, GDAL_DCAP_CREATECOPY, NULL); + if (state == NULL) continue; + + /* VirtualIO support */ + state = GDALGetMetadataItem(drv, GDAL_DCAP_VIRTUALIO, NULL); + if (state == NULL) continue; + } + + /* index of driver */ + rtn[j].idx = i; + + /* short name */ + txt = GDALGetDriverShortName(drv); + txt_len = strlen(txt); + + if (cancc) { + RASTER_DEBUGF(3, "rt_raster_gdal_driver: driver %s (%d) supports CreateCopy() and VirtualIO()", txt, i); + } + + txt_len = (txt_len + 1) * sizeof(char); + rtn[j].short_name = (char *) rtalloc(txt_len); + memcpy(rtn[j].short_name, txt, txt_len); + + /* long name */ + txt = GDALGetDriverLongName(drv); + txt_len = strlen(txt); + + txt_len = (txt_len + 1) * sizeof(char); + rtn[j].long_name = (char *) rtalloc(txt_len); + memcpy(rtn[j].long_name, txt, txt_len); + + /* creation options */ + txt = GDALGetDriverCreationOptionList(drv); + txt_len = strlen(txt); + + txt_len = (txt_len + 1) * sizeof(char); + rtn[j].create_options = (char *) rtalloc(txt_len); + memcpy(rtn[j].create_options, txt, txt_len); + + j++; + } + + /* free unused memory */ + rtn = rtrealloc(rtn, j * sizeof(struct rt_gdaldriver_t)); + *drv_count = j; + + return rtn; +} + +/****************************************************************************** +* rt_raster_to_gdal_mem() +******************************************************************************/ + +/** + * Return GDAL dataset using GDAL MEM driver from raster + * + * @param raster : raster to convert to GDAL MEM + * @param srs : the raster's coordinate system in OGC WKT + * @param bandNums : array of band numbers to extract from raster + * and include in the GDAL dataset (0 based) + * @param excludeNodataValues : array of zero, nonzero where if non-zero, + * ignore nodata values for the band + * @param count : number of elements in bandNums + * @param rtn_drv : is set to the GDAL driver object + * + * @return GDAL dataset using GDAL MEM driver + */ +GDALDatasetH +rt_raster_to_gdal_mem( + rt_raster raster, + const char *srs, + uint32_t *bandNums, + int *excludeNodataValues, + int count, + GDALDriverH *rtn_drv +) { + GDALDriverH drv = NULL; + GDALDatasetH ds = NULL; + double gt[6] = {0.0}; + CPLErr cplerr; + GDALDataType gdal_pt = GDT_Unknown; + GDALRasterBandH band; + void *pVoid; + char *pszDataPointer; + char szGDALOption[50]; + char *apszOptions[4]; + double nodata = 0.0; + int allocBandNums = 0; + int allocNodataValues = 0; + + int i; + int numBands; + uint32_t width = 0; + uint32_t height = 0; + rt_band rtband = NULL; + rt_pixtype pt = PT_END; + + assert(NULL != raster); + assert(NULL != rtn_drv); + + /* store raster in GDAL MEM raster */ + if (!rt_util_gdal_driver_registered("MEM")) + GDALRegister_MEM(); + drv = GDALGetDriverByName("MEM"); + if (NULL == drv) { + rterror("rt_raster_to_gdal_mem: Could not load the MEM GDAL driver"); + return 0; + } + *rtn_drv = drv; + + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + ds = GDALCreate( + drv, "", + width, height, + 0, GDT_Byte, NULL + ); + if (NULL == ds) { + rterror("rt_raster_to_gdal_mem: Could not create a GDALDataset to convert into"); + return 0; + } + + /* add geotransform */ + rt_raster_get_geotransform_matrix(raster, gt); + cplerr = GDALSetGeoTransform(ds, gt); + if (cplerr != CE_None) { + rterror("rt_raster_to_gdal_mem: Could not set geotransformation"); + GDALClose(ds); + return 0; + } + + /* set spatial reference */ + if (NULL != srs && strlen(srs)) { + char *_srs = rt_util_gdal_convert_sr(srs, 0); + if (_srs == NULL) { + rterror("rt_raster_to_gdal_mem: Could not convert srs to GDAL accepted format"); + GDALClose(ds); + return 0; + } + + cplerr = GDALSetProjection(ds, _srs); + CPLFree(_srs); + if (cplerr != CE_None) { + rterror("rt_raster_to_gdal_mem: Could not set projection"); + GDALClose(ds); + return 0; + } + RASTER_DEBUGF(3, "Projection set to: %s", GDALGetProjectionRef(ds)); + } + + /* process bandNums */ + numBands = rt_raster_get_num_bands(raster); + if (NULL != bandNums && count > 0) { + for (i = 0; i < count; i++) { + if (bandNums[i] >= numBands) { + rterror("rt_raster_to_gdal_mem: The band index %d is invalid", bandNums[i]); + GDALClose(ds); + return 0; + } + } + } + else { + count = numBands; + bandNums = (uint32_t *) rtalloc(sizeof(uint32_t) * count); + if (NULL == bandNums) { + rterror("rt_raster_to_gdal_mem: Could not allocate memory for band indices"); + GDALClose(ds); + return 0; + } + allocBandNums = 1; + for (i = 0; i < count; i++) bandNums[i] = i; + } + + /* process exclude_nodata_values */ + if (NULL == excludeNodataValues) { + excludeNodataValues = (int *) rtalloc(sizeof(int) * count); + if (NULL == excludeNodataValues) { + rterror("rt_raster_to_gdal_mem: Could not allocate memory for NODATA flags"); + GDALClose(ds); + return 0; + } + allocNodataValues = 1; + for (i = 0; i < count; i++) excludeNodataValues[i] = 1; + } + + /* add band(s) */ + for (i = 0; i < count; i++) { + rtband = rt_raster_get_band(raster, bandNums[i]); + if (NULL == rtband) { + rterror("rt_raster_to_gdal_mem: Could not get requested band index %d", bandNums[i]); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + + pt = rt_band_get_pixtype(rtband); + gdal_pt = rt_util_pixtype_to_gdal_datatype(pt); + if (gdal_pt == GDT_Unknown) + rtwarn("rt_raster_to_gdal_mem: Unknown pixel type for band"); + + /* + For all pixel types other than PT_8BSI, set pointer to start of data + */ + if (pt != PT_8BSI) { + pVoid = rt_band_get_data(rtband); + RASTER_DEBUGF(4, "Band data is at pos %p", pVoid); + + pszDataPointer = (char *) rtalloc(20 * sizeof (char)); + sprintf(pszDataPointer, "%p", pVoid); + RASTER_DEBUGF(4, "rt_raster_to_gdal_mem: szDatapointer is %p", + pszDataPointer); + + if (strnicmp(pszDataPointer, "0x", 2) == 0) + sprintf(szGDALOption, "DATAPOINTER=%s", pszDataPointer); + else + sprintf(szGDALOption, "DATAPOINTER=0x%s", pszDataPointer); + + RASTER_DEBUG(3, "Storing info for GDAL MEM raster band"); + + apszOptions[0] = szGDALOption; + apszOptions[1] = NULL; /* pixel offset, not needed */ + apszOptions[2] = NULL; /* line offset, not needed */ + apszOptions[3] = NULL; + + /* free */ + rtdealloc(pszDataPointer); + + /* add band */ + if (GDALAddBand(ds, gdal_pt, apszOptions) == CE_Failure) { + rterror("rt_raster_to_gdal_mem: Could not add GDAL raster band"); + if (allocBandNums) rtdealloc(bandNums); + GDALClose(ds); + return 0; + } + } + /* + PT_8BSI is special as GDAL has no equivalent pixel type. + Must convert 8BSI to 16BSI so create basic band + */ + else { + /* add band */ + if (GDALAddBand(ds, gdal_pt, NULL) == CE_Failure) { + rterror("rt_raster_to_gdal_mem: Could not add GDAL raster band"); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + } + + /* check band count */ + if (GDALGetRasterCount(ds) != i + 1) { + rterror("rt_raster_to_gdal_mem: Error creating GDAL MEM raster band"); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + + /* get new band */ + band = NULL; + band = GDALGetRasterBand(ds, i + 1); + if (NULL == band) { + rterror("rt_raster_to_gdal_mem: Could not get GDAL band for additional processing"); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + + /* PT_8BSI requires manual setting of pixels */ + if (pt == PT_8BSI) { + int nXBlocks, nYBlocks; + int nXBlockSize, nYBlockSize; + int iXBlock, iYBlock; + int nXValid, nYValid; + int iX, iY; + int iXMax, iYMax; + + int x, y, z; + uint32_t valueslen = 0; + int16_t *values = NULL; + double value = 0.; + + /* this makes use of GDAL's "natural" blocks */ + GDALGetBlockSize(band, &nXBlockSize, &nYBlockSize); + nXBlocks = (width + nXBlockSize - 1) / nXBlockSize; + nYBlocks = (height + nYBlockSize - 1) / nYBlockSize; + RASTER_DEBUGF(4, "(nXBlockSize, nYBlockSize) = (%d, %d)", nXBlockSize, nYBlockSize); + RASTER_DEBUGF(4, "(nXBlocks, nYBlocks) = (%d, %d)", nXBlocks, nYBlocks); + + /* length is for the desired pixel type */ + valueslen = rt_pixtype_size(PT_16BSI) * nXBlockSize * nYBlockSize; + values = rtalloc(valueslen); + if (NULL == values) { + rterror("rt_raster_to_gdal_mem: Could not allocate memory for GDAL band pixel values"); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + + for (iYBlock = 0; iYBlock < nYBlocks; iYBlock++) { + for (iXBlock = 0; iXBlock < nXBlocks; iXBlock++) { + memset(values, 0, valueslen); + + x = iXBlock * nXBlockSize; + y = iYBlock * nYBlockSize; + RASTER_DEBUGF(4, "(iXBlock, iYBlock) = (%d, %d)", iXBlock, iYBlock); + RASTER_DEBUGF(4, "(x, y) = (%d, %d)", x, y); + + /* valid block width */ + if ((iXBlock + 1) * nXBlockSize > width) + nXValid = width - (iXBlock * nXBlockSize); + else + nXValid = nXBlockSize; + + /* valid block height */ + if ((iYBlock + 1) * nYBlockSize > height) + nYValid = height - (iYBlock * nYBlockSize); + else + nYValid = nYBlockSize; + + RASTER_DEBUGF(4, "(nXValid, nYValid) = (%d, %d)", nXValid, nYValid); + + /* convert 8BSI values to 16BSI */ + z = 0; + iYMax = y + nYValid; + iXMax = x + nXValid; + for (iY = y; iY < iYMax; iY++) { + for (iX = x; iX < iXMax; iX++) { + if (rt_band_get_pixel(rtband, iX, iY, &value, NULL) != ES_NONE) { + rterror("rt_raster_to_gdal_mem: Could not get pixel value to convert from 8BSI to 16BSI"); + rtdealloc(values); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + + values[z++] = rt_util_clamp_to_16BSI(value); + } + } + + /* burn values */ + if (GDALRasterIO( + band, GF_Write, + x, y, + nXValid, nYValid, + values, nXValid, nYValid, + gdal_pt, + 0, 0 + ) != CE_None) { + rterror("rt_raster_to_gdal_mem: Could not write converted 8BSI to 16BSI values to GDAL band"); + rtdealloc(values); + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + GDALClose(ds); + return 0; + } + } + } + + rtdealloc(values); + } + + /* Add nodata value for band */ + if (rt_band_get_hasnodata_flag(rtband) != FALSE && excludeNodataValues[i]) { + rt_band_get_nodata(rtband, &nodata); + if (GDALSetRasterNoDataValue(band, nodata) != CE_None) + rtwarn("rt_raster_to_gdal_mem: Could not set nodata value for band"); + RASTER_DEBUGF(3, "nodata value set to %f", GDALGetRasterNoDataValue(band, NULL)); + } + +#if POSTGIS_DEBUG_LEVEL > 3 + { + GDALRasterBandH _grb = NULL; + double _min; + double _max; + double _mean; + double _stddev; + + _grb = GDALGetRasterBand(ds, i + 1); + GDALComputeRasterStatistics(_grb, FALSE, &_min, &_max, &_mean, &_stddev, NULL, NULL); + RASTER_DEBUGF(4, "GDAL Band %d stats: %f, %f, %f, %f", i + 1, _min, _max, _mean, _stddev); + } +#endif + + } + + /* necessary??? */ + GDALFlushCache(ds); + + if (allocBandNums) rtdealloc(bandNums); + if (allocNodataValues) rtdealloc(excludeNodataValues); + + return ds; +} + +/****************************************************************************** +* rt_raster_from_gdal_dataset() +******************************************************************************/ + +/** + * Return a raster from a GDAL dataset + * + * @param ds : the GDAL dataset to convert to a raster + * + * @return raster or NULL + */ +rt_raster +rt_raster_from_gdal_dataset(GDALDatasetH ds) { + rt_raster rast = NULL; + double gt[6] = {0}; + CPLErr cplerr; + uint32_t width = 0; + uint32_t height = 0; + uint32_t numBands = 0; + int i = 0; + char *authname = NULL; + char *authcode = NULL; + + GDALRasterBandH gdband = NULL; + GDALDataType gdpixtype = GDT_Unknown; + rt_band band; + int32_t idx; + rt_pixtype pt = PT_END; + uint32_t ptlen = 0; + int hasnodata = 0; + double nodataval; + + int x; + int y; + + int nXBlocks, nYBlocks; + int nXBlockSize, nYBlockSize; + int iXBlock, iYBlock; + int nXValid, nYValid; + int iY; + + void *values = NULL; + uint32_t valueslen = 0; + void *ptr = NULL; + + assert(NULL != ds); + + /* raster size */ + width = GDALGetRasterXSize(ds); + height = GDALGetRasterYSize(ds); + RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", width, height); + + /* create new raster */ + RASTER_DEBUG(3, "Creating new raster"); + rast = rt_raster_new(width, height); + if (NULL == rast) { + rterror("rt_raster_from_gdal_dataset: Out of memory allocating new raster"); + return NULL; + } + RASTER_DEBUGF(3, "Created raster dimensions (width x height): %d x %d", rast->width, rast->height); + + /* get raster attributes */ + cplerr = GDALGetGeoTransform(ds, gt); + if (GDALGetGeoTransform(ds, gt) != CE_None) { + RASTER_DEBUG(4, "Using default geotransform matrix (0, 1, 0, 0, 0, -1)"); + gt[0] = 0; + gt[1] = 1; + gt[2] = 0; + gt[3] = 0; + gt[4] = 0; + gt[5] = -1; + } + + /* apply raster attributes */ + rt_raster_set_geotransform_matrix(rast, gt); + + RASTER_DEBUGF(3, "Raster geotransform (%f, %f, %f, %f, %f, %f)", + gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); + + /* srid */ + if (rt_util_gdal_sr_auth_info(ds, &authname, &authcode) == ES_NONE) { + if ( + authname != NULL && + strcmp(authname, "EPSG") == 0 && + authcode != NULL + ) { + rt_raster_set_srid(rast, atoi(authcode)); + RASTER_DEBUGF(3, "New raster's SRID = %d", rast->srid); + } + + if (authname != NULL) + rtdealloc(authname); + if (authcode != NULL) + rtdealloc(authcode); + } + + numBands = GDALGetRasterCount(ds); + +#if POSTGIS_DEBUG_LEVEL > 3 + for (i = 1; i <= numBands; i++) { + GDALRasterBandH _grb = NULL; + double _min; + double _max; + double _mean; + double _stddev; + + _grb = GDALGetRasterBand(ds, i); + GDALComputeRasterStatistics(_grb, FALSE, &_min, &_max, &_mean, &_stddev, NULL, NULL); + RASTER_DEBUGF(4, "GDAL Band %d stats: %f, %f, %f, %f", i, _min, _max, _mean, _stddev); + } +#endif + + /* copy bands */ + for (i = 1; i <= numBands; i++) { + RASTER_DEBUGF(3, "Processing band %d of %d", i, numBands); + gdband = NULL; + gdband = GDALGetRasterBand(ds, i); + if (NULL == gdband) { + rterror("rt_raster_from_gdal_dataset: Could not get GDAL band"); + rt_raster_destroy(rast); + return NULL; + } + RASTER_DEBUGF(4, "gdband @ %p", gdband); + + /* pixtype */ + gdpixtype = GDALGetRasterDataType(gdband); + RASTER_DEBUGF(4, "gdpixtype, size = %s, %d", GDALGetDataTypeName(gdpixtype), GDALGetDataTypeSize(gdpixtype) / 8); + pt = rt_util_gdal_datatype_to_pixtype(gdpixtype); + if (pt == PT_END) { + rterror("rt_raster_from_gdal_dataset: Unknown pixel type for GDAL band"); + rt_raster_destroy(rast); + return NULL; + } + ptlen = rt_pixtype_size(pt); + + /* size: width and height */ + width = GDALGetRasterBandXSize(gdband); + height = GDALGetRasterBandYSize(gdband); + RASTER_DEBUGF(3, "GDAL band dimensions (width x height): %d x %d", width, height); + + /* nodata */ + nodataval = GDALGetRasterNoDataValue(gdband, &hasnodata); + RASTER_DEBUGF(3, "(hasnodata, nodataval) = (%d, %f)", hasnodata, nodataval); + + /* create band object */ + idx = rt_raster_generate_new_band( + rast, pt, + (hasnodata ? nodataval : 0), + hasnodata, nodataval, rt_raster_get_num_bands(rast) + ); + if (idx < 0) { + rterror("rt_raster_from_gdal_dataset: Could not allocate memory for raster band"); + rt_raster_destroy(rast); + return NULL; + } + band = rt_raster_get_band(rast, idx); + RASTER_DEBUGF(3, "Created band of dimension (width x height): %d x %d", band->width, band->height); + + /* this makes use of GDAL's "natural" blocks */ + GDALGetBlockSize(gdband, &nXBlockSize, &nYBlockSize); + nXBlocks = (width + nXBlockSize - 1) / nXBlockSize; + nYBlocks = (height + nYBlockSize - 1) / nYBlockSize; + RASTER_DEBUGF(4, "(nXBlockSize, nYBlockSize) = (%d, %d)", nXBlockSize, nYBlockSize); + RASTER_DEBUGF(4, "(nXBlocks, nYBlocks) = (%d, %d)", nXBlocks, nYBlocks); + + /* allocate memory for values */ + valueslen = ptlen * nXBlockSize * nYBlockSize; + switch (gdpixtype) { + case GDT_Byte: + values = (uint8_t *) rtalloc(valueslen); + break; + case GDT_UInt16: + values = (uint16_t *) rtalloc(valueslen); + break; + case GDT_Int16: + values = (int16_t *) rtalloc(valueslen); + break; + case GDT_UInt32: + values = (uint32_t *) rtalloc(valueslen); + break; + case GDT_Int32: + values = (int32_t *) rtalloc(valueslen); + break; + case GDT_Float32: + values = (float *) rtalloc(valueslen); + break; + case GDT_Float64: + values = (double *) rtalloc(valueslen); + break; + default: + /* should NEVER get here */ + rterror("rt_raster_from_gdal_dataset: Could not allocate memory for unknown pixel type"); + rt_raster_destroy(rast); + return NULL; + } + if (values == NULL) { + rterror("rt_raster_from_gdal_dataset: Could not allocate memory for GDAL band pixel values"); + rt_raster_destroy(rast); + return NULL; + } + RASTER_DEBUGF(3, "values @ %p of length = %d", values, valueslen); + + for (iYBlock = 0; iYBlock < nYBlocks; iYBlock++) { + for (iXBlock = 0; iXBlock < nXBlocks; iXBlock++) { + x = iXBlock * nXBlockSize; + y = iYBlock * nYBlockSize; + RASTER_DEBUGF(4, "(iXBlock, iYBlock) = (%d, %d)", iXBlock, iYBlock); + RASTER_DEBUGF(4, "(x, y) = (%d, %d)", x, y); + + memset(values, 0, valueslen); + + /* valid block width */ + if ((iXBlock + 1) * nXBlockSize > width) + nXValid = width - (iXBlock * nXBlockSize); + else + nXValid = nXBlockSize; + + /* valid block height */ + if ((iYBlock + 1) * nYBlockSize > height) + nYValid = height - (iYBlock * nYBlockSize); + else + nYValid = nYBlockSize; + + RASTER_DEBUGF(4, "(nXValid, nYValid) = (%d, %d)", nXValid, nYValid); + + cplerr = GDALRasterIO( + gdband, GF_Read, + x, y, + nXValid, nYValid, + values, nXValid, nYValid, + gdpixtype, + 0, 0 + ); + if (cplerr != CE_None) { + rterror("rt_raster_from_gdal_dataset: Could not get data from GDAL raster"); + rtdealloc(values); + rt_raster_destroy(rast); + return NULL; + } + + /* if block width is same as raster width, shortcut */ + if (nXBlocks == 1 && nYBlockSize > 1 && nXValid == width) { + x = 0; + y = nYBlockSize * iYBlock; + + RASTER_DEBUGF(4, "Setting set of pixel lines at (%d, %d) for %d pixels", x, y, nXValid * nYValid); + rt_band_set_pixel_line(band, x, y, values, nXValid * nYValid); + } + else { + ptr = values; + x = nXBlockSize * iXBlock; + for (iY = 0; iY < nYValid; iY++) { + y = iY + (nYBlockSize * iYBlock); + + RASTER_DEBUGF(4, "Setting pixel line at (%d, %d) for %d pixels", x, y, nXValid); + rt_band_set_pixel_line(band, x, y, ptr, nXValid); + ptr += (nXValid * ptlen); + } + } + } + } + + /* free memory */ + rtdealloc(values); + } + + return rast; +} + +/****************************************************************************** +* rt_raster_gdal_rasterize() +******************************************************************************/ + +typedef struct _rti_rasterize_arg_t* _rti_rasterize_arg; +struct _rti_rasterize_arg_t { + uint8_t noband; + + uint32_t numbands; + + rt_pixtype *pixtype; + double *init; + double *nodata; + uint8_t *hasnodata; + double *value; + int *bandlist; +}; + +static _rti_rasterize_arg +_rti_rasterize_arg_init() { + _rti_rasterize_arg arg = NULL; + + arg = rtalloc(sizeof(struct _rti_rasterize_arg_t)); + if (arg == NULL) { + rterror("_rti_rasterize_arg_init: Could not allocate memory for _rti_rasterize_arg"); + return NULL; + } + + arg->noband = 0; + + arg->numbands = 0; + arg->pixtype = NULL; + arg->init = NULL; + arg->nodata = NULL; + arg->hasnodata = NULL; + arg->value = NULL; + arg->bandlist = NULL; + + return arg; +} + +static void +_rti_rasterize_arg_destroy(_rti_rasterize_arg arg) { + if (arg->noband) { + if (arg->pixtype != NULL) + rtdealloc(arg->pixtype); + if (arg->init != NULL) + rtdealloc(arg->init); + if (arg->nodata != NULL) + rtdealloc(arg->nodata); + if (arg->hasnodata != NULL) + rtdealloc(arg->hasnodata); + if (arg->value != NULL) + rtdealloc(arg->value); + } + + if (arg->bandlist != NULL) + rtdealloc(arg->bandlist); + + rtdealloc(arg); +} + +/** + * Return a raster of the provided geometry + * + * @param wkb : WKB representation of the geometry to convert + * @param wkb_len : length of the WKB representation of the geometry + * @param srs : the geometry's coordinate system in OGC WKT + * @param num_bands : number of bands in the output raster + * @param pixtype : data type of each band + * @param init : array of values to initialize each band with + * @param value : array of values for pixels of geometry + * @param nodata : array of nodata values for each band + * @param hasnodata : array flagging the presence of nodata for each band + * @param width : the number of columns of the raster + * @param height : the number of rows of the raster + * @param scale_x : the pixel width of the raster + * @param scale_y : the pixel height of the raster + * @param ul_xw : the X value of upper-left corner of the raster + * @param ul_yw : the Y value of upper-left corner of the raster + * @param grid_xw : the X value of point on grid to align raster to + * @param grid_yw : the Y value of point on grid to align raster to + * @param skew_x : the X skew of the raster + * @param skew_y : the Y skew of the raster + * @param options : array of options. only option is "ALL_TOUCHED" + * + * @return the raster of the provided geometry or NULL + */ +rt_raster +rt_raster_gdal_rasterize( + const unsigned char *wkb, uint32_t wkb_len, + const char *srs, + uint32_t num_bands, rt_pixtype *pixtype, + double *init, double *value, + double *nodata, uint8_t *hasnodata, + int *width, int *height, + double *scale_x, double *scale_y, + double *ul_xw, double *ul_yw, + double *grid_xw, double *grid_yw, + double *skew_x, double *skew_y, + char **options +) { + rt_raster rast = NULL; + int i = 0; + int err = 0; + + _rti_rasterize_arg arg = NULL; + + int _dim[2] = {0}; + double _scale[2] = {0}; + double _skew[2] = {0}; + + OGRErr ogrerr; + OGRSpatialReferenceH src_sr = NULL; + OGRGeometryH src_geom; + OGREnvelope src_env; + rt_envelope extent; + OGRwkbGeometryType wkbtype = wkbUnknown; + + int ul_user = 0; + + CPLErr cplerr; + double _gt[6] = {0}; + GDALDriverH _drv = NULL; + GDALDatasetH _ds = NULL; + GDALRasterBandH _band = NULL; + + uint16_t _width = 0; + uint16_t _height = 0; + + RASTER_DEBUG(3, "starting"); + + assert(NULL != wkb); + assert(0 != wkb_len); + + /* internal variables */ + arg = _rti_rasterize_arg_init(); + if (arg == NULL) { + rterror("rt_raster_gdal_rasterize: Could not initialize internal variables"); + return NULL; + } + + /* no bands, raster is a mask */ + if (num_bands < 1) { + arg->noband = 1; + arg->numbands = 1; + + arg->pixtype = (rt_pixtype *) rtalloc(sizeof(rt_pixtype)); + arg->pixtype[0] = PT_8BUI; + + arg->init = (double *) rtalloc(sizeof(double)); + arg->init[0] = 0; + + arg->nodata = (double *) rtalloc(sizeof(double)); + arg->nodata[0] = 0; + + arg->hasnodata = (uint8_t *) rtalloc(sizeof(uint8_t)); + arg->hasnodata[0] = 1; + + arg->value = (double *) rtalloc(sizeof(double)); + arg->value[0] = 1; + } + else { + arg->noband = 0; + arg->numbands = num_bands; + + arg->pixtype = pixtype; + arg->init = init; + arg->nodata = nodata; + arg->hasnodata = hasnodata; + arg->value = value; + } + + /* OGR spatial reference */ + if (NULL != srs && strlen(srs)) { + src_sr = OSRNewSpatialReference(NULL); + if (OSRSetFromUserInput(src_sr, srs) != OGRERR_NONE) { + rterror("rt_raster_gdal_rasterize: Could not create OSR spatial reference using the provided srs: %s", srs); + _rti_rasterize_arg_destroy(arg); + return NULL; + } + } + + /* convert WKB to OGR Geometry */ + ogrerr = OGR_G_CreateFromWkb((unsigned char *) wkb, src_sr, &src_geom, wkb_len); + if (ogrerr != OGRERR_NONE) { + rterror("rt_raster_gdal_rasterize: Could not create OGR Geometry from WKB"); + + _rti_rasterize_arg_destroy(arg); + + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* OGR Geometry is empty */ + if (OGR_G_IsEmpty(src_geom)) { + rtinfo("Geometry provided is empty. Returning empty raster"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return rt_raster_new(0, 0); + } + + /* get envelope */ + OGR_G_GetEnvelope(src_geom, &src_env); + rt_util_from_ogr_envelope(src_env, &extent); + + RASTER_DEBUGF(3, "Suggested raster envelope: %f, %f, %f, %f", + extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); + + /* user-defined scale */ + if ( + (NULL != scale_x) && + (NULL != scale_y) && + (FLT_NEQ(*scale_x, 0.0)) && + (FLT_NEQ(*scale_y, 0.0)) + ) { + /* for now, force scale to be in left-right, top-down orientation */ + _scale[0] = fabs(*scale_x); + _scale[1] = fabs(*scale_y); + } + /* user-defined width/height */ + else if ( + (NULL != width) && + (NULL != height) && + (FLT_NEQ(*width, 0.0)) && + (FLT_NEQ(*height, 0.0)) + ) { + _dim[0] = fabs(*width); + _dim[1] = fabs(*height); + + if (FLT_NEQ(extent.MaxX, extent.MinX)) + _scale[0] = fabs((extent.MaxX - extent.MinX) / _dim[0]); + else + _scale[0] = 1.; + + if (FLT_NEQ(extent.MaxY, extent.MinY)) + _scale[1] = fabs((extent.MaxY - extent.MinY) / _dim[1]); + else + _scale[1] = 1.; + } + else { + rterror("rt_raster_gdal_rasterize: Values must be provided for width and height or X and Y of scale"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + RASTER_DEBUGF(3, "scale (x, y) = %f, %f", _scale[0], -1 * _scale[1]); + RASTER_DEBUGF(3, "dim (x, y) = %d, %d", _dim[0], _dim[1]); + + /* user-defined skew */ + if (NULL != skew_x) { + _skew[0] = *skew_x; + + /* + negative scale-x affects skew + for now, force skew to be in left-right, top-down orientation + */ + if ( + NULL != scale_x && + *scale_x < 0. + ) { + _skew[0] *= -1; + } + } + if (NULL != skew_y) { + _skew[1] = *skew_y; + + /* + positive scale-y affects skew + for now, force skew to be in left-right, top-down orientation + */ + if ( + NULL != scale_y && + *scale_y > 0. + ) { + _skew[1] *= -1; + } + } + + /* + if geometry is a point, a linestring or set of either and bounds not set, + increase extent by a pixel to avoid missing points on border + + a whole pixel is used instead of half-pixel due to backward + compatibility with GDAL 1.6, 1.7 and 1.8. 1.9+ works fine with half-pixel. + */ + wkbtype = wkbFlatten(OGR_G_GetGeometryType(src_geom)); + if (( + (wkbtype == wkbPoint) || + (wkbtype == wkbMultiPoint) || + (wkbtype == wkbLineString) || + (wkbtype == wkbMultiLineString) + ) && + FLT_EQ(_dim[0], 0) && + FLT_EQ(_dim[1], 0) + ) { + int result; + LWPOLY *epoly = NULL; + LWGEOM *lwgeom = NULL; + GEOSGeometry *egeom = NULL; + GEOSGeometry *geom = NULL; + + RASTER_DEBUG(3, "Testing geometry is properly contained by extent"); + + /* + see if geometry is properly contained by extent + all parts of geometry lies within extent + */ + + /* initialize GEOS */ + initGEOS(lwnotice, lwgeom_geos_error); + + /* convert envelope to geometry */ + RASTER_DEBUG(4, "Converting envelope to geometry"); + epoly = rt_util_envelope_to_lwpoly(extent); + if (epoly == NULL) { + rterror("rt_raster_gdal_rasterize: Could not create envelope's geometry to test if geometry is properly contained by extent"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + egeom = (GEOSGeometry *) LWGEOM2GEOS(lwpoly_as_lwgeom(epoly)); + lwpoly_free(epoly); + + /* convert WKB to geometry */ + RASTER_DEBUG(4, "Converting WKB to geometry"); + lwgeom = lwgeom_from_wkb(wkb, wkb_len, LW_PARSER_CHECK_NONE); + geom = (GEOSGeometry *) LWGEOM2GEOS(lwgeom); + lwgeom_free(lwgeom); + + result = GEOSRelatePattern(egeom, geom, "T**FF*FF*"); + GEOSGeom_destroy(geom); + GEOSGeom_destroy(egeom); + + if (result == 2) { + rterror("rt_raster_gdal_rasterize: Could not test if geometry is properly contained by extent for geometry within extent"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* geometry NOT properly contained by extent */ + if (!result) { + +#if POSTGIS_GDAL_VERSION > 18 + + /* check alignment flag: grid_xw */ + if ( + (NULL == ul_xw && NULL == ul_yw) && + (NULL != grid_xw && NULL != grid_xw) && + FLT_NEQ(*grid_xw, extent.MinX) + ) { + // do nothing + RASTER_DEBUG(3, "Skipping extent adjustment on X-axis due to upcoming alignment"); + } + else { + RASTER_DEBUG(3, "Adjusting extent for GDAL > 1.8 by half the scale on X-axis"); + extent.MinX -= (_scale[0] / 2.); + extent.MaxX += (_scale[0] / 2.); + } + + /* check alignment flag: grid_yw */ + if ( + (NULL == ul_xw && NULL == ul_yw) && + (NULL != grid_xw && NULL != grid_xw) && + FLT_NEQ(*grid_yw, extent.MaxY) + ) { + // do nothing + RASTER_DEBUG(3, "Skipping extent adjustment on Y-axis due to upcoming alignment"); + } + else { + RASTER_DEBUG(3, "Adjusting extent for GDAL > 1.8 by half the scale on Y-axis"); + extent.MinY -= (_scale[1] / 2.); + extent.MaxY += (_scale[1] / 2.); + } + +#else + + /* check alignment flag: grid_xw */ + if ( + (NULL == ul_xw && NULL == ul_yw) && + (NULL != grid_xw && NULL != grid_xw) && + FLT_NEQ(*grid_xw, extent.MinX) + ) { + // do nothing + RASTER_DEBUG(3, "Skipping extent adjustment on X-axis due to upcoming alignment"); + } + else { + RASTER_DEBUG(3, "Adjusting extent for GDAL <= 1.8 by the scale on X-axis"); + extent.MinX -= _scale[0]; + extent.MaxX += _scale[0]; + } + + + /* check alignment flag: grid_yw */ + if ( + (NULL == ul_xw && NULL == ul_yw) && + (NULL != grid_xw && NULL != grid_xw) && + FLT_NEQ(*grid_yw, extent.MaxY) + ) { + // do nothing + RASTER_DEBUG(3, "Skipping extent adjustment on Y-axis due to upcoming alignment"); + } + else { + RASTER_DEBUG(3, "Adjusting extent for GDAL <= 1.8 by the scale on Y-axis"); + extent.MinY -= _scale[1]; + extent.MaxY += _scale[1]; + } + +#endif + + } + + RASTER_DEBUGF(3, "Adjusted extent: %f, %f, %f, %f", + extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); + + extent.UpperLeftX = extent.MinX; + extent.UpperLeftY = extent.MaxY; + } + + /* reprocess extent if skewed */ + if ( + FLT_NEQ(_skew[0], 0) || + FLT_NEQ(_skew[1], 0) + ) { + rt_raster skewedrast; + + RASTER_DEBUG(3, "Computing skewed extent's envelope"); + + skewedrast = rt_raster_compute_skewed_raster( + extent, + _skew, + _scale, + 0.01 + ); + if (skewedrast == NULL) { + rterror("rt_raster_gdal_rasterize: Could not compute skewed raster"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + _dim[0] = skewedrast->width; + _dim[1] = skewedrast->height; + + extent.UpperLeftX = skewedrast->ipX; + extent.UpperLeftY = skewedrast->ipY; + + rt_raster_destroy(skewedrast); + } + + /* raster dimensions */ + if (!_dim[0]) + _dim[0] = (int) fmax((fabs(extent.MaxX - extent.MinX) + (_scale[0] / 2.)) / _scale[0], 1); + if (!_dim[1]) + _dim[1] = (int) fmax((fabs(extent.MaxY - extent.MinY) + (_scale[1] / 2.)) / _scale[1], 1); + + /* temporary raster */ + rast = rt_raster_new(_dim[0], _dim[1]); + if (rast == NULL) { + rterror("rt_raster_gdal_rasterize: Out of memory allocating temporary raster"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* set raster's spatial attributes */ + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + rt_raster_set_scale(rast, _scale[0], -1 * _scale[1]); + rt_raster_set_skews(rast, _skew[0], _skew[1]); + + rt_raster_get_geotransform_matrix(rast, _gt); + RASTER_DEBUGF(3, "Temp raster's geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); + RASTER_DEBUGF(3, "Temp raster's dimensions (width x height): %d x %d", + _dim[0], _dim[1]); + + /* user-specified upper-left corner */ + if ( + NULL != ul_xw && + NULL != ul_yw + ) { + ul_user = 1; + + RASTER_DEBUGF(4, "Using user-specified upper-left corner: %f, %f", *ul_xw, *ul_yw); + + /* set upper-left corner */ + rt_raster_set_offsets(rast, *ul_xw, *ul_yw); + extent.UpperLeftX = *ul_xw; + extent.UpperLeftY = *ul_yw; + } + else if ( + ((NULL != ul_xw) && (NULL == ul_yw)) || + ((NULL == ul_xw) && (NULL != ul_yw)) + ) { + rterror("rt_raster_gdal_rasterize: Both X and Y upper-left corner values must be provided"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* alignment only considered if upper-left corner not provided */ + if ( + !ul_user && ( + (NULL != grid_xw) || (NULL != grid_yw) + ) + ) { + + if ( + ((NULL != grid_xw) && (NULL == grid_yw)) || + ((NULL == grid_xw) && (NULL != grid_yw)) + ) { + rterror("rt_raster_gdal_rasterize: Both X and Y alignment values must be provided"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + RASTER_DEBUGF(4, "Aligning extent to user-specified grid: %f, %f", *grid_xw, *grid_yw); + + do { + double _r[2] = {0}; + double _w[2] = {0}; + + /* raster is already aligned */ + if (FLT_EQ(*grid_xw, extent.UpperLeftX) && FLT_EQ(*grid_yw, extent.UpperLeftY)) { + RASTER_DEBUG(3, "Skipping raster alignment as it is already aligned to grid"); + break; + } + + extent.UpperLeftX = rast->ipX; + extent.UpperLeftY = rast->ipY; + rt_raster_set_offsets(rast, *grid_xw, *grid_yw); + + /* process upper-left corner */ + if (rt_raster_geopoint_to_cell( + rast, + extent.UpperLeftX, extent.UpperLeftY, + &(_r[0]), &(_r[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute raster pixel for spatial coordinates"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + if (rt_raster_cell_to_geopoint( + rast, + _r[0], _r[1], + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* shift occurred */ + if (FLT_NEQ(_w[0], extent.UpperLeftX)) { + if (NULL == width) + rast->width++; + else if (NULL == scale_x) { + double _c[2] = {0}; + + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + + /* get upper-right corner */ + if (rt_raster_cell_to_geopoint( + rast, + rast->width, 0, + &(_c[0]), &(_c[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + rast->scaleX = fabs((_c[0] - _w[0]) / ((double) rast->width)); + } + } + if (FLT_NEQ(_w[1], extent.UpperLeftY)) { + if (NULL == height) + rast->height++; + else if (NULL == scale_y) { + double _c[2] = {0}; + + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + + /* get upper-right corner */ + if (rt_raster_cell_to_geopoint( + rast, + 0, rast->height, + &(_c[0]), &(_c[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + rast->scaleY = -1 * fabs((_c[1] - _w[1]) / ((double) rast->height)); + } + } + + rt_raster_set_offsets(rast, _w[0], _w[1]); + } + while (0); + } + + /* + after this point, rt_envelope extent is no longer used + */ + + /* get key attributes from rast */ + _dim[0] = rast->width; + _dim[1] = rast->height; + rt_raster_get_geotransform_matrix(rast, _gt); + + /* scale-x is negative or scale-y is positive */ + if (( + (NULL != scale_x) && (*scale_x < 0.) + ) || ( + (NULL != scale_y) && (*scale_y > 0) + )) { + double _w[2] = {0}; + + /* negative scale-x */ + if ( + (NULL != scale_x) && + (*scale_x < 0.) + ) { + RASTER_DEBUG(3, "Processing negative scale-x"); + + if (rt_raster_cell_to_geopoint( + rast, + _dim[0], 0, + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + _gt[0] = _w[0]; + _gt[1] = *scale_x; + + /* check for skew */ + if (NULL != skew_x && FLT_NEQ(*skew_x, 0)) + _gt[2] = *skew_x; + } + /* positive scale-y */ + if ( + (NULL != scale_y) && + (*scale_y > 0) + ) { + RASTER_DEBUG(3, "Processing positive scale-y"); + + if (rt_raster_cell_to_geopoint( + rast, + 0, _dim[1], + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + _gt[3] = _w[1]; + _gt[5] = *scale_y; + + /* check for skew */ + if (NULL != skew_y && FLT_NEQ(*skew_y, 0)) + _gt[4] = *skew_y; + } + } + + rt_raster_destroy(rast); + rast = NULL; + + RASTER_DEBUGF(3, "Applied geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); + RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", + _dim[0], _dim[1]); + + /* load GDAL mem */ + if (!rt_util_gdal_driver_registered("MEM")) + GDALRegister_MEM(); + _drv = GDALGetDriverByName("MEM"); + if (NULL == _drv) { + rterror("rt_raster_gdal_rasterize: Could not load the MEM GDAL driver"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + _ds = GDALCreate(_drv, "", _dim[0], _dim[1], 0, GDT_Byte, NULL); + if (NULL == _ds) { + rterror("rt_raster_gdal_rasterize: Could not create a GDALDataset to rasterize the geometry into"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + return NULL; + } + + /* set geotransform */ + cplerr = GDALSetGeoTransform(_ds, _gt); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not set geotransform on GDALDataset"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + GDALClose(_ds); + + return NULL; + } + + /* set SRS */ + if (NULL != src_sr) { + char *_srs = NULL; + OSRExportToWkt(src_sr, &_srs); + + cplerr = GDALSetProjection(_ds, _srs); + CPLFree(_srs); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not set projection on GDALDataset"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + GDALClose(_ds); + + return NULL; + } + } + + /* set bands */ + for (i = 0; i < arg->numbands; i++) { + err = 0; + + do { + /* add band */ + cplerr = GDALAddBand(_ds, rt_util_pixtype_to_gdal_datatype(arg->pixtype[i]), NULL); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not add band to GDALDataset"); + err = 1; + break; + } + + _band = GDALGetRasterBand(_ds, i + 1); + if (NULL == _band) { + rterror("rt_raster_gdal_rasterize: Could not get band %d from GDALDataset", i + 1); + err = 1; + break; + } + + /* nodata value */ + if (arg->hasnodata[i]) { + RASTER_DEBUGF(4, "Setting NODATA value of band %d to %f", i, arg->nodata[i]); + cplerr = GDALSetRasterNoDataValue(_band, arg->nodata[i]); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not set nodata value"); + err = 1; + break; + } + RASTER_DEBUGF(4, "NODATA value set to %f", GDALGetRasterNoDataValue(_band, NULL)); + } + + /* initial value */ + cplerr = GDALFillRaster(_band, arg->init[i], 0); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not set initial value"); + err = 1; + break; + } + } + while (0); + + if (err) { + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + GDALClose(_ds); + + return NULL; + } + } + + arg->bandlist = (int *) rtalloc(sizeof(int) * arg->numbands); + for (i = 0; i < arg->numbands; i++) arg->bandlist[i] = i + 1; + + + /* burn geometry */ + cplerr = GDALRasterizeGeometries( + _ds, + arg->numbands, arg->bandlist, + 1, &src_geom, + NULL, NULL, + arg->value, + options, + NULL, NULL + ); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_rasterize: Could not rasterize geometry"); + + _rti_rasterize_arg_destroy(arg); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + GDALClose(_ds); + + return NULL; + } + + /* convert gdal dataset to raster */ + GDALFlushCache(_ds); + RASTER_DEBUG(3, "Converting GDAL dataset to raster"); + rast = rt_raster_from_gdal_dataset(_ds); + + OGR_G_DestroyGeometry(src_geom); + if (src_sr != NULL) OSRDestroySpatialReference(src_sr); + /* OGRCleanupAll(); */ + + GDALClose(_ds); + + if (NULL == rast) { + rterror("rt_raster_gdal_rasterize: Could not rasterize geometry"); + return NULL; + } + + /* width, height */ + _width = rt_raster_get_width(rast); + _height = rt_raster_get_height(rast); + + /* check each band for pixtype */ + for (i = 0; i < arg->numbands; i++) { + uint8_t *data = NULL; + rt_band band = NULL; + rt_band oldband = NULL; + + double val = 0; + int nodata = 0; + int hasnodata = 0; + double nodataval = 0; + int x = 0; + int y = 0; + + oldband = rt_raster_get_band(rast, i); + if (oldband == NULL) { + rterror("rt_raster_gdal_rasterize: Could not get band %d of output raster", i); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + return NULL; + } + + /* band is of user-specified type */ + if (rt_band_get_pixtype(oldband) == arg->pixtype[i]) + continue; + + /* hasnodata, nodataval */ + hasnodata = rt_band_get_hasnodata_flag(oldband); + if (hasnodata) + rt_band_get_nodata(oldband, &nodataval); + + /* allocate data */ + data = rtalloc(rt_pixtype_size(arg->pixtype[i]) * _width * _height); + if (data == NULL) { + rterror("rt_raster_gdal_rasterize: Could not allocate memory for band data"); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + return NULL; + } + memset(data, 0, rt_pixtype_size(arg->pixtype[i]) * _width * _height); + + /* create new band of correct type */ + band = rt_band_new_inline( + _width, _height, + arg->pixtype[i], + hasnodata, nodataval, + data + ); + if (band == NULL) { + rterror("rt_raster_gdal_rasterize: Could not create band"); + rtdealloc(data); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + return NULL; + } + + /* give ownership of data to band */ + rt_band_set_ownsdata_flag(band, 1); + + /* copy pixel by pixel */ + for (x = 0; x < _width; x++) { + for (y = 0; y < _height; y++) { + err = rt_band_get_pixel(oldband, x, y, &val, &nodata); + if (err != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not get pixel value"); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + rt_band_destroy(band); + return NULL; + } + + if (nodata) + val = nodataval; + + err = rt_band_set_pixel(band, x, y, val, NULL); + if (err != ES_NONE) { + rterror("rt_raster_gdal_rasterize: Could not set pixel value"); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + rt_band_destroy(band); + return NULL; + } + } + } + + /* replace band */ + oldband = rt_raster_replace_band(rast, band, i); + if (oldband == NULL) { + rterror("rt_raster_gdal_rasterize: Could not replace band %d of output raster", i); + _rti_rasterize_arg_destroy(arg); + rt_raster_destroy(rast); + rt_band_destroy(band); + return NULL; + } + + /* free oldband */ + rt_band_destroy(oldband); + } + + _rti_rasterize_arg_destroy(arg); + + RASTER_DEBUG(3, "done"); + + return rast; +} + +/****************************************************************************** +* rt_raster_from_two_rasters() +******************************************************************************/ + +/* + * Return raster of computed extent specified extenttype applied + * on two input rasters. The raster returned should be freed by + * the caller + * + * @param rast1 : the first raster + * @param rast2 : the second raster + * @param extenttype : type of extent for the output raster + * @param *rtnraster : raster of computed extent + * @param *offset : 4-element array indicating the X,Y offsets + * for each raster. 0,1 for rast1 X,Y. 2,3 for rast2 X,Y. + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_from_two_rasters( + rt_raster rast1, rt_raster rast2, + rt_extenttype extenttype, + rt_raster *rtnraster, double *offset +) { + int i; + + rt_raster _rast[2] = {rast1, rast2}; + double _offset[2][4] = {{0.}}; + uint16_t _dim[2][2] = {{0}}; + + rt_raster raster = NULL; + int aligned = 0; + int dim[2] = {0}; + double gt[6] = {0.}; + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != rtnraster); + + /* set rtnraster to NULL */ + *rtnraster = NULL; + + /* rasters must be aligned */ + if (rt_raster_same_alignment(rast1, rast2, &aligned, NULL) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not test for alignment on the two rasters"); + return ES_ERROR; + } + if (!aligned) { + rterror("rt_raster_from_two_rasters: The two rasters provided do not have the same alignment"); + return ES_ERROR; + } + + /* dimensions */ + _dim[0][0] = rast1->width; + _dim[0][1] = rast1->height; + _dim[1][0] = rast2->width; + _dim[1][1] = rast2->height; + + /* get raster offsets */ + if (rt_raster_geopoint_to_cell( + _rast[1], + _rast[0]->ipX, _rast[0]->ipY, + &(_offset[1][0]), &(_offset[1][1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not compute offsets of the second raster relative to the first raster"); + return ES_ERROR; + } + _offset[1][0] = -1 * _offset[1][0]; + _offset[1][1] = -1 * _offset[1][1]; + _offset[1][2] = _offset[1][0] + _dim[1][0] - 1; + _offset[1][3] = _offset[1][1] + _dim[1][1] - 1; + + i = -1; + switch (extenttype) { + case ET_FIRST: + i = 0; + _offset[0][0] = 0.; + _offset[0][1] = 0.; + case ET_LAST: + case ET_SECOND: + if (i < 0) { + i = 1; + _offset[0][0] = -1 * _offset[1][0]; + _offset[0][1] = -1 * _offset[1][1]; + _offset[1][0] = 0.; + _offset[1][1] = 0.; + } + + dim[0] = _dim[i][0]; + dim[1] = _dim[i][1]; + raster = rt_raster_new( + dim[0], + dim[1] + ); + if (raster == NULL) { + rterror("rt_raster_from_two_rasters: Could not create output raster"); + return ES_ERROR; + } + rt_raster_set_srid(raster, _rast[i]->srid); + rt_raster_get_geotransform_matrix(_rast[i], gt); + rt_raster_set_geotransform_matrix(raster, gt); + break; + case ET_UNION: { + double off[4] = {0}; + + rt_raster_get_geotransform_matrix(_rast[0], gt); + RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", + gt[0], + gt[1], + gt[2], + gt[3], + gt[4], + gt[5] + ); + + /* new raster upper left offset */ + off[0] = 0; + if (_offset[1][0] < 0) + off[0] = _offset[1][0]; + off[1] = 0; + if (_offset[1][1] < 0) + off[1] = _offset[1][1]; + + /* new raster lower right offset */ + off[2] = _dim[0][0] - 1; + if ((int) _offset[1][2] >= _dim[0][0]) + off[2] = _offset[1][2]; + off[3] = _dim[0][1] - 1; + if ((int) _offset[1][3] >= _dim[0][1]) + off[3] = _offset[1][3]; + + /* upper left corner */ + if (rt_raster_cell_to_geopoint( + _rast[0], + off[0], off[1], + &(gt[0]), &(gt[3]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get spatial coordinates of upper-left pixel of output raster"); + return ES_ERROR; + } + + dim[0] = off[2] - off[0] + 1; + dim[1] = off[3] - off[1] + 1; + RASTER_DEBUGF(4, "off = (%f, %f, %f, %f)", + off[0], + off[1], + off[2], + off[3] + ); + RASTER_DEBUGF(4, "dim = (%d, %d)", dim[0], dim[1]); + + raster = rt_raster_new( + dim[0], + dim[1] + ); + if (raster == NULL) { + rterror("rt_raster_from_two_rasters: Could not create output raster"); + return ES_ERROR; + } + rt_raster_set_srid(raster, _rast[0]->srid); + rt_raster_set_geotransform_matrix(raster, gt); + RASTER_DEBUGF(4, "gt = (%f, %f, %f, %f, %f, %f)", + gt[0], + gt[1], + gt[2], + gt[3], + gt[4], + gt[5] + ); + + /* get offsets */ + if (rt_raster_geopoint_to_cell( + _rast[0], + gt[0], gt[3], + &(_offset[0][0]), &(_offset[0][1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get offsets of the FIRST raster relative to the output raster"); + rt_raster_destroy(raster); + return ES_ERROR; + } + _offset[0][0] *= -1; + _offset[0][1] *= -1; + + if (rt_raster_geopoint_to_cell( + _rast[1], + gt[0], gt[3], + &(_offset[1][0]), &(_offset[1][1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get offsets of the SECOND raster relative to the output raster"); + rt_raster_destroy(raster); + return ES_ERROR; + } + _offset[1][0] *= -1; + _offset[1][1] *= -1; + break; + } + case ET_INTERSECTION: { + double off[4] = {0}; + + /* no intersection */ + if ( + (_offset[1][2] < 0 || _offset[1][0] > (_dim[0][0] - 1)) || + (_offset[1][3] < 0 || _offset[1][1] > (_dim[0][1] - 1)) + ) { + RASTER_DEBUG(3, "The two rasters provided have no intersection. Returning no band raster"); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + rterror("rt_raster_from_two_rasters: Could not create output raster"); + return ES_ERROR; + } + rt_raster_set_srid(raster, _rast[0]->srid); + rt_raster_set_scale(raster, 0, 0); + + /* set offsets if provided */ + if (NULL != offset) { + for (i = 0; i < 4; i++) + offset[i] = _offset[i / 2][i % 2]; + } + + *rtnraster = raster; + return ES_NONE; + } + + if (_offset[1][0] > 0) + off[0] = _offset[1][0]; + if (_offset[1][1] > 0) + off[1] = _offset[1][1]; + + off[2] = _dim[0][0] - 1; + if (_offset[1][2] < _dim[0][0]) + off[2] = _offset[1][2]; + off[3] = _dim[0][1] - 1; + if (_offset[1][3] < _dim[0][1]) + off[3] = _offset[1][3]; + + dim[0] = off[2] - off[0] + 1; + dim[1] = off[3] - off[1] + 1; + raster = rt_raster_new( + dim[0], + dim[1] + ); + if (raster == NULL) { + rterror("rt_raster_from_two_rasters: Could not create output raster"); + return ES_ERROR; + } + rt_raster_set_srid(raster, _rast[0]->srid); + + /* get upper-left corner */ + rt_raster_get_geotransform_matrix(_rast[0], gt); + if (rt_raster_cell_to_geopoint( + _rast[0], + off[0], off[1], + &(gt[0]), &(gt[3]), + gt + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get spatial coordinates of upper-left pixel of output raster"); + rt_raster_destroy(raster); + return ES_ERROR; + } + + rt_raster_set_geotransform_matrix(raster, gt); + + /* get offsets */ + if (rt_raster_geopoint_to_cell( + _rast[0], + gt[0], gt[3], + &(_offset[0][0]), &(_offset[0][1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get pixel coordinates to compute the offsets of the FIRST raster relative to the output raster"); + rt_raster_destroy(raster); + return ES_ERROR; + } + _offset[0][0] *= -1; + _offset[0][1] *= -1; + + if (rt_raster_geopoint_to_cell( + _rast[1], + gt[0], gt[3], + &(_offset[1][0]), &(_offset[1][1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_from_two_rasters: Could not get pixel coordinates to compute the offsets of the SECOND raster relative to the output raster"); + rt_raster_destroy(raster); + return ES_ERROR; + } + _offset[1][0] *= -1; + _offset[1][1] *= -1; + break; + } + case ET_CUSTOM: + rterror("rt_raster_from_two_rasters: Extent type ET_CUSTOM is not supported by this function"); + break; + } + + /* set offsets if provided */ + if (NULL != offset) { + for (i = 0; i < 4; i++) + offset[i] = _offset[i / 2][i % 2]; + } + + *rtnraster = raster; + return ES_NONE; +} diff --git a/raster/rt_core/rt_serialize.c b/raster/rt_core/rt_serialize.c new file mode 100644 index 000000000..b4740af60 --- /dev/null +++ b/raster/rt_core/rt_serialize.c @@ -0,0 +1,904 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" +#include "rt_serialize.h" + +/****************************************************************************** +* Debug and Testing Utilities +******************************************************************************/ + +#if POSTGIS_DEBUG_LEVEL > 2 + +char* +d_binary_to_hex(const uint8_t * const raw, uint32_t size, uint32_t *hexsize) { + char* hex = NULL; + uint32_t i = 0; + + + assert(NULL != raw); + assert(NULL != hexsize); + + + *hexsize = size * 2; /* hex is 2 times bytes */ + hex = (char*) rtalloc((*hexsize) + 1); + if (!hex) { + rterror("d_binary_to_hex: Out of memory hexifying raw binary"); + return NULL; + } + hex[*hexsize] = '\0'; /* Null-terminate */ + + for (i = 0; i < size; ++i) { + deparse_hex(raw[i], &(hex[2 * i])); + } + + assert(NULL != hex); + assert(0 == strlen(hex) % 2); + return hex; +} + +void +d_print_binary_hex(const char* msg, const uint8_t * const raw, uint32_t size) { + char* hex = NULL; + uint32_t hexsize = 0; + + + assert(NULL != msg); + assert(NULL != raw); + + + hex = d_binary_to_hex(raw, size, &hexsize); + if (NULL != hex) { + rtinfo("%s\t%s", msg, hex); + rtdealloc(hex); + } +} + +size_t +d_binptr_to_pos(const uint8_t * const ptr, const uint8_t * const end, size_t size) { + assert(NULL != ptr && NULL != end); + + return (size - (end - ptr)); +} + +#endif /* if POSTGIS_DEBUG_LEVEL > 2 */ + + +#ifdef OPTIMIZE_SPACE + +/* + * Set given number of bits of the given byte, + * starting from given bitOffset (from the first) + * to the given value. + * + * Examples: + * char ch; + * ch=0; setBits(&ch, 1, 1, 0) -> ch==8 + * ch=0; setBits(&ch, 3, 2, 1) -> ch==96 (0x60) + * + * Note that number of bits set must be <= 8-bitOffset + * + */ +void +setBits(char* ch, double val, int bits, int bitOffset) { + char mask = 0xFF >> (8 - bits); + char ival = val; + + + assert(ch != NULL); + assert(8 - bitOffset >= bits); + + RASTER_DEBUGF(4, "ival:%d bits:%d mask:%hhx bitoffset:%d\n", + ival, bits, mask, bitOffset); + + /* clear all but significant bits from ival */ + ival &= mask; +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + if (ival != val) { + rtwarn("Pixel value for %d-bits band got truncated" + " from %g to %hhu", bits, val, ival); + } +#endif /* POSTGIS_RASTER_WARN_ON_TRUNCATION */ + + RASTER_DEBUGF(4, " cleared ival:%hhx\n", ival); + + + /* Shift ival so the significant bits start at + * the first bit */ + ival <<= (8 - bitOffset - bits); + + RASTER_DEBUGF(4, " ival shifted:%hhx\n", ival); + RASTER_DEBUGF(4, " ch:%hhx\n", *ch); + + /* clear first bits of target */ + *ch &= ~(mask << (8 - bits - bitOffset)); + + RASTER_DEBUGF(4, " ch cleared:%hhx\n", *ch); + + /* Set the first bit of target */ + *ch |= ival; + + RASTER_DEBUGF(4, " ch ored:%hhx\n", *ch); + +} +#endif /* OPTIMIZE_SPACE */ + +void +swap_char(uint8_t *a, uint8_t *b) { + uint8_t c = 0; + + assert(NULL != a && NULL != b); + + c = *a; + *a = *b; + *b = c; +} + +void +flip_endian_16(uint8_t *d) { + assert(NULL != d); + + swap_char(d, d + 1); +} + +void +flip_endian_32(uint8_t *d) { + assert(NULL != d); + + swap_char(d, d + 3); + swap_char(d + 1, d + 2); +} + +void +flip_endian_64(uint8_t *d) { + assert(NULL != d); + + swap_char(d + 7, d); + swap_char(d + 6, d + 1); + swap_char(d + 5, d + 2); + swap_char(d + 4, d + 3); +} + +uint8_t +isMachineLittleEndian(void) { + static int endian_check_int = 1; /* dont modify this!!! */ + /* 0=big endian|xdr -- 1=little endian|ndr */ + return *((uint8_t *) & endian_check_int); +} + +uint8_t +read_uint8(const uint8_t** from) { + assert(NULL != from); + + return *(*from)++; +} + +/* unused up to now +void +write_uint8(uint8_t** from, uint8_t v) { + assert(NULL != from); + + *(*from)++ = v; +} +*/ + +int8_t +read_int8(const uint8_t** from) { + assert(NULL != from); + + return (int8_t) read_uint8(from); +} + +/* unused up to now +void +write_int8(uint8_t** from, int8_t v) { + assert(NULL != from); + + *(*from)++ = v; +} +*/ + +uint16_t +read_uint16(const uint8_t** from, uint8_t littleEndian) { + uint16_t ret = 0; + + assert(NULL != from); + + if (littleEndian) { + ret = (*from)[0] | + (*from)[1] << 8; + } else { + /* big endian */ + ret = (*from)[0] << 8 | + (*from)[1]; + } + *from += 2; + return ret; +} + +void +write_uint16(uint8_t** to, uint8_t littleEndian, uint16_t v) { + assert(NULL != to); + + if (littleEndian) { + (*to)[0] = v & 0x00FF; + (*to)[1] = v >> 8; + } else { + (*to)[1] = v & 0x00FF; + (*to)[0] = v >> 8; + } + *to += 2; +} + +int16_t +read_int16(const uint8_t** from, uint8_t littleEndian) { + assert(NULL != from); + + return read_uint16(from, littleEndian); +} + +/* unused up to now +void +write_int16(uint8_t** to, uint8_t littleEndian, int16_t v) { + assert(NULL != to); + + if ( littleEndian ) + { + (*to)[0] = v & 0x00FF; + (*to)[1] = v >> 8; + } + else + { + (*to)[1] = v & 0x00FF; + (*to)[0] = v >> 8; + } + *to += 2; +} +*/ + +uint32_t +read_uint32(const uint8_t** from, uint8_t littleEndian) { + uint32_t ret = 0; + + assert(NULL != from); + + if (littleEndian) { + ret = (uint32_t) ((*from)[0] & 0xff) | + (uint32_t) ((*from)[1] & 0xff) << 8 | + (uint32_t) ((*from)[2] & 0xff) << 16 | + (uint32_t) ((*from)[3] & 0xff) << 24; + } else { + /* big endian */ + ret = (uint32_t) ((*from)[3] & 0xff) | + (uint32_t) ((*from)[2] & 0xff) << 8 | + (uint32_t) ((*from)[1] & 0xff) << 16 | + (uint32_t) ((*from)[0] & 0xff) << 24; + } + + *from += 4; + return ret; +} + +/* unused up to now +void +write_uint32(uint8_t** to, uint8_t littleEndian, uint32_t v) { + assert(NULL != to); + + if ( littleEndian ) + { + (*to)[0] = v & 0x000000FF; + (*to)[1] = ( v & 0x0000FF00 ) >> 8; + (*to)[2] = ( v & 0x00FF0000 ) >> 16; + (*to)[3] = ( v & 0xFF000000 ) >> 24; + } + else + { + (*to)[3] = v & 0x000000FF; + (*to)[2] = ( v & 0x0000FF00 ) >> 8; + (*to)[1] = ( v & 0x00FF0000 ) >> 16; + (*to)[0] = ( v & 0xFF000000 ) >> 24; + } + *to += 4; +} +*/ + +int32_t +read_int32(const uint8_t** from, uint8_t littleEndian) { + assert(NULL != from); + + return read_uint32(from, littleEndian); +} + +/* unused up to now +void +write_int32(uint8_t** to, uint8_t littleEndian, int32_t v) { + assert(NULL != to); + + if ( littleEndian ) + { + (*to)[0] = v & 0x000000FF; + (*to)[1] = ( v & 0x0000FF00 ) >> 8; + (*to)[2] = ( v & 0x00FF0000 ) >> 16; + (*to)[3] = ( v & 0xFF000000 ) >> 24; + } + else + { + (*to)[3] = v & 0x000000FF; + (*to)[2] = ( v & 0x0000FF00 ) >> 8; + (*to)[1] = ( v & 0x00FF0000 ) >> 16; + (*to)[0] = ( v & 0xFF000000 ) >> 24; + } + *to += 4; +} +*/ + +float +read_float32(const uint8_t** from, uint8_t littleEndian) { + + union { + float f; + uint32_t i; + } ret; + + ret.i = read_uint32(from, littleEndian); + + return ret.f; +} + +/* unused up to now +void +write_float32(uint8_t** from, uint8_t littleEndian, float f) { + union { + float f; + uint32_t i; + } u; + + u.f = f; + write_uint32(from, littleEndian, u.i); +} +*/ + +double +read_float64(const uint8_t** from, uint8_t littleEndian) { + + union { + double d; + uint64_t i; + } ret; + + assert(NULL != from); + + if (littleEndian) { + ret.i = (uint64_t) ((*from)[0] & 0xff) | + (uint64_t) ((*from)[1] & 0xff) << 8 | + (uint64_t) ((*from)[2] & 0xff) << 16 | + (uint64_t) ((*from)[3] & 0xff) << 24 | + (uint64_t) ((*from)[4] & 0xff) << 32 | + (uint64_t) ((*from)[5] & 0xff) << 40 | + (uint64_t) ((*from)[6] & 0xff) << 48 | + (uint64_t) ((*from)[7] & 0xff) << 56; + } else { + /* big endian */ + ret.i = (uint64_t) ((*from)[7] & 0xff) | + (uint64_t) ((*from)[6] & 0xff) << 8 | + (uint64_t) ((*from)[5] & 0xff) << 16 | + (uint64_t) ((*from)[4] & 0xff) << 24 | + (uint64_t) ((*from)[3] & 0xff) << 32 | + (uint64_t) ((*from)[2] & 0xff) << 40 | + (uint64_t) ((*from)[1] & 0xff) << 48 | + (uint64_t) ((*from)[0] & 0xff) << 56; + } + + *from += 8; + return ret.d; +} + +/* unused up to now +void +write_float64(uint8_t** to, uint8_t littleEndian, double v) { + union { + double d; + uint64_t i; + } u; + + assert(NULL != to); + + u.d = v; + + if ( littleEndian ) + { + (*to)[0] = u.i & 0x00000000000000FFULL; + (*to)[1] = ( u.i & 0x000000000000FF00ULL ) >> 8; + (*to)[2] = ( u.i & 0x0000000000FF0000ULL ) >> 16; + (*to)[3] = ( u.i & 0x00000000FF000000ULL ) >> 24; + (*to)[4] = ( u.i & 0x000000FF00000000ULL ) >> 32; + (*to)[5] = ( u.i & 0x0000FF0000000000ULL ) >> 40; + (*to)[6] = ( u.i & 0x00FF000000000000ULL ) >> 48; + (*to)[7] = ( u.i & 0xFF00000000000000ULL ) >> 56; + } + else + { + (*to)[7] = u.i & 0x00000000000000FFULL; + (*to)[6] = ( u.i & 0x000000000000FF00ULL ) >> 8; + (*to)[5] = ( u.i & 0x0000000000FF0000ULL ) >> 16; + (*to)[4] = ( u.i & 0x00000000FF000000ULL ) >> 24; + (*to)[3] = ( u.i & 0x000000FF00000000ULL ) >> 32; + (*to)[2] = ( u.i & 0x0000FF0000000000ULL ) >> 40; + (*to)[1] = ( u.i & 0x00FF000000000000ULL ) >> 48; + (*to)[0] = ( u.i & 0xFF00000000000000ULL ) >> 56; + } + *to += 8; +} +*/ + +static uint32_t +rt_raster_serialized_size(rt_raster raster) { + uint32_t size = sizeof (struct rt_raster_serialized_t); + uint16_t i = 0; + + assert(NULL != raster); + + RASTER_DEBUGF(3, "Serialized size with just header:%d - now adding size of %d bands", + size, raster->numBands); + + for (i = 0; i < raster->numBands; ++i) { + rt_band band = raster->bands[i]; + rt_pixtype pixtype = band->pixtype; + int pixbytes = rt_pixtype_size(pixtype); + + if (pixbytes < 1) { + rterror("rt_raster_serialized_size: Corrupted band: unknown pixtype"); + return 0; + } + + /* Add space for band type, hasnodata flag and data padding */ + size += pixbytes; + + /* Add space for nodata value */ + size += pixbytes; + + if (band->offline) { + /* Add space for band number */ + size += 1; + + /* Add space for null-terminated path */ + size += strlen(band->data.offline.path) + 1; + } + else { + /* Add space for raster band data */ + size += pixbytes * raster->width * raster->height; + } + + RASTER_DEBUGF(3, "Size before alignment is %d", size); + + /* Align size to 8-bytes boundary (trailing padding) */ + /* XXX jorgearevalo: bug here. If the size is actually 8-bytes aligned, + this line will add 8 bytes trailing padding, and it's not necessary */ + /*size += 8 - (size % 8);*/ + if (size % 8) + size += 8 - (size % 8); + + RASTER_DEBUGF(3, "Size after alignment is %d", size); + } + + return size; +} + +/** + * Return this raster in serialized form. + * Memory (band data included) is copied from rt_raster. + * + * Serialized form is documented in doc/RFC1-SerializedFormat. + */ +void* +rt_raster_serialize(rt_raster raster) { + uint32_t size = 0; + uint8_t* ret = NULL; + uint8_t* ptr = NULL; + uint16_t i = 0; + + assert(NULL != raster); + + size = rt_raster_serialized_size(raster); + ret = (uint8_t*) rtalloc(size); + if (!ret) { + rterror("rt_raster_serialize: Out of memory allocating %d bytes for serializing a raster", size); + return NULL; + } + memset(ret, '-', size); + ptr = ret; + + RASTER_DEBUGF(3, "sizeof(struct rt_raster_serialized_t):%u", + sizeof (struct rt_raster_serialized_t)); + RASTER_DEBUGF(3, "sizeof(struct rt_raster_t):%u", + sizeof (struct rt_raster_t)); + RASTER_DEBUGF(3, "serialized size:%lu", (long unsigned) size); + + /* Set size */ + /* NOTE: Value of rt_raster.size may be updated in + * returned object, for instance, by rt_pg layer to + * store value calculated by SET_VARSIZE. + */ + raster->size = size; + + /* Set version */ + raster->version = 0; + + /* Copy header */ + memcpy(ptr, raster, sizeof (struct rt_raster_serialized_t)); + + RASTER_DEBUG(3, "Start hex dump of raster being serialized using 0x2D to mark non-written bytes"); + +#if POSTGIS_DEBUG_LEVEL > 2 + uint8_t* dbg_ptr = ptr; + d_print_binary_hex("HEADER", dbg_ptr, size); +#endif + + ptr += sizeof (struct rt_raster_serialized_t); + + /* Serialize bands now */ + for (i = 0; i < raster->numBands; ++i) { + rt_band band = raster->bands[i]; + assert(NULL != band); + + rt_pixtype pixtype = band->pixtype; + int pixbytes = rt_pixtype_size(pixtype); + if (pixbytes < 1) { + rterror("rt_raster_serialize: Corrupted band: unknown pixtype"); + rtdealloc(ret); + return NULL; + } + + /* Add band type */ + *ptr = band->pixtype; + if (band->offline) { + *ptr |= BANDTYPE_FLAG_OFFDB; + } + if (band->hasnodata) { + *ptr |= BANDTYPE_FLAG_HASNODATA; + } + + if (band->isnodata) { + *ptr |= BANDTYPE_FLAG_ISNODATA; + } + +#if POSTGIS_DEBUG_LEVEL > 2 + d_print_binary_hex("PIXTYPE", dbg_ptr, size); +#endif + + ptr += 1; + + /* Add padding (if needed) */ + if (pixbytes > 1) { + memset(ptr, '\0', pixbytes - 1); + ptr += pixbytes - 1; + } + +#if POSTGIS_DEBUG_LEVEL > 2 + d_print_binary_hex("PADDING", dbg_ptr, size); +#endif + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((ptr - ret) % pixbytes)); + + /* Add nodata value */ + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: { + uint8_t v = band->nodataval; + *ptr = v; + ptr += 1; + break; + } + case PT_8BSI: { + int8_t v = band->nodataval; + *ptr = v; + ptr += 1; + break; + } + case PT_16BSI: + case PT_16BUI: { + uint16_t v = band->nodataval; + memcpy(ptr, &v, 2); + ptr += 2; + break; + } + case PT_32BSI: + case PT_32BUI: { + uint32_t v = band->nodataval; + memcpy(ptr, &v, 4); + ptr += 4; + break; + } + case PT_32BF: { + float v = band->nodataval; + memcpy(ptr, &v, 4); + ptr += 4; + break; + } + case PT_64BF: { + memcpy(ptr, &band->nodataval, 8); + ptr += 8; + break; + } + default: + rterror("rt_raster_serialize: Fatal error caused by unknown pixel type. Aborting."); + rtdealloc(ret); + return NULL; + } + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((ptr - ret) % pixbytes)); + +#if POSTGIS_DEBUG_LEVEL > 2 + d_print_binary_hex("nodata", dbg_ptr, size); +#endif + + if (band->offline) { + /* Write band number */ + *ptr = band->data.offline.bandNum; + ptr += 1; + + /* Write path */ + strcpy((char*) ptr, band->data.offline.path); + ptr += strlen(band->data.offline.path) + 1; + } + else { + /* Write data */ + uint32_t datasize = raster->width * raster->height * pixbytes; + memcpy(ptr, band->data.mem, datasize); + ptr += datasize; + } + +#if POSTGIS_DEBUG_LEVEL > 2 + d_print_binary_hex("BAND", dbg_ptr, size); +#endif + + /* Pad up to 8-bytes boundary */ + while ((uintptr_t) ptr % 8) { + *ptr = 0; + ++ptr; + + RASTER_DEBUGF(3, "PAD at %d", (uintptr_t) ptr % 8); + } + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((ptr - ret) % pixbytes)); + } /* for-loop over bands */ + +#if POSTGIS_DEBUG_LEVEL > 2 + d_print_binary_hex("SERIALIZED RASTER", dbg_ptr, size); +#endif + return ret; +} + +/** + * Return a raster from a serialized form. + * + * Serialized form is documented in doc/RFC1-SerializedFormat. + * + * NOTE: the raster will contain pointer to the serialized + * form (including band data), which must be kept alive. + */ +rt_raster +rt_raster_deserialize(void* serialized, int header_only) { + rt_raster rast = NULL; + const uint8_t *ptr = NULL; + const uint8_t *beg = NULL; + uint16_t i = 0; + uint16_t j = 0; + uint8_t littleEndian = isMachineLittleEndian(); + + assert(NULL != serialized); + + RASTER_DEBUG(2, "rt_raster_deserialize: Entering..."); + + /* NOTE: Value of rt_raster.size may be different + * than actual size of raster data being read. + * See note on SET_VARSIZE in rt_raster_serialize function above. + */ + + /* Allocate memory for deserialized raster header */ + RASTER_DEBUG(3, "rt_raster_deserialize: Allocating memory for deserialized raster header"); + rast = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); + if (!rast) { + rterror("rt_raster_deserialize: Out of memory allocating raster for deserialization"); + return NULL; + } + + /* Deserialize raster header */ + RASTER_DEBUG(3, "rt_raster_deserialize: Deserialize raster header"); + memcpy(rast, serialized, sizeof (struct rt_raster_serialized_t)); + + if (0 == rast->numBands || header_only) { + rast->bands = 0; + return rast; + } + + beg = (const uint8_t*) serialized; + + /* Allocate registry of raster bands */ + RASTER_DEBUG(3, "rt_raster_deserialize: Allocating memory for bands"); + rast->bands = rtalloc(rast->numBands * sizeof (rt_band)); + if (rast->bands == NULL) { + rterror("rt_raster_deserialize: Out of memory allocating bands"); + rtdealloc(rast); + return NULL; + } + + RASTER_DEBUGF(3, "rt_raster_deserialize: %d bands", rast->numBands); + + /* Move to the beginning of first band */ + ptr = beg; + ptr += sizeof (struct rt_raster_serialized_t); + + /* Deserialize bands now */ + for (i = 0; i < rast->numBands; ++i) { + rt_band band = NULL; + uint8_t type = 0; + int pixbytes = 0; + + band = rtalloc(sizeof(struct rt_band_t)); + if (!band) { + rterror("rt_raster_deserialize: Out of memory allocating rt_band during deserialization"); + for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); + rt_raster_destroy(rast); + return NULL; + } + + rast->bands[i] = band; + + type = *ptr; + ptr++; + band->pixtype = type & BANDTYPE_PIXTYPE_MASK; + + RASTER_DEBUGF(3, "rt_raster_deserialize: band %d with pixel type %s", i, rt_pixtype_name(band->pixtype)); + + band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0; + band->hasnodata = BANDTYPE_HAS_NODATA(type) ? 1 : 0; + band->isnodata = band->hasnodata ? (BANDTYPE_IS_NODATA(type) ? 1 : 0) : 0; + band->width = rast->width; + band->height = rast->height; + band->ownsdata = 0; /* we do NOT own this data!!! */ + band->raster = rast; + + /* Advance by data padding */ + pixbytes = rt_pixtype_size(band->pixtype); + ptr += pixbytes - 1; + + /* Read nodata value */ + switch (band->pixtype) { + case PT_1BB: { + band->nodataval = ((int) read_uint8(&ptr)) & 0x01; + break; + } + case PT_2BUI: { + band->nodataval = ((int) read_uint8(&ptr)) & 0x03; + break; + } + case PT_4BUI: { + band->nodataval = ((int) read_uint8(&ptr)) & 0x0F; + break; + } + case PT_8BSI: { + band->nodataval = read_int8(&ptr); + break; + } + case PT_8BUI: { + band->nodataval = read_uint8(&ptr); + break; + } + case PT_16BSI: { + band->nodataval = read_int16(&ptr, littleEndian); + break; + } + case PT_16BUI: { + band->nodataval = read_uint16(&ptr, littleEndian); + break; + } + case PT_32BSI: { + band->nodataval = read_int32(&ptr, littleEndian); + break; + } + case PT_32BUI: { + band->nodataval = read_uint32(&ptr, littleEndian); + break; + } + case PT_32BF: { + band->nodataval = read_float32(&ptr, littleEndian); + break; + } + case PT_64BF: { + band->nodataval = read_float64(&ptr, littleEndian); + break; + } + default: { + rterror("rt_raster_deserialize: Unknown pixeltype %d", band->pixtype); + for (j = 0; j <= i; j++) rt_band_destroy(rast->bands[j]); + rt_raster_destroy(rast); + return NULL; + } + } + + RASTER_DEBUGF(3, "rt_raster_deserialize: has nodata flag %d", band->hasnodata); + RASTER_DEBUGF(3, "rt_raster_deserialize: nodata value %g", band->nodataval); + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((ptr - beg) % pixbytes)); + + if (band->offline) { + int pathlen = 0; + + /* Read band number */ + band->data.offline.bandNum = *ptr; + ptr += 1; + + /* Register path */ + pathlen = strlen((char*) ptr); + band->data.offline.path = rtalloc(sizeof(char) * (pathlen + 1)); + if (band->data.offline.path == NULL) { + rterror("rt_raster_deserialize: Could not allocate momory for offline band path"); + for (j = 0; j <= i; j++) rt_band_destroy(rast->bands[j]); + rt_raster_destroy(rast); + return NULL; + } + + memcpy(band->data.offline.path, ptr, pathlen); + band->data.offline.path[pathlen] = '\0'; + ptr += pathlen + 1; + + band->data.offline.mem = NULL; + } + else { + /* Register data */ + const uint32_t datasize = rast->width * rast->height * pixbytes; + band->data.mem = (uint8_t*) ptr; + ptr += datasize; + } + + /* Skip bytes of padding up to 8-bytes boundary */ +#if POSTGIS_DEBUG_LEVEL > 0 + const uint8_t *padbeg = ptr; +#endif + while (0 != ((ptr - beg) % 8)) { + ++ptr; + } + + RASTER_DEBUGF(3, "rt_raster_deserialize: skip %d bytes of 8-bytes boundary padding", ptr - padbeg); + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((ptr - beg) % pixbytes)); + } + + return rast; +} diff --git a/raster/rt_core/rt_serialize.h b/raster/rt_core/rt_serialize.h new file mode 100644 index 000000000..849c344d2 --- /dev/null +++ b/raster/rt_core/rt_serialize.h @@ -0,0 +1,167 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef RT_SERIALIZE_H_INCLUDED +#define RT_SERIALIZE_H_INCLUDED + +#include "librtcore.h" + +#define BANDTYPE_FLAGS_MASK 0xF0 +#define BANDTYPE_PIXTYPE_MASK 0x0F +#define BANDTYPE_FLAG_OFFDB (1<<7) +#define BANDTYPE_FLAG_HASNODATA (1<<6) +#define BANDTYPE_FLAG_ISNODATA (1<<5) +#define BANDTYPE_FLAG_RESERVED3 (1<<4) + +#define BANDTYPE_PIXTYPE(x) ((x)&BANDTYPE_PIXTYPE_MASK) +#define BANDTYPE_IS_OFFDB(x) ((x)&BANDTYPE_FLAG_OFFDB) +#define BANDTYPE_HAS_NODATA(x) ((x)&BANDTYPE_FLAG_HASNODATA) +#define BANDTYPE_IS_NODATA(x) ((x)&BANDTYPE_FLAG_ISNODATA) + +#if POSTGIS_DEBUG_LEVEL > 2 +char* +d_binary_to_hex(const uint8_t * const raw, uint32_t size, uint32_t *hexsize); + +void +d_print_binary_hex(const char* msg, const uint8_t * const raw, uint32_t size); + +size_t +d_binptr_to_pos(const uint8_t * const ptr, const uint8_t * const end, size_t size); + +#define CHECK_BINPTR_POSITION(ptr, end, size, pos) \ + { if (pos != d_binptr_to_pos(ptr, end, size)) { \ + fprintf(stderr, "Check of binary stream pointer position failed on line %d\n", __LINE__); \ + fprintf(stderr, "\tactual = %lu, expected = %lu\n", (long unsigned)d_binptr_to_pos(ptr, end, size), (long unsigned)pos); \ + } } + +#else + +#define CHECK_BINPTR_POSITION(ptr, end, size, pos) ((void)0); + +#endif /* if POSTGIS_DEBUG_LEVEL > 2 */ + +#ifdef OPTIMIZE_SPACE + +/* + * Set given number of bits of the given byte, + * starting from given bitOffset (from the first) + * to the given value. + * + * Examples: + * char ch; + * ch=0; setBits(&ch, 1, 1, 0) -> ch==8 + * ch=0; setBits(&ch, 3, 2, 1) -> ch==96 (0x60) + * + * Note that number of bits set must be <= 8-bitOffset + * + */ +void +setBits(char* ch, double val, int bits, int bitOffset); + +#endif /* ifdef OPTIMIZE_SPACE */ + +void +swap_char(uint8_t *a, uint8_t *b); + +void +flip_endian_16(uint8_t *d); + +void +flip_endian_32(uint8_t *d); + +void +flip_endian_64(uint8_t *d); + +uint8_t +isMachineLittleEndian(void); + +uint8_t +read_uint8(const uint8_t** from); + +/* unused up to now +void +write_uint8(uint8_t** from, uint8_t v); +*/ + +int8_t +read_int8(const uint8_t** from); + +/* unused up to now +void +write_int8(uint8_t** from, int8_t v); +*/ + +uint16_t +read_uint16(const uint8_t** from, uint8_t littleEndian); + +void +write_uint16(uint8_t** to, uint8_t littleEndian, uint16_t v); + +int16_t +read_int16(const uint8_t** from, uint8_t littleEndian); + +/* unused up to now +void +write_int16(uint8_t** to, uint8_t littleEndian, int16_t v); +*/ + +uint32_t +read_uint32(const uint8_t** from, uint8_t littleEndian); + +/* unused up to now +void +write_uint32(uint8_t** to, uint8_t littleEndian, uint32_t v); +*/ + +int32_t +read_int32(const uint8_t** from, uint8_t littleEndian); + +/* unused up to now +void +write_int32(uint8_t** to, uint8_t littleEndian, int32_t v); +*/ + +float +read_float32(const uint8_t** from, uint8_t littleEndian); + +/* unused up to now +void +write_float32(uint8_t** from, uint8_t littleEndian, float f); +*/ + +double +read_float64(const uint8_t** from, uint8_t littleEndian); + +/* unused up to now +void +write_float64(uint8_t** to, uint8_t littleEndian, double v); +*/ + +#endif /* RT_SERIALIZE_H_INCLUDED */ diff --git a/raster/rt_core/rt_spatial_relationship.c b/raster/rt_core/rt_spatial_relationship.c new file mode 100644 index 000000000..a07223b4b --- /dev/null +++ b/raster/rt_core/rt_spatial_relationship.c @@ -0,0 +1,1301 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/* + * Return ES_ERROR if error occurred in function. + * Paramter aligned returns non-zero if two rasters are aligned + * + * @param rast1 : the first raster for alignment test + * @param rast2 : the second raster for alignment test + * @param *aligned : non-zero value if the two rasters are aligned + * @param *reason : reason why rasters are not aligned + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_same_alignment( + rt_raster rast1, + rt_raster rast2, + int *aligned, char **reason +) { + double xr; + double yr; + double xw; + double yw; + int err = 0; + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != aligned); + + err = 0; + /* same srid */ + if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { + if (reason != NULL) *reason = "The rasters have different SRIDs"; + err = 1; + } + /* scales must match */ + else if (FLT_NEQ(fabs(rast1->scaleX), fabs(rast2->scaleX))) { + if (reason != NULL) *reason = "The rasters have different scales on the X axis"; + err = 1; + } + else if (FLT_NEQ(fabs(rast1->scaleY), fabs(rast2->scaleY))) { + if (reason != NULL) *reason = "The rasters have different scales on the Y axis"; + err = 1; + } + /* skews must match */ + else if (FLT_NEQ(rast1->skewX, rast2->skewX)) { + if (reason != NULL) *reason = "The rasters have different skews on the X axis"; + err = 1; + } + else if (FLT_NEQ(rast1->skewY, rast2->skewY)) { + if (reason != NULL) *reason = "The rasters have different skews on the Y axis"; + err = 1; + } + + if (err) { + *aligned = 0; + return ES_NONE; + } + + /* raster coordinates in context of second raster of first raster's upper-left corner */ + if (rt_raster_geopoint_to_cell( + rast2, + rast1->ipX, rast1->ipY, + &xr, &yr, + NULL + ) != ES_NONE) { + rterror("rt_raster_same_alignment: Could not get raster coordinates of second raster from first raster's spatial coordinates"); + *aligned = 0; + return ES_ERROR; + } + + /* spatial coordinates of raster coordinates from above */ + if (rt_raster_cell_to_geopoint( + rast2, + xr, yr, + &xw, &yw, + NULL + ) != ES_NONE) { + rterror("rt_raster_same_alignment: Could not get spatial coordinates of second raster from raster coordinates"); + *aligned = 0; + return ES_ERROR; + } + + RASTER_DEBUGF(4, "rast1(ipX, ipxY) = (%f, %f)", rast1->ipX, rast1->ipY); + RASTER_DEBUGF(4, "rast2(xr, yr) = (%f, %f)", xr, yr); + RASTER_DEBUGF(4, "rast2(xw, yw) = (%f, %f)", xw, yw); + + /* spatial coordinates are identical to that of first raster's upper-left corner */ + if (FLT_EQ(xw, rast1->ipX) && FLT_EQ(yw, rast1->ipY)) { + if (reason != NULL) *reason = "The rasters are aligned"; + *aligned = 1; + return ES_NONE; + } + + /* no alignment */ + if (reason != NULL) *reason = "The rasters (pixel corner coordinates) are not aligned"; + + *aligned = 0; + return ES_NONE; +} + +/****************************************************************************** +* GEOS-based spatial relationship tests +******************************************************************************/ + +static +rt_errorstate rt_raster_geos_spatial_relationship( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + rt_geos_spatial_test testtype, + int *testresult +) { + LWMPOLY *surface1 = NULL; + LWMPOLY *surface2 = NULL; + GEOSGeometry *geom1 = NULL; + GEOSGeometry *geom2 = NULL; + int rtn = 0; + int flag = 0; + + RASTER_DEBUG(3, "Starting"); + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != testresult); + + if (nband1 < 0 && nband2 < 0) { + nband1 = -1; + nband2 = -1; + } + else { + assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); + assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); + } + + /* initialize to zero, false result of spatial relationship test */ + *testresult = 0; + + /* same srid */ + if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { + rterror("rt_raster_geos_spatial_relationship: The two rasters provided have different SRIDs"); + return ES_ERROR; + } + + initGEOS(lwnotice, lwgeom_geos_error); + + /* get LWMPOLY of each band */ + if (rt_raster_surface(rast1, nband1, &surface1) != ES_NONE) { + rterror("rt_raster_geos_spatial_relationship: Could not get surface of the specified band from the first raster"); + return ES_ERROR; + } + if (rt_raster_surface(rast2, nband2, &surface2) != ES_NONE) { + rterror("rt_raster_geos_spatial_relationship: Could not get surface of the specified band from the second raster"); + lwmpoly_free(surface1); + return ES_ERROR; + } + + /* either surface is NULL, spatial relationship test is false */ + if (surface1 == NULL || surface2 == NULL) { + if (surface1 != NULL) lwmpoly_free(surface1); + if (surface2 != NULL) lwmpoly_free(surface2); + return ES_NONE; + } + + /* convert LWMPOLY to GEOSGeometry */ + geom1 = LWGEOM2GEOS(lwmpoly_as_lwgeom(surface1)); + lwmpoly_free(surface1); + if (geom1 == NULL) { + rterror("rt_raster_geos_spatial_relationship: Could not convert surface of the specified band from the first raster to a GEOSGeometry"); + lwmpoly_free(surface2); + return ES_ERROR; + } + + geom2 = LWGEOM2GEOS(lwmpoly_as_lwgeom(surface2)); + lwmpoly_free(surface2); + if (geom2 == NULL) { + rterror("rt_raster_geos_spatial_relationship: Could not convert surface of the specified band from the second raster to a GEOSGeometry"); + return ES_ERROR; + } + + flag = 0; + switch (testtype) { + case GSR_OVERLAPS: + rtn = GEOSOverlaps(geom1, geom2); + break; + case GSR_TOUCHES: + rtn = GEOSTouches(geom1, geom2); + break; + case GSR_CONTAINS: + rtn = GEOSContains(geom1, geom2); + break; + case GSR_CONTAINSPROPERLY: + rtn = GEOSRelatePattern(geom1, geom2, "T**FF*FF*"); + break; + case GSR_COVERS: + rtn = GEOSRelatePattern(geom1, geom2, "******FF*"); + break; + case GSR_COVEREDBY: + rtn = GEOSRelatePattern(geom1, geom2, "**F**F***"); + break; + default: + rterror("rt_raster_geos_spatial_relationship: Unknown or unsupported GEOS spatial relationship test"); + flag = -1; + break; + } + GEOSGeom_destroy(geom1); + GEOSGeom_destroy(geom2); + + /* something happened in the spatial relationship test */ + if (rtn == 2) { + rterror("rt_raster_geos_spatial_relationship: Could not run the appropriate GEOS spatial relationship test"); + flag = ES_ERROR; + } + /* spatial relationship test ran fine */ + else if (flag >= 0) { + if (rtn != 0) + *testresult = 1; + flag = ES_NONE; + } + /* flag < 0 for when testtype is unknown */ + else + flag = ES_ERROR; + + return flag; +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter overlaps returns non-zero if two rasters overlap + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param overlaps : non-zero value if the two rasters' bands overlaps + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_overlaps( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *overlaps +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_OVERLAPS, + overlaps + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter touches returns non-zero if two rasters touch + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param touches : non-zero value if the two rasters' bands touch + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_touches( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *touches +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_TOUCHES, + touches + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter contains returns non-zero if rast1 contains rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param contains : non-zero value if rast1 contains rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_contains( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *contains +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_CONTAINS, + contains + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter contains returns non-zero if rast1 contains properly rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param contains : non-zero value if rast1 contains properly rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_contains_properly( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *contains +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_CONTAINSPROPERLY, + contains + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter covers returns non-zero if rast1 covers rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param covers : non-zero value if rast1 covers rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_covers( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *covers +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_COVERS, + covers + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter coveredby returns non-zero if rast1 is covered by rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param coveredby : non-zero value if rast1 is covered by rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_coveredby( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *coveredby +) { + RASTER_DEBUG(3, "Starting"); + + return rt_raster_geos_spatial_relationship( + rast1, nband1, + rast2, nband2, + GSR_COVEREDBY, + coveredby + ); +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter dwithin returns non-zero if rast1 is within the specified + * distance of rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param dwithin : non-zero value if rast1 is within the specified distance + * of rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_within_distance( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + double distance, + int *dwithin +) { + LWMPOLY *surface = NULL; + LWGEOM *surface1 = NULL; + LWGEOM *surface2 = NULL; + double mindist = 0; + + RASTER_DEBUG(3, "Starting"); + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != dwithin); + + if (nband1 < 0 && nband2 < 0) { + nband1 = -1; + nband2 = -1; + } + else { + assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); + assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); + } + + /* initialize to zero, false result */ + *dwithin = 0; + + /* same srid */ + if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { + rterror("rt_raster_distance_within: The two rasters provided have different SRIDs"); + return ES_ERROR; + } + + /* distance cannot be less than zero */ + if (distance < 0) { + rterror("rt_raster_distance_within: Distance cannot be less than zero"); + return ES_ERROR; + } + + /* get LWMPOLY of each band */ + if (rt_raster_surface(rast1, nband1, &surface) != ES_NONE) { + rterror("rt_raster_distance_within: Could not get surface of the specified band from the first raster"); + return ES_ERROR; + } + surface1 = lwmpoly_as_lwgeom(surface); + + if (rt_raster_surface(rast2, nband2, &surface) != ES_NONE) { + rterror("rt_raster_distance_within: Could not get surface of the specified band from the second raster"); + lwgeom_free(surface1); + return ES_ERROR; + } + surface2 = lwmpoly_as_lwgeom(surface); + + /* either surface is NULL, test is false */ + if (surface1 == NULL || surface2 == NULL) { + if (surface1 != NULL) lwgeom_free(surface1); + if (surface2 != NULL) lwgeom_free(surface2); + return ES_NONE; + } + + /* get the min distance between the two surfaces */ + mindist = lwgeom_mindistance2d_tolerance(surface1, surface2, distance); + + lwgeom_free(surface1); + lwgeom_free(surface2); + + /* if distance >= mindist, true */ + if (FLT_EQ(mindist, distance) || distance > mindist) + *dwithin = 1; + + RASTER_DEBUGF(3, "(mindist, distance) = (%f, %f, %d)", mindist, distance, *dwithin); + + return ES_NONE; +} + +/** + * Return ES_ERROR if error occurred in function. + * Parameter dfwithin returns non-zero if rast1 is fully within the specified + * distance of rast2 + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param dfwithin : non-zero value if rast1 is fully within the specified + * distance of rast2 + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate rt_raster_fully_within_distance( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + double distance, + int *dfwithin +) { + LWMPOLY *surface = NULL; + LWGEOM *surface1 = NULL; + LWGEOM *surface2 = NULL; + double maxdist = 0; + + RASTER_DEBUG(3, "Starting"); + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != dfwithin); + + if (nband1 < 0 && nband2 < 0) { + nband1 = -1; + nband2 = -1; + } + else { + assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); + assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); + } + + /* initialize to zero, false result */ + *dfwithin = 0; + + /* same srid */ + if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { + rterror("rt_raster_fully_within_distance: The two rasters provided have different SRIDs"); + return ES_ERROR; + } + + /* distance cannot be less than zero */ + if (distance < 0) { + rterror("rt_raster_fully_within_distance: Distance cannot be less than zero"); + return ES_ERROR; + } + + /* get LWMPOLY of each band */ + if (rt_raster_surface(rast1, nband1, &surface) != ES_NONE) { + rterror("rt_raster_fully_within_distance: Could not get surface of the specified band from the first raster"); + return ES_ERROR; + } + surface1 = lwmpoly_as_lwgeom(surface); + + if (rt_raster_surface(rast2, nband2, &surface) != ES_NONE) { + rterror("rt_raster_fully_within_distance: Could not get surface of the specified band from the second raster"); + lwgeom_free(surface1); + return ES_ERROR; + } + surface2 = lwmpoly_as_lwgeom(surface); + + /* either surface is NULL, test is false */ + if (surface1 == NULL || surface2 == NULL) { + if (surface1 != NULL) lwgeom_free(surface1); + if (surface2 != NULL) lwgeom_free(surface2); + return ES_NONE; + } + + /* get the maximum distance between the two surfaces */ + maxdist = lwgeom_maxdistance2d_tolerance(surface1, surface2, distance); + + lwgeom_free(surface1); + lwgeom_free(surface2); + + /* if distance >= maxdist, true */ + if (FLT_EQ(maxdist, distance) || distance > maxdist) + *dfwithin = 1; + + RASTER_DEBUGF(3, "(maxdist, distance, dfwithin) = (%f, %f, %d)", maxdist, distance, *dfwithin); + + return ES_NONE; +} + +/****************************************************************************** +* rt_raster_intersects() +******************************************************************************/ + +static +int rt_raster_intersects_algorithm( + rt_raster rast1, rt_raster rast2, + rt_band band1, rt_band band2, + int hasnodata1, int hasnodata2, + double nodata1, double nodata2 +) { + int i; + int byHeight = 1; + uint32_t dimValue; + + uint32_t row; + uint32_t rowoffset; + uint32_t col; + uint32_t coloffset; + + enum line_points {X1, Y1, X2, Y2}; + enum point {pX, pY}; + double line1[4] = {0.}; + double line2[4] = {0.}; + double P[2] = {0.}; + double Qw[2] = {0.}; + double Qr[2] = {0.}; + double gt1[6] = {0.}; + double gt2[6] = {0.}; + double igt1[6] = {0}; + double igt2[6] = {0}; + double d; + double val1; + int noval1; + int isnodata1; + double val2; + int noval2; + int isnodata2; + uint32_t adjacent[8] = {0}; + + double xscale; + double yscale; + + uint16_t width1; + uint16_t height1; + uint16_t width2; + uint16_t height2; + + width1 = rt_raster_get_width(rast1); + height1 = rt_raster_get_height(rast1); + width2 = rt_raster_get_width(rast2); + height2 = rt_raster_get_height(rast2); + + /* sampling scale */ + xscale = fmin(rt_raster_get_x_scale(rast1), rt_raster_get_x_scale(rast2)) / 10.; + yscale = fmin(rt_raster_get_y_scale(rast1), rt_raster_get_y_scale(rast2)) / 10.; + + /* see if skew made rast2's rows are parallel to rast1's cols */ + rt_raster_cell_to_geopoint( + rast1, + 0, 0, + &(line1[X1]), &(line1[Y1]), + gt1 + ); + + rt_raster_cell_to_geopoint( + rast1, + 0, height1, + &(line1[X2]), &(line1[Y2]), + gt1 + ); + + rt_raster_cell_to_geopoint( + rast2, + 0, 0, + &(line2[X1]), &(line2[Y1]), + gt2 + ); + + rt_raster_cell_to_geopoint( + rast2, + width2, 0, + &(line2[X2]), &(line2[Y2]), + gt2 + ); + + /* parallel vertically */ + if (FLT_EQ(line1[X2] - line1[X1], 0.) && FLT_EQ(line2[X2] - line2[X1], 0.)) + byHeight = 0; + /* parallel */ + else if (FLT_EQ(((line1[Y2] - line1[Y1]) / (line1[X2] - line1[X1])), ((line2[Y2] - line2[Y1]) / (line2[X2] - line2[X1])))) + byHeight = 0; + + if (byHeight) + dimValue = height2; + else + dimValue = width2; + RASTER_DEBUGF(4, "byHeight: %d, dimValue: %d", byHeight, dimValue); + + /* 3 x 3 search */ + for (coloffset = 0; coloffset < 3; coloffset++) { + for (rowoffset = 0; rowoffset < 3; rowoffset++) { + /* smaller raster */ + for (col = coloffset; col <= width1; col += 3) { + + rt_raster_cell_to_geopoint( + rast1, + col, 0, + &(line1[X1]), &(line1[Y1]), + gt1 + ); + + rt_raster_cell_to_geopoint( + rast1, + col, height1, + &(line1[X2]), &(line1[Y2]), + gt1 + ); + + /* larger raster */ + for (row = rowoffset; row <= dimValue; row += 3) { + + if (byHeight) { + rt_raster_cell_to_geopoint( + rast2, + 0, row, + &(line2[X1]), &(line2[Y1]), + gt2 + ); + + rt_raster_cell_to_geopoint( + rast2, + width2, row, + &(line2[X2]), &(line2[Y2]), + gt2 + ); + } + else { + rt_raster_cell_to_geopoint( + rast2, + row, 0, + &(line2[X1]), &(line2[Y1]), + gt2 + ); + + rt_raster_cell_to_geopoint( + rast2, + row, height2, + &(line2[X2]), &(line2[Y2]), + gt2 + ); + } + + RASTER_DEBUGF(4, "(col, row) = (%d, %d)", col, row); + RASTER_DEBUGF(4, "line1(x1, y1, x2, y2) = (%f, %f, %f, %f)", + line1[X1], line1[Y1], line1[X2], line1[Y2]); + RASTER_DEBUGF(4, "line2(x1, y1, x2, y2) = (%f, %f, %f, %f)", + line2[X1], line2[Y1], line2[X2], line2[Y2]); + + /* intersection */ + /* http://en.wikipedia.org/wiki/Line-line_intersection */ + d = ((line1[X1] - line1[X2]) * (line2[Y1] - line2[Y2])) - ((line1[Y1] - line1[Y2]) * (line2[X1] - line2[X2])); + /* no intersection */ + if (FLT_EQ(d, 0.)) { + continue; + } + + P[pX] = (((line1[X1] * line1[Y2]) - (line1[Y1] * line1[X2])) * (line2[X1] - line2[X2])) - + ((line1[X1] - line1[X2]) * ((line2[X1] * line2[Y2]) - (line2[Y1] * line2[X2]))); + P[pX] = P[pX] / d; + + P[pY] = (((line1[X1] * line1[Y2]) - (line1[Y1] * line1[X2])) * (line2[Y1] - line2[Y2])) - + ((line1[Y1] - line1[Y2]) * ((line2[X1] * line2[Y2]) - (line2[Y1] * line2[X2]))); + P[pY] = P[pY] / d; + + RASTER_DEBUGF(4, "P(x, y) = (%f, %f)", P[pX], P[pY]); + + /* intersection within bounds */ + if (( + (FLT_EQ(P[pX], line1[X1]) || FLT_EQ(P[pX], line1[X2])) || + (P[pX] > fmin(line1[X1], line1[X2]) && (P[pX] < fmax(line1[X1], line1[X2]))) + ) && ( + (FLT_EQ(P[pY], line1[Y1]) || FLT_EQ(P[pY], line1[Y2])) || + (P[pY] > fmin(line1[Y1], line1[Y2]) && (P[pY] < fmax(line1[Y1], line1[Y2]))) + ) && ( + (FLT_EQ(P[pX], line2[X1]) || FLT_EQ(P[pX], line2[X2])) || + (P[pX] > fmin(line2[X1], line2[X2]) && (P[pX] < fmax(line2[X1], line2[X2]))) + ) && ( + (FLT_EQ(P[pY], line2[Y1]) || FLT_EQ(P[pY], line2[Y2])) || + (P[pY] > fmin(line2[Y1], line2[Y2]) && (P[pY] < fmax(line2[Y1], line2[Y2]))) + )) { + RASTER_DEBUG(4, "within bounds"); + + for (i = 0; i < 8; i++) adjacent[i] = 0; + + /* test points around intersection */ + for (i = 0; i < 8; i++) { + switch (i) { + case 7: + Qw[pX] = P[pX] - xscale; + Qw[pY] = P[pY] + yscale; + break; + /* 270 degrees = 09:00 */ + case 6: + Qw[pX] = P[pX] - xscale; + Qw[pY] = P[pY]; + break; + case 5: + Qw[pX] = P[pX] - xscale; + Qw[pY] = P[pY] - yscale; + break; + /* 180 degrees = 06:00 */ + case 4: + Qw[pX] = P[pX]; + Qw[pY] = P[pY] - yscale; + break; + case 3: + Qw[pX] = P[pX] + xscale; + Qw[pY] = P[pY] - yscale; + break; + /* 90 degrees = 03:00 */ + case 2: + Qw[pX] = P[pX] + xscale; + Qw[pY] = P[pY]; + break; + /* 45 degrees */ + case 1: + Qw[pX] = P[pX] + xscale; + Qw[pY] = P[pY] + yscale; + break; + /* 0 degrees = 00:00 */ + case 0: + Qw[pX] = P[pX]; + Qw[pY] = P[pY] + yscale; + break; + } + + /* unable to convert point to cell */ + noval1 = 0; + if (rt_raster_geopoint_to_cell( + rast1, + Qw[pX], Qw[pY], + &(Qr[pX]), &(Qr[pY]), + igt1 + ) != ES_NONE) { + noval1 = 1; + } + /* cell is outside bounds of grid */ + else if ( + (Qr[pX] < 0 || Qr[pX] > width1 || FLT_EQ(Qr[pX], width1)) || + (Qr[pY] < 0 || Qr[pY] > height1 || FLT_EQ(Qr[pY], height1)) + ) { + noval1 = 1; + } + else if (hasnodata1 == FALSE) + val1 = 1; + /* unable to get value at cell */ + else if (rt_band_get_pixel(band1, Qr[pX], Qr[pY], &val1, &isnodata1) != ES_NONE) + noval1 = 1; + + /* unable to convert point to cell */ + noval2 = 0; + if (rt_raster_geopoint_to_cell( + rast2, + Qw[pX], Qw[pY], + &(Qr[pX]), &(Qr[pY]), + igt2 + ) != ES_NONE) { + noval2 = 1; + } + /* cell is outside bounds of grid */ + else if ( + (Qr[pX] < 0 || Qr[pX] > width2 || FLT_EQ(Qr[pX], width2)) || + (Qr[pY] < 0 || Qr[pY] > height2 || FLT_EQ(Qr[pY], height2)) + ) { + noval2 = 1; + } + else if (hasnodata2 == FALSE) + val2 = 1; + /* unable to get value at cell */ + else if (rt_band_get_pixel(band2, Qr[pX], Qr[pY], &val2, &isnodata2) != ES_NONE) + noval2 = 1; + + if (!noval1) { + RASTER_DEBUGF(4, "val1 = %f", val1); + } + if (!noval2) { + RASTER_DEBUGF(4, "val2 = %f", val2); + } + + /* pixels touch */ + if (!noval1 && ( + (hasnodata1 == FALSE) || !isnodata1 + )) { + adjacent[i]++; + } + if (!noval2 && ( + (hasnodata2 == FALSE) || !isnodata2 + )) { + adjacent[i] += 3; + } + + /* two pixel values not present */ + if (noval1 || noval2) { + RASTER_DEBUGF(4, "noval1 = %d, noval2 = %d", noval1, noval2); + continue; + } + + /* pixels valid, so intersect */ + if ( + ((hasnodata1 == FALSE) || !isnodata1) && + ((hasnodata2 == FALSE) || !isnodata2) + ) { + RASTER_DEBUG(3, "The two rasters do intersect"); + + return 1; + } + } + + /* pixels touch */ + for (i = 0; i < 4; i++) { + RASTER_DEBUGF(4, "adjacent[%d] = %d, adjacent[%d] = %d" + , i, adjacent[i], i + 4, adjacent[i + 4]); + if (adjacent[i] == 0) continue; + + if (adjacent[i] + adjacent[i + 4] == 4) { + RASTER_DEBUG(3, "The two rasters touch"); + + return 1; + } + } + } + else { + RASTER_DEBUG(4, "outside of bounds"); + } + } + } + } + } + + return 0; +} + +/** + * Return zero if error occurred in function. + * Parameter intersects returns non-zero if two rasters intersect + * + * @param rast1 : the first raster whose band will be tested + * @param nband1 : the 0-based band of raster rast1 to use + * if value is less than zero, bands are ignored. + * if nband1 gte zero, nband2 must be gte zero + * @param rast2 : the second raster whose band will be tested + * @param nband2 : the 0-based band of raster rast2 to use + * if value is less than zero, bands are ignored + * if nband2 gte zero, nband1 must be gte zero + * @param intersects : non-zero value if the two rasters' bands intersects + * + * @return ES_NONE if success, ES_ERROR if error + */ +rt_errorstate +rt_raster_intersects( + rt_raster rast1, int nband1, + rt_raster rast2, int nband2, + int *intersects +) { + int i; + int j; + int within = 0; + + LWGEOM *hull[2] = {NULL}; + GEOSGeometry *ghull[2] = {NULL}; + + uint16_t width1; + uint16_t height1; + uint16_t width2; + uint16_t height2; + double area1; + double area2; + double pixarea1; + double pixarea2; + rt_raster rastS = NULL; + rt_raster rastL = NULL; + uint16_t *widthS = NULL; + uint16_t *heightS = NULL; + uint16_t *widthL = NULL; + uint16_t *heightL = NULL; + int nbandS; + int nbandL; + rt_band bandS = NULL; + rt_band bandL = NULL; + int hasnodataS = FALSE; + int hasnodataL = FALSE; + double nodataS = 0; + double nodataL = 0; + int isnodataS = 0; + int isnodataL = 0; + double gtS[6] = {0}; + double igtL[6] = {0}; + + uint32_t row; + uint32_t rowoffset; + uint32_t col; + uint32_t coloffset; + + enum line_points {X1, Y1, X2, Y2}; + enum point {pX, pY}; + double lineS[4]; + double Qr[2]; + double valS; + double valL; + + RASTER_DEBUG(3, "Starting"); + + assert(NULL != rast1); + assert(NULL != rast2); + assert(NULL != intersects); + + if (nband1 < 0 && nband2 < 0) { + nband1 = -1; + nband2 = -1; + } + else { + assert(nband1 >= 0 && nband1 < rt_raster_get_num_bands(rast1)); + assert(nband2 >= 0 && nband2 < rt_raster_get_num_bands(rast2)); + } + + /* same srid */ + if (rt_raster_get_srid(rast1) != rt_raster_get_srid(rast2)) { + rterror("rt_raster_intersects: The two rasters provided have different SRIDs"); + *intersects = 0; + return ES_ERROR; + } + + /* raster extents need to intersect */ + do { + int rtn; + + initGEOS(lwnotice, lwgeom_geos_error); + + rtn = 1; + for (i = 0; i < 2; i++) { + if ((rt_raster_get_convex_hull(i < 1 ? rast1 : rast2, &(hull[i])) != ES_NONE) || NULL == hull[i]) { + for (j = 0; j < i; j++) { + GEOSGeom_destroy(ghull[j]); + lwgeom_free(hull[j]); + } + rtn = 0; + break; + } + ghull[i] = (GEOSGeometry *) LWGEOM2GEOS(hull[i]); + if (NULL == ghull[i]) { + for (j = 0; j < i; j++) { + GEOSGeom_destroy(ghull[j]); + lwgeom_free(hull[j]); + } + lwgeom_free(hull[i]); + rtn = 0; + break; + } + } + if (!rtn) break; + + /* test to see if raster within the other */ + within = 0; + if (GEOSWithin(ghull[0], ghull[1]) == 1) + within = -1; + else if (GEOSWithin(ghull[1], ghull[0]) == 1) + within = 1; + + if (within != 0) + rtn = 1; + else + rtn = GEOSIntersects(ghull[0], ghull[1]); + + for (i = 0; i < 2; i++) { + GEOSGeom_destroy(ghull[i]); + lwgeom_free(hull[i]); + } + + if (rtn != 2) { + RASTER_DEBUGF(4, "convex hulls of rasters do %sintersect", rtn != 1 ? "NOT " : ""); + if (rtn != 1) { + *intersects = 0; + return ES_NONE; + } + /* band isn't specified */ + else if (nband1 < 0) { + *intersects = 1; + return ES_NONE; + } + } + else { + RASTER_DEBUG(4, "GEOSIntersects() returned a 2!!!!"); + } + } + while (0); + + /* smaller raster by area or width */ + width1 = rt_raster_get_width(rast1); + height1 = rt_raster_get_height(rast1); + width2 = rt_raster_get_width(rast2); + height2 = rt_raster_get_height(rast2); + pixarea1 = fabs(rt_raster_get_x_scale(rast1) * rt_raster_get_y_scale(rast1)); + pixarea2 = fabs(rt_raster_get_x_scale(rast2) * rt_raster_get_y_scale(rast2)); + area1 = fabs(width1 * height1 * pixarea1); + area2 = fabs(width2 * height2 * pixarea2); + RASTER_DEBUGF(4, "pixarea1, pixarea2, area1, area2 = %f, %f, %f, %f", + pixarea1, pixarea2, area1, area2); + if ( + (within <= 0) || + (area1 < area2) || + FLT_EQ(area1, area2) || + (area1 < pixarea2) || /* area of rast1 smaller than pixel area of rast2 */ + FLT_EQ(area1, pixarea2) + ) { + rastS = rast1; + nbandS = nband1; + widthS = &width1; + heightS = &height1; + + rastL = rast2; + nbandL = nband2; + widthL = &width2; + heightL = &height2; + } + else { + rastS = rast2; + nbandS = nband2; + widthS = &width2; + heightS = &height2; + + rastL = rast1; + nbandL = nband1; + widthL = &width1; + heightL = &height1; + } + + /* no band to use, set band to zero */ + if (nband1 < 0) { + nbandS = 0; + nbandL = 0; + } + + RASTER_DEBUGF(4, "rast1 @ %p", rast1); + RASTER_DEBUGF(4, "rast2 @ %p", rast2); + RASTER_DEBUGF(4, "rastS @ %p", rastS); + RASTER_DEBUGF(4, "rastL @ %p", rastL); + + /* load band of smaller raster */ + bandS = rt_raster_get_band(rastS, nbandS); + if (NULL == bandS) { + rterror("rt_raster_intersects: Could not get band %d of the first raster", nbandS); + *intersects = 0; + return ES_ERROR; + } + + hasnodataS = rt_band_get_hasnodata_flag(bandS); + if (hasnodataS != FALSE) + rt_band_get_nodata(bandS, &nodataS); + + /* load band of larger raster */ + bandL = rt_raster_get_band(rastL, nbandL); + if (NULL == bandL) { + rterror("rt_raster_intersects: Could not get band %d of the first raster", nbandL); + *intersects = 0; + return ES_ERROR; + } + + hasnodataL = rt_band_get_hasnodata_flag(bandL); + if (hasnodataL != FALSE) + rt_band_get_nodata(bandL, &nodataL); + + /* no band to use, ignore nodata */ + if (nband1 < 0) { + hasnodataS = FALSE; + hasnodataL = FALSE; + } + + /* hasnodata(S|L) = TRUE and one of the two bands is isnodata */ + if ( + (hasnodataS && rt_band_get_isnodata_flag(bandS)) || + (hasnodataL && rt_band_get_isnodata_flag(bandL)) + ) { + RASTER_DEBUG(3, "One of the two raster bands is NODATA. The two rasters do not intersect"); + *intersects = 0; + return ES_NONE; + } + + /* special case where a raster can fit inside another raster's pixel */ + if (within != 0 && ((pixarea1 > area2) || (pixarea2 > area1))) { + RASTER_DEBUG(4, "Using special case of raster fitting into another raster's pixel"); + /* 3 x 3 search */ + for (coloffset = 0; coloffset < 3; coloffset++) { + for (rowoffset = 0; rowoffset < 3; rowoffset++) { + for (col = coloffset; col < *widthS; col += 3) { + for (row = rowoffset; row < *heightS; row += 3) { + if (hasnodataS == FALSE) + valS = 1; + else if (rt_band_get_pixel(bandS, col, row, &valS, &isnodataS) != ES_NONE) + continue; + + if ((hasnodataS == FALSE) || !isnodataS) { + rt_raster_cell_to_geopoint( + rastS, + col, row, + &(lineS[X1]), &(lineS[Y1]), + gtS + ); + + if (rt_raster_geopoint_to_cell( + rastL, + lineS[X1], lineS[Y1], + &(Qr[pX]), &(Qr[pY]), + igtL + ) != ES_NONE) { + continue; + } + + if ( + (Qr[pX] < 0 || Qr[pX] > *widthL || FLT_EQ(Qr[pX], *widthL)) || + (Qr[pY] < 0 || Qr[pY] > *heightL || FLT_EQ(Qr[pY], *heightL)) + ) { + continue; + } + + if (hasnodataS == FALSE) + valL = 1; + else if (rt_band_get_pixel(bandL, Qr[pX], Qr[pY], &valL, &isnodataL) != ES_NONE) + continue; + + if ((hasnodataL == FALSE) || !isnodataL) { + RASTER_DEBUG(3, "The two rasters do intersect"); + *intersects = 1; + return ES_NONE; + } + } + } + } + } + } + RASTER_DEBUG(4, "Smaller raster not in the other raster's pixel. Continuing"); + } + + RASTER_DEBUG(4, "Testing smaller raster vs larger raster"); + *intersects = rt_raster_intersects_algorithm( + rastS, rastL, + bandS, bandL, + hasnodataS, hasnodataL, + nodataS, nodataL + ); + + if (*intersects) return ES_NONE; + + RASTER_DEBUG(4, "Testing larger raster vs smaller raster"); + *intersects = rt_raster_intersects_algorithm( + rastL, rastS, + bandL, bandS, + hasnodataL, hasnodataS, + nodataL, nodataS + ); + + if (*intersects) return ES_NONE; + + RASTER_DEBUG(3, "The two rasters do not intersect"); + + *intersects = 0; + return ES_NONE; +} + diff --git a/raster/rt_core/rt_statistics.c b/raster/rt_core/rt_statistics.c new file mode 100644 index 000000000..245967297 --- /dev/null +++ b/raster/rt_core/rt_statistics.c @@ -0,0 +1,1853 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* quicksort +******************************************************************************/ + +#define SWAP(x, y) { double t; t = x; x = y; y = t; } +#define ORDER(x, y) if (x > y) SWAP(x, y) + +static double pivot(double *left, double *right) { + double l, m, r, *p; + + l = *left; + m = *(left + (right - left) / 2); + r = *right; + + /* order */ + ORDER(l, m); + ORDER(l, r); + ORDER(m, r); + + /* pivot is higher of two values */ + if (l < m) return m; + if (m < r) return r; + + /* find pivot that isn't left */ + for (p = left + 1; p <= right; ++p) { + if (*p != *left) + return (*p < *left) ? *left : *p; + } + + /* all values are same */ + return -1; +} + +static double *partition(double *left, double *right, double pivot) { + while (left <= right) { + while (*left < pivot) ++left; + while (*right >= pivot) --right; + + if (left < right) { + SWAP(*left, *right); + ++left; + --right; + } + } + + return left; +} + +static void quicksort(double *left, double *right) { + double p = pivot(left, right); + double *pos; + + if (p != -1) { + pos = partition(left, right, p); + quicksort(left, pos - 1); + quicksort(pos, right); + } +} + +/****************************************************************************** +* rt_band_get_summary_stats() +******************************************************************************/ + +/** + * Compute summary statistics for a band + * + * @param band : the band to query for summary stats + * @param exclude_nodata_value : if non-zero, ignore nodata values + * @param sample : percentage of pixels to sample + * @param inc_vals : flag to include values in return struct + * @param cK : number of pixels counted thus far in coverage + * @param cM : M component of 1-pass stddev for coverage + * @param cQ : Q component of 1-pass stddev for coverage + * + * @return the summary statistics for a band or NULL + */ +rt_bandstats +rt_band_get_summary_stats( + rt_band band, + int exclude_nodata_value, double sample, int inc_vals, + uint64_t *cK, double *cM, double *cQ +) { + uint32_t x = 0; + uint32_t y = 0; + uint32_t z = 0; + uint32_t offset = 0; + uint32_t diff = 0; + int rtn; + int hasnodata = FALSE; + double nodata = 0; + double *values = NULL; + double value; + int isnodata = 0; + rt_bandstats stats = NULL; + + uint32_t do_sample = 0; + uint32_t sample_size = 0; + uint32_t sample_per = 0; + uint32_t sample_int = 0; + uint32_t i = 0; + uint32_t j = 0; + double sum = 0; + uint32_t k = 0; + double M = 0; + double Q = 0; + +#if POSTGIS_DEBUG_LEVEL > 0 + clock_t start, stop; + double elapsed = 0; +#endif + + RASTER_DEBUG(3, "starting"); +#if POSTGIS_DEBUG_LEVEL > 0 + start = clock(); +#endif + + assert(NULL != band); + + /* band is empty (width < 1 || height < 1) */ + if (band->width < 1 || band->height < 1) { + stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); + if (NULL == stats) { + rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); + return NULL; + } + + rtwarn("Band is empty as width and/or height is 0"); + + stats->sample = 1; + stats->sorted = 0; + stats->values = NULL; + stats->count = 0; + stats->min = stats->max = 0; + stats->sum = 0; + stats->mean = 0; + stats->stddev = -1; + + return stats; + } + + hasnodata = rt_band_get_hasnodata_flag(band); + if (hasnodata != FALSE) + rt_band_get_nodata(band, &nodata); + else + exclude_nodata_value = 0; + + RASTER_DEBUGF(3, "nodata = %f", nodata); + RASTER_DEBUGF(3, "hasnodata = %d", hasnodata); + RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); + + /* entire band is nodata */ + if (rt_band_get_isnodata_flag(band) != FALSE) { + stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); + if (NULL == stats) { + rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); + return NULL; + } + + stats->sample = 1; + stats->sorted = 0; + stats->values = NULL; + + if (exclude_nodata_value) { + rtwarn("All pixels of band have the NODATA value"); + + stats->count = 0; + stats->min = stats->max = 0; + stats->sum = 0; + stats->mean = 0; + stats->stddev = -1; + } + else { + stats->count = band->width * band->height; + stats->min = stats->max = nodata; + stats->sum = stats->count * nodata; + stats->mean = nodata; + stats->stddev = 0; + } + + return stats; + } + + /* clamp percentage */ + if ( + (sample < 0 || FLT_EQ(sample, 0.0)) || + (sample > 1 || FLT_EQ(sample, 1.0)) + ) { + do_sample = 0; + sample = 1; + } + else + do_sample = 1; + RASTER_DEBUGF(3, "do_sample = %d", do_sample); + + /* sample all pixels */ + if (!do_sample) { + sample_size = band->width * band->height; + sample_per = band->height; + } + /* + randomly sample a percentage of available pixels + sampling method is known as + "systematic random sample without replacement" + */ + else { + sample_size = round((band->width * band->height) * sample); + sample_per = round(sample_size / band->width); + if (sample_per < 1) + sample_per = 1; + sample_int = round(band->height / sample_per); + srand(time(NULL)); + } + + RASTER_DEBUGF(3, "sampling %d of %d available pixels w/ %d per set" + , sample_size, (band->width * band->height), sample_per); + + if (inc_vals) { + values = rtalloc(sizeof(double) * sample_size); + if (NULL == values) { + rtwarn("Could not allocate memory for values"); + inc_vals = 0; + } + } + + /* initialize stats */ + stats = (rt_bandstats) rtalloc(sizeof(struct rt_bandstats_t)); + if (NULL == stats) { + rterror("rt_band_get_summary_stats: Could not allocate memory for stats"); + return NULL; + } + stats->sample = sample; + stats->count = 0; + stats->sum = 0; + stats->mean = 0; + stats->stddev = -1; + stats->min = stats->max = 0; + stats->values = NULL; + stats->sorted = 0; + + for (x = 0, j = 0, k = 0; x < band->width; x++) { + y = -1; + diff = 0; + + for (i = 0, z = 0; i < sample_per; i++) { + if (!do_sample) + y = i; + else { + offset = (rand() % sample_int) + 1; + y += diff + offset; + diff = sample_int - offset; + } + RASTER_DEBUGF(5, "(x, y, z) = (%d, %d, %d)", x, y, z); + if (y >= band->height || z > sample_per) break; + + rtn = rt_band_get_pixel(band, x, y, &value, &isnodata); + + j++; + if (rtn == ES_NONE && (!exclude_nodata_value || (exclude_nodata_value && !isnodata))) { + + /* inc_vals set, collect pixel values */ + if (inc_vals) values[k] = value; + + /* average */ + k++; + sum += value; + + /* + one-pass standard deviation + http://www.eecs.berkeley.edu/~mhoemmen/cs194/Tutorials/variance.pdf + */ + if (k == 1) { + Q = 0; + M = value; + } + else { + Q += (((k - 1) * pow(value - M, 2)) / k); + M += ((value - M ) / k); + } + + /* coverage one-pass standard deviation */ + if (NULL != cK) { + (*cK)++; + if (*cK == 1) { + *cQ = 0; + *cM = value; + } + else { + *cQ += (((*cK - 1) * pow(value - *cM, 2)) / *cK); + *cM += ((value - *cM ) / *cK); + } + } + + /* min/max */ + if (stats->count < 1) { + stats->count = 1; + stats->min = stats->max = value; + } + else { + if (value < stats->min) + stats->min = value; + if (value > stats->max) + stats->max = value; + } + + } + + z++; + } + } + + RASTER_DEBUG(3, "sampling complete"); + + stats->count = k; + if (k > 0) { + if (inc_vals) { + /* free unused memory */ + if (sample_size != k) { + values = rtrealloc(values, k * sizeof(double)); + } + + stats->values = values; + } + + stats->sum = sum; + stats->mean = sum / k; + + /* standard deviation */ + if (!do_sample) + stats->stddev = sqrt(Q / k); + /* sample deviation */ + else { + if (k < 2) + stats->stddev = -1; + else + stats->stddev = sqrt(Q / (k - 1)); + } + } + /* inc_vals thus values allocated but not used */ + else if (inc_vals) + rtdealloc(values); + + /* if do_sample is one */ + if (do_sample && k < 1) + rtwarn("All sampled pixels of band have the NODATA value"); + +#if POSTGIS_DEBUG_LEVEL > 0 + stop = clock(); + elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; + RASTER_DEBUGF(3, "(time, count, mean, stddev, min, max) = (%0.4f, %d, %f, %f, %f, %f)", + elapsed, stats->count, stats->mean, stats->stddev, stats->min, stats->max); +#endif + + RASTER_DEBUG(3, "done"); + return stats; +} + +/****************************************************************************** +* rt_band_get_histogram() +******************************************************************************/ + +/** + * Count the distribution of data + * + * @param stats : a populated stats struct for processing + * @param bin_count : the number of bins to group the data by + * @param bin_width : the width of each bin as an array + * @param bin_width_count : number of values in bin_width + * @param right : evaluate bins by (a,b] rather than default [a,b) + * @param min : user-defined minimum value of the histogram + * a value less than the minimum value is not counted in any bins + * if min = max, min and max are not used + * @param max : user-defined maximum value of the histogram + * a value greater than the max value is not counted in any bins + * if min = max, min and max are not used + * @param rtn_count : set to the number of bins being returned + * + * @return the histogram of the data or NULL + */ +rt_histogram +rt_band_get_histogram( + rt_bandstats stats, + int bin_count, double *bin_width, int bin_width_count, + int right, double min, double max, + uint32_t *rtn_count +) { + rt_histogram bins = NULL; + int init_width = 0; + int i; + int j; + double tmp; + double value; + int sum = 0; + double qmin; + double qmax; + +#if POSTGIS_DEBUG_LEVEL > 0 + clock_t start, stop; + double elapsed = 0; +#endif + + RASTER_DEBUG(3, "starting"); +#if POSTGIS_DEBUG_LEVEL > 0 + start = clock(); +#endif + + assert(NULL != stats); + assert(NULL != rtn_count); + + if (stats->count < 1 || NULL == stats->values) { + rterror("rt_util_get_histogram: rt_bandstats object has no value"); + return NULL; + } + + /* bin width must be positive numbers and not zero */ + if (NULL != bin_width && bin_width_count > 0) { + for (i = 0; i < bin_width_count; i++) { + if (bin_width[i] < 0 || FLT_EQ(bin_width[i], 0.0)) { + rterror("rt_util_get_histogram: bin_width element is less than or equal to zero"); + return NULL; + } + } + } + + /* ignore min and max parameters */ + if (FLT_EQ(max, min)) { + qmin = stats->min; + qmax = stats->max; + } + else { + qmin = min; + qmax = max; + if (qmin > qmax) { + qmin = max; + qmax = min; + } + } + + /* # of bins not provided */ + if (bin_count <= 0) { + /* + determine # of bins + http://en.wikipedia.org/wiki/Histogram + + all computed bins are assumed to have equal width + */ + /* Square-root choice for stats->count < 30 */ + if (stats->count < 30) + bin_count = ceil(sqrt(stats->count)); + /* Sturges' formula for stats->count >= 30 */ + else + bin_count = ceil(log2((double) stats->count) + 1.); + + /* bin_width_count provided and bin_width has value */ + if (bin_width_count > 0 && NULL != bin_width) { + /* user has defined something specific */ + if (bin_width_count > bin_count) + bin_count = bin_width_count; + else if (bin_width_count > 1) { + tmp = 0; + for (i = 0; i < bin_width_count; i++) tmp += bin_width[i]; + bin_count = ceil((qmax - qmin) / tmp) * bin_width_count; + } + else + bin_count = ceil((qmax - qmin) / bin_width[0]); + } + /* set bin width count to zero so that one can be calculated */ + else { + bin_width_count = 0; + } + } + + /* min and max the same */ + if (FLT_EQ(qmax, qmin)) bin_count = 1; + + RASTER_DEBUGF(3, "bin_count = %d", bin_count); + + /* bin count = 1, all values are in one bin */ + if (bin_count < 2) { + bins = rtalloc(sizeof(struct rt_histogram_t)); + if (NULL == bins) { + rterror("rt_util_get_histogram: Could not allocate memory for histogram"); + return NULL; + } + + bins->count = stats->count; + bins->percent = -1; + bins->min = qmin; + bins->max = qmax; + bins->inc_min = bins->inc_max = 1; + + *rtn_count = bin_count; + return bins; + } + + /* establish bin width */ + if (bin_width_count == 0) { + bin_width_count = 1; + + /* bin_width unallocated */ + if (NULL == bin_width) { + bin_width = rtalloc(sizeof(double)); + if (NULL == bin_width) { + rterror("rt_util_get_histogram: Could not allocate memory for bin widths"); + return NULL; + } + init_width = 1; + } + + bin_width[0] = (qmax - qmin) / bin_count; + } + + /* initialize bins */ + bins = rtalloc(bin_count * sizeof(struct rt_histogram_t)); + if (NULL == bins) { + rterror("rt_util_get_histogram: Could not allocate memory for histogram"); + if (init_width) rtdealloc(bin_width); + return NULL; + } + if (!right) + tmp = qmin; + else + tmp = qmax; + for (i = 0; i < bin_count;) { + for (j = 0; j < bin_width_count; j++) { + bins[i].count = 0; + bins->percent = -1; + + if (!right) { + bins[i].min = tmp; + tmp += bin_width[j]; + bins[i].max = tmp; + + bins[i].inc_min = 1; + bins[i].inc_max = 0; + } + else { + bins[i].max = tmp; + tmp -= bin_width[j]; + bins[i].min = tmp; + + bins[i].inc_min = 0; + bins[i].inc_max = 1; + } + + i++; + } + } + if (!right) { + bins[bin_count - 1].inc_max = 1; + + /* align last bin to the max value */ + if (bins[bin_count - 1].max < qmax) + bins[bin_count - 1].max = qmax; + } + else { + bins[bin_count - 1].inc_min = 1; + + /* align first bin to the min value */ + if (bins[bin_count - 1].min > qmin) + bins[bin_count - 1].min = qmin; + } + + /* process the values */ + for (i = 0; i < stats->count; i++) { + value = stats->values[i]; + + /* default, [a, b) */ + if (!right) { + for (j = 0; j < bin_count; j++) { + if ( + (!bins[j].inc_max && value < bins[j].max) || ( + bins[j].inc_max && ( + (value < bins[j].max) || + FLT_EQ(value, bins[j].max) + ) + ) + ) { + bins[j].count++; + sum++; + break; + } + } + } + /* (a, b] */ + else { + for (j = 0; j < bin_count; j++) { + if ( + (!bins[j].inc_min && value > bins[j].min) || ( + bins[j].inc_min && ( + (value > bins[j].min) || + FLT_EQ(value, bins[j].min) + ) + ) + ) { + bins[j].count++; + sum++; + break; + } + } + } + } + + for (i = 0; i < bin_count; i++) { + bins[i].percent = ((double) bins[i].count) / sum; + } + +#if POSTGIS_DEBUG_LEVEL > 0 + stop = clock(); + elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; + RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); + + for (j = 0; j < bin_count; j++) { + RASTER_DEBUGF(5, "(min, max, inc_min, inc_max, count, sum, percent) = (%f, %f, %d, %d, %d, %d, %f)", + bins[j].min, bins[j].max, bins[j].inc_min, bins[j].inc_max, bins[j].count, sum, bins[j].percent); + } +#endif + + if (init_width) rtdealloc(bin_width); + *rtn_count = bin_count; + RASTER_DEBUG(3, "done"); + return bins; +} + +/****************************************************************************** +* rt_band_get_quantiles() +******************************************************************************/ + +/** + * Compute the default set of or requested quantiles for a set of data + * the quantile formula used is same as Excel and R default method + * + * @param stats : a populated stats struct for processing + * @param quantiles : the quantiles to be computed + * @param quantiles_count : the number of quantiles to be computed + * @param rtn_count : set to the number of quantiles being returned + * + * @return the default set of or requested quantiles for a band or NULL + */ +rt_quantile +rt_band_get_quantiles( + rt_bandstats stats, + double *quantiles, int quantiles_count, + uint32_t *rtn_count +) { + rt_quantile rtn; + int init_quantiles = 0; + int i = 0; + double h; + int hl; + +#if POSTGIS_DEBUG_LEVEL > 0 + clock_t start, stop; + double elapsed = 0; +#endif + + RASTER_DEBUG(3, "starting"); +#if POSTGIS_DEBUG_LEVEL > 0 + start = clock(); +#endif + + assert(NULL != stats); + assert(NULL != rtn_count); + + if (stats->count < 1 || NULL == stats->values) { + rterror("rt_band_get_quantiles: rt_bandstats object has no value"); + return NULL; + } + + /* quantiles not provided */ + if (NULL == quantiles) { + /* quantile count not specified, default to quartiles */ + if (quantiles_count < 2) + quantiles_count = 5; + + quantiles = rtalloc(sizeof(double) * quantiles_count); + init_quantiles = 1; + if (NULL == quantiles) { + rterror("rt_band_get_quantiles: Could not allocate memory for quantile input"); + return NULL; + } + + quantiles_count--; + for (i = 0; i <= quantiles_count; i++) + quantiles[i] = ((double) i) / quantiles_count; + quantiles_count++; + } + + /* check quantiles */ + for (i = 0; i < quantiles_count; i++) { + if (quantiles[i] < 0. || quantiles[i] > 1.) { + rterror("rt_band_get_quantiles: Quantile value not between 0 and 1"); + if (init_quantiles) rtdealloc(quantiles); + return NULL; + } + } + quicksort(quantiles, quantiles + quantiles_count - 1); + + /* initialize rt_quantile */ + rtn = rtalloc(sizeof(struct rt_quantile_t) * quantiles_count); + if (NULL == rtn) { + rterror("rt_band_get_quantiles: Could not allocate memory for quantile output"); + if (init_quantiles) rtdealloc(quantiles); + return NULL; + } + + /* sort values */ + if (!stats->sorted) { + quicksort(stats->values, stats->values + stats->count - 1); + stats->sorted = 1; + } + + /* + make quantiles + + formula is that used in R (method 7) and Excel from + http://en.wikipedia.org/wiki/Quantile + */ + for (i = 0; i < quantiles_count; i++) { + rtn[i].quantile = quantiles[i]; + + h = ((stats->count - 1.) * quantiles[i]) + 1.; + hl = floor(h); + + /* h greater than hl, do full equation */ + if (h > hl) + rtn[i].value = stats->values[hl - 1] + ((h - hl) * (stats->values[hl] - stats->values[hl - 1])); + /* shortcut as second part of equation is zero */ + else + rtn[i].value = stats->values[hl - 1]; + } + +#if POSTGIS_DEBUG_LEVEL > 0 + stop = clock(); + elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; + RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); +#endif + + *rtn_count = quantiles_count; + if (init_quantiles) rtdealloc(quantiles); + RASTER_DEBUG(3, "done"); + return rtn; +} + +/****************************************************************************** +* rt_band_get_quantiles_stream() +******************************************************************************/ + +static struct quantile_llist_element *quantile_llist_search( + struct quantile_llist_element *element, + double needle +) { + if (NULL == element) + return NULL; + else if (FLT_NEQ(needle, element->value)) { + if (NULL != element->next) + return quantile_llist_search(element->next, needle); + else + return NULL; + } + else + return element; +} + +static struct quantile_llist_element *quantile_llist_insert( + struct quantile_llist_element *element, + double value, + uint32_t *idx +) { + struct quantile_llist_element *qle = NULL; + + if (NULL == element) { + qle = rtalloc(sizeof(struct quantile_llist_element)); + RASTER_DEBUGF(4, "qle @ %p is only element in list", qle); + if (NULL == qle) return NULL; + + qle->value = value; + qle->count = 1; + + qle->prev = NULL; + qle->next = NULL; + + if (NULL != idx) *idx = 0; + return qle; + } + else if (value > element->value) { + if (NULL != idx) *idx += 1; + if (NULL != element->next) + return quantile_llist_insert(element->next, value, idx); + /* insert as last element in list */ + else { + qle = rtalloc(sizeof(struct quantile_llist_element)); + RASTER_DEBUGF(4, "insert qle @ %p as last element", qle); + if (NULL == qle) return NULL; + + qle->value = value; + qle->count = 1; + + qle->prev = element; + qle->next = NULL; + element->next = qle; + + return qle; + } + } + /* insert before current element */ + else { + qle = rtalloc(sizeof(struct quantile_llist_element)); + RASTER_DEBUGF(4, "insert qle @ %p before current element", qle); + if (NULL == qle) return NULL; + + qle->value = value; + qle->count = 1; + + if (NULL != element->prev) element->prev->next = qle; + qle->next = element; + qle->prev = element->prev; + element->prev = qle; + + return qle; + } +} + +static int quantile_llist_delete(struct quantile_llist_element *element) { + if (NULL == element) return 0; + + /* beginning of list */ + if (NULL == element->prev && NULL != element->next) { + element->next->prev = NULL; + } + /* end of list */ + else if (NULL != element->prev && NULL == element->next) { + element->prev->next = NULL; + } + /* within list */ + else if (NULL != element->prev && NULL != element->next) { + element->prev->next = element->next; + element->next->prev = element->prev; + } + + RASTER_DEBUGF(4, "qle @ %p destroyed", element); + rtdealloc(element); + + return 1; +} + +int quantile_llist_destroy(struct quantile_llist **list, uint32_t list_count) { + struct quantile_llist_element *element = NULL; + uint32_t i; + + if (NULL == *list) return 0; + + for (i = 0; i < list_count; i++) { + element = (*list)[i].head; + while (NULL != element->next) { + quantile_llist_delete(element->next); + } + quantile_llist_delete(element); + + rtdealloc((*list)[i].index); + } + + rtdealloc(*list); + return 1; +} + +static void quantile_llist_index_update(struct quantile_llist *qll, struct quantile_llist_element *qle, uint32_t idx) { + uint32_t anchor = (uint32_t) floor(idx / 100); + + if (qll->tail == qle) return; + + if ( + (anchor != 0) && ( + NULL == qll->index[anchor].element || + idx <= qll->index[anchor].index + ) + ) { + qll->index[anchor].index = idx; + qll->index[anchor].element = qle; + } + + if (anchor != 0 && NULL == qll->index[0].element) { + qll->index[0].index = 0; + qll->index[0].element = qll->head; + } +} + +static void quantile_llist_index_delete(struct quantile_llist *qll, struct quantile_llist_element *qle) { + uint32_t i = 0; + + for (i = 0; i < qll->index_max; i++) { + if ( + NULL == qll->index[i].element || + (qll->index[i].element) != qle + ) { + continue; + } + + RASTER_DEBUGF(5, "deleting index: %d => %f", i, qle->value); + qll->index[i].index = -1; + qll->index[i].element = NULL; + } +} + +static struct quantile_llist_element *quantile_llist_index_search( + struct quantile_llist *qll, + double value, + uint32_t *index +) { + uint32_t i = 0, j = 0; + RASTER_DEBUGF(5, "searching index for value %f", value); + + for (i = 0; i < qll->index_max; i++) { + if (NULL == qll->index[i].element) { + if (i < 1) break; + continue; + } + if (value > (qll->index[i]).element->value) continue; + + if (FLT_EQ(value, qll->index[i].element->value)) { + RASTER_DEBUGF(5, "using index value at %d = %f", i, qll->index[i].element->value); + *index = i * 100; + return qll->index[i].element; + } + else if (i > 0) { + for (j = 1; j < i; j++) { + if (NULL != qll->index[i - j].element) { + RASTER_DEBUGF(5, "using index value at %d = %f", i - j, qll->index[i - j].element->value); + *index = (i - j) * 100; + return qll->index[i - j].element; + } + } + } + } + + *index = 0; + return qll->head; +} + +static void quantile_llist_index_reset(struct quantile_llist *qll) { + uint32_t i = 0; + + RASTER_DEBUG(5, "resetting index"); + for (i = 0; i < qll->index_max; i++) { + qll->index[i].index = -1; + qll->index[i].element = NULL; + } +} + + +/** + * Compute the default set of or requested quantiles for a coverage + * + * This function is based upon the algorithm described in: + * + * A One-Pass Space-Efficient Algorithm for Finding Quantiles (1995) + * by Rakesh Agrawal, Arun Swami + * in Proc. 7th Intl. Conf. Management of Data (COMAD-95) + * + * http://www.almaden.ibm.com/cs/projects/iis/hdb/Publications/papers/comad95.pdf + * + * In the future, it may be worth exploring algorithms that don't + * require the size of the coverage + * + * @param band : the band to include in the quantile search + * @param exclude_nodata_value : if non-zero, ignore nodata values + * @param sample : percentage of pixels to sample + * @param cov_count : number of values in coverage + * @param qlls : set of quantile_llist structures + * @param qlls_count : the number of quantile_llist structures + * @param quantiles : the quantiles to be computed + * if bot qlls and quantiles provided, qlls is used + * @param quantiles_count : the number of quantiles to be computed + * @param rtn_count : the number of quantiles being returned + * + * @return the default set of or requested quantiles for a band or NULL + */ +rt_quantile +rt_band_get_quantiles_stream( + rt_band band, + int exclude_nodata_value, double sample, + uint64_t cov_count, + struct quantile_llist **qlls, uint32_t *qlls_count, + double *quantiles, int quantiles_count, + uint32_t *rtn_count +) { + rt_quantile rtn = NULL; + int init_quantiles = 0; + + struct quantile_llist *qll = NULL; + struct quantile_llist_element *qle = NULL; + struct quantile_llist_element *qls = NULL; + const uint32_t MAX_VALUES = 750; + + uint8_t *data = NULL; + double value; + int isnodata = 0; + + uint32_t a = 0; + uint32_t i = 0; + uint32_t j = 0; + uint32_t k = 0; + uint32_t x = 0; + uint32_t y = 0; + uint32_t z = 0; + uint32_t idx = 0; + uint32_t offset = 0; + uint32_t diff = 0; + uint8_t exists = 0; + + uint32_t do_sample = 0; + uint32_t sample_size = 0; + uint32_t sample_per = 0; + uint32_t sample_int = 0; + int status; + + RASTER_DEBUG(3, "starting"); + + assert(NULL != band); + assert(cov_count > 1); + assert(NULL != rtn_count); + RASTER_DEBUGF(3, "cov_count = %d", cov_count); + + data = rt_band_get_data(band); + if (data == NULL) { + rterror("rt_band_get_summary_stats: Cannot get band data"); + return NULL; + } + + if (!rt_band_get_hasnodata_flag(band)) + exclude_nodata_value = 0; + RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); + + /* quantile_llist not provided */ + if (NULL == *qlls) { + /* quantiles not provided */ + if (NULL == quantiles) { + /* quantile count not specified, default to quartiles */ + if (quantiles_count < 2) + quantiles_count = 5; + + quantiles = rtalloc(sizeof(double) * quantiles_count); + init_quantiles = 1; + if (NULL == quantiles) { + rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile input"); + return NULL; + } + + quantiles_count--; + for (i = 0; i <= quantiles_count; i++) + quantiles[i] = ((double) i) / quantiles_count; + quantiles_count++; + } + + /* check quantiles */ + for (i = 0; i < quantiles_count; i++) { + if (quantiles[i] < 0. || quantiles[i] > 1.) { + rterror("rt_band_get_quantiles_stream: Quantile value not between 0 and 1"); + if (init_quantiles) rtdealloc(quantiles); + return NULL; + } + } + quicksort(quantiles, quantiles + quantiles_count - 1); + + /* initialize linked-list set */ + *qlls_count = quantiles_count * 2; + RASTER_DEBUGF(4, "qlls_count = %d", *qlls_count); + *qlls = rtalloc(sizeof(struct quantile_llist) * *qlls_count); + if (NULL == *qlls) { + rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile output"); + if (init_quantiles) rtdealloc(quantiles); + return NULL; + } + + j = (uint32_t) floor(MAX_VALUES / 100.) + 1; + for (i = 0; i < *qlls_count; i++) { + qll = &((*qlls)[i]); + qll->quantile = quantiles[(i * quantiles_count) / *qlls_count]; + qll->count = 0; + qll->sum1 = 0; + qll->sum2 = 0; + qll->head = NULL; + qll->tail = NULL; + + /* initialize index */ + qll->index = rtalloc(sizeof(struct quantile_llist_index) * j); + if (NULL == qll->index) { + rterror("rt_band_get_quantiles_stream: Could not allocate memory for quantile output"); + if (init_quantiles) rtdealloc(quantiles); + return NULL; + } + qll->index_max = j; + quantile_llist_index_reset(qll); + + /* AL-GEQ */ + if (!(i % 2)) { + qll->algeq = 1; + qll->tau = (uint64_t) ROUND(cov_count - (cov_count * qll->quantile), 0); + if (qll->tau < 1) qll->tau = 1; + } + /* AL-GT */ + else { + qll->algeq = 0; + qll->tau = cov_count - (*qlls)[i - 1].tau + 1; + } + + RASTER_DEBUGF(4, "qll init: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", + qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); + RASTER_DEBUGF(4, "qll init: (head, tail) = (%p, %p)", qll->head, qll->tail); + } + + if (init_quantiles) rtdealloc(quantiles); + } + + /* clamp percentage */ + if ( + (sample < 0 || FLT_EQ(sample, 0.0)) || + (sample > 1 || FLT_EQ(sample, 1.0)) + ) { + do_sample = 0; + sample = 1; + } + else + do_sample = 1; + RASTER_DEBUGF(3, "do_sample = %d", do_sample); + + /* sample all pixels */ + if (!do_sample) { + sample_size = band->width * band->height; + sample_per = band->height; + } + /* + randomly sample a percentage of available pixels + sampling method is known as + "systematic random sample without replacement" + */ + else { + sample_size = round((band->width * band->height) * sample); + sample_per = round(sample_size / band->width); + sample_int = round(band->height / sample_per); + srand(time(NULL)); + } + RASTER_DEBUGF(3, "sampling %d of %d available pixels w/ %d per set" + , sample_size, (band->width * band->height), sample_per); + + for (x = 0, j = 0, k = 0; x < band->width; x++) { + y = -1; + diff = 0; + + /* exclude_nodata_value = TRUE and band is NODATA */ + if (exclude_nodata_value && rt_band_get_isnodata_flag(band)) { + RASTER_DEBUG(3, "Skipping quantile calcuation as band is NODATA"); + break; + } + + for (i = 0, z = 0; i < sample_per; i++) { + if (do_sample != 1) + y = i; + else { + offset = (rand() % sample_int) + 1; + y += diff + offset; + diff = sample_int - offset; + } + RASTER_DEBUGF(5, "(x, y, z) = (%d, %d, %d)", x, y, z); + if (y >= band->height || z > sample_per) break; + + status = rt_band_get_pixel(band, x, y, &value, &isnodata); + + j++; + if (status == ES_NONE && (!exclude_nodata_value || (exclude_nodata_value && !isnodata))) { + + /* process each quantile */ + for (a = 0; a < *qlls_count; a++) { + qll = &((*qlls)[a]); + qls = NULL; + RASTER_DEBUGF(4, "%d of %d (%f)", a + 1, *qlls_count, qll->quantile); + RASTER_DEBUGF(5, "qll before: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", + qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); + RASTER_DEBUGF(5, "qll before: (head, tail) = (%p, %p)", qll->head, qll->tail); + + /* OPTIMIZATION: shortcuts for quantiles of zero or one */ + if (FLT_EQ(qll->quantile, 0.)) { + if (NULL != qll->head) { + if (value < qll->head->value) + qll->head->value = value; + } + else { + qle = quantile_llist_insert(qll->head, value, NULL); + qll->head = qle; + qll->tail = qle; + qll->count = 1; + } + + RASTER_DEBUGF(4, "quantile shortcut for %f\n\n", qll->quantile); + continue; + } + else if (FLT_EQ(qll->quantile, 1.)) { + if (NULL != qll->head) { + if (value > qll->head->value) + qll->head->value = value; + } + else { + qle = quantile_llist_insert(qll->head, value, NULL); + qll->head = qle; + qll->tail = qle; + qll->count = 1; + } + + RASTER_DEBUGF(4, "quantile shortcut for %f\n\n", qll->quantile); + continue; + } + + /* value exists in list */ + /* OPTIMIZATION: check to see if value exceeds last value */ + if (NULL != qll->tail && value > qll->tail->value) + qle = NULL; + /* OPTIMIZATION: check to see if value equals last value */ + else if (NULL != qll->tail && FLT_EQ(value, qll->tail->value)) + qle = qll->tail; + /* OPTIMIZATION: use index if possible */ + else { + qls = quantile_llist_index_search(qll, value, &idx); + qle = quantile_llist_search(qls, value); + } + + /* value found */ + if (NULL != qle) { + RASTER_DEBUGF(4, "%f found in list", value); + RASTER_DEBUGF(5, "qle before: (value, count) = (%f, %d)", qle->value, qle->count); + + qle->count++; + qll->sum1++; + + if (qll->algeq) + qll->sum2 = qll->sum1 - qll->head->count; + else + qll->sum2 = qll->sum1 - qll->tail->count; + + RASTER_DEBUGF(4, "qle after: (value, count) = (%f, %d)", qle->value, qle->count); + } + /* can still add element */ + else if (qll->count < MAX_VALUES) { + RASTER_DEBUGF(4, "Adding %f to list", value); + + /* insert */ + /* OPTIMIZATION: check to see if value exceeds last value */ + if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { + idx = qll->count; + qle = quantile_llist_insert(qll->tail, value, &idx); + } + /* OPTIMIZATION: use index if possible */ + else + qle = quantile_llist_insert(qls, value, &idx); + if (NULL == qle) return NULL; + RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); + qll->count++; + qll->sum1++; + + /* first element */ + if (NULL == qle->prev) + qll->head = qle; + /* last element */ + if (NULL == qle->next) + qll->tail = qle; + + if (qll->algeq) + qll->sum2 = qll->sum1 - qll->head->count; + else + qll->sum2 = qll->sum1 - qll->tail->count; + + /* index is only needed if there are at least 100 values */ + quantile_llist_index_update(qll, qle, idx); + + RASTER_DEBUGF(5, "qle, prev, next, head, tail = %p, %p, %p, %p, %p", qle, qle->prev, qle->next, qll->head, qll->tail); + } + /* AL-GEQ */ + else if (qll->algeq) { + RASTER_DEBUGF(4, "value, head->value = %f, %f", value, qll->head->value); + + if (value < qll->head->value) { + /* ignore value if test isn't true */ + if (qll->sum1 >= qll->tau) { + RASTER_DEBUGF(4, "skipping %f", value); + } + else { + + /* delete last element */ + RASTER_DEBUGF(4, "deleting %f from list", qll->tail->value); + qle = qll->tail->prev; + RASTER_DEBUGF(5, "to-be tail is %f with count %d", qle->value, qle->count); + qle->count += qll->tail->count; + quantile_llist_index_delete(qll, qll->tail); + quantile_llist_delete(qll->tail); + qll->tail = qle; + qll->count--; + RASTER_DEBUGF(5, "tail is %f with count %d", qll->tail->value, qll->tail->count); + + /* insert value */ + RASTER_DEBUGF(4, "adding %f to list", value); + /* OPTIMIZATION: check to see if value exceeds last value */ + if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { + idx = qll->count; + qle = quantile_llist_insert(qll->tail, value, &idx); + } + /* OPTIMIZATION: use index if possible */ + else { + qls = quantile_llist_index_search(qll, value, &idx); + qle = quantile_llist_insert(qls, value, &idx); + } + if (NULL == qle) return NULL; + RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); + qll->count++; + qll->sum1++; + + /* first element */ + if (NULL == qle->prev) + qll->head = qle; + /* last element */ + if (NULL == qle->next) + qll->tail = qle; + + qll->sum2 = qll->sum1 - qll->head->count; + + quantile_llist_index_update(qll, qle, idx); + + RASTER_DEBUGF(5, "qle, head, tail = %p, %p, %p", qle, qll->head, qll->tail); + + } + } + else { + qle = qll->tail; + while (NULL != qle) { + if (qle->value < value) { + qle->count++; + qll->sum1++; + qll->sum2 = qll->sum1 - qll->head->count; + RASTER_DEBUGF(4, "incremented count of %f by 1 to %d", qle->value, qle->count); + break; + } + + qle = qle->prev; + } + } + } + /* AL-GT */ + else { + RASTER_DEBUGF(4, "value, tail->value = %f, %f", value, qll->tail->value); + + if (value > qll->tail->value) { + /* ignore value if test isn't true */ + if (qll->sum1 >= qll->tau) { + RASTER_DEBUGF(4, "skipping %f", value); + } + else { + + /* delete last element */ + RASTER_DEBUGF(4, "deleting %f from list", qll->head->value); + qle = qll->head->next; + RASTER_DEBUGF(5, "to-be tail is %f with count %d", qle->value, qle->count); + qle->count += qll->head->count; + quantile_llist_index_delete(qll, qll->head); + quantile_llist_delete(qll->head); + qll->head = qle; + qll->count--; + quantile_llist_index_update(qll, NULL, 0); + RASTER_DEBUGF(5, "tail is %f with count %d", qll->head->value, qll->head->count); + + /* insert value */ + RASTER_DEBUGF(4, "adding %f to list", value); + /* OPTIMIZATION: check to see if value exceeds last value */ + if (NULL != qll->tail && (value > qll->tail->value || FLT_EQ(value, qll->tail->value))) { + idx = qll->count; + qle = quantile_llist_insert(qll->tail, value, &idx); + } + /* OPTIMIZATION: use index if possible */ + else { + qls = quantile_llist_index_search(qll, value, &idx); + qle = quantile_llist_insert(qls, value, &idx); + } + if (NULL == qle) return NULL; + RASTER_DEBUGF(5, "value added at index: %d => %f", idx, value); + qll->count++; + qll->sum1++; + + /* first element */ + if (NULL == qle->prev) + qll->head = qle; + /* last element */ + if (NULL == qle->next) + qll->tail = qle; + + qll->sum2 = qll->sum1 - qll->tail->count; + + quantile_llist_index_update(qll, qle, idx); + + RASTER_DEBUGF(5, "qle, head, tail = %p, %p, %p", qle, qll->head, qll->tail); + + } + } + else { + qle = qll->head; + while (NULL != qle) { + if (qle->value > value) { + qle->count++; + qll->sum1++; + qll->sum2 = qll->sum1 - qll->tail->count; + RASTER_DEBUGF(4, "incremented count of %f by 1 to %d", qle->value, qle->count); + break; + } + + qle = qle->next; + } + } + } + + RASTER_DEBUGF(5, "sum2, tau = %d, %d", qll->sum2, qll->tau); + if (qll->sum2 >= qll->tau) { + /* AL-GEQ */ + if (qll->algeq) { + RASTER_DEBUGF(4, "deleting first element %f from list", qll->head->value); + + if (NULL != qll->head->next) { + qle = qll->head->next; + qll->sum1 -= qll->head->count; + qll->sum2 = qll->sum1 - qle->count; + quantile_llist_index_delete(qll, qll->head); + quantile_llist_delete(qll->head); + qll->head = qle; + qll->count--; + + quantile_llist_index_update(qll, NULL, 0); + } + else { + quantile_llist_delete(qll->head); + qll->head = NULL; + qll->tail = NULL; + qll->sum1 = 0; + qll->sum2 = 0; + qll->count = 0; + + quantile_llist_index_reset(qll); + } + } + /* AL-GT */ + else { + RASTER_DEBUGF(4, "deleting first element %f from list", qll->tail->value); + + if (NULL != qll->tail->prev) { + qle = qll->tail->prev; + qll->sum1 -= qll->tail->count; + qll->sum2 = qll->sum1 - qle->count; + quantile_llist_index_delete(qll, qll->tail); + quantile_llist_delete(qll->tail); + qll->tail = qle; + qll->count--; + } + else { + quantile_llist_delete(qll->tail); + qll->head = NULL; + qll->tail = NULL; + qll->sum1 = 0; + qll->sum2 = 0; + qll->count = 0; + + quantile_llist_index_reset(qll); + } + } + } + + RASTER_DEBUGF(5, "qll after: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", + qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); + RASTER_DEBUGF(5, "qll after: (head, tail) = (%p, %p)\n\n", qll->head, qll->tail); + } + + } + else { + RASTER_DEBUGF(5, "skipping value at (x, y) = (%d, %d)", x, y); + } + + z++; + } + } + + /* process quantiles */ + *rtn_count = *qlls_count / 2; + rtn = rtalloc(sizeof(struct rt_quantile_t) * *rtn_count); + if (NULL == rtn) return NULL; + + RASTER_DEBUGF(3, "returning %d quantiles", *rtn_count); + for (i = 0, k = 0; i < *qlls_count; i++) { + qll = &((*qlls)[i]); + + exists = 0; + for (x = 0; x < k; x++) { + if (FLT_EQ(qll->quantile, rtn[x].quantile)) { + exists = 1; + break; + } + } + if (exists) continue; + + RASTER_DEBUGF(5, "qll: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", + qll->algeq, qll->quantile, qll->count, qll->tau, qll->sum1, qll->sum2); + RASTER_DEBUGF(5, "qll: (head, tail) = (%p, %p)", qll->head, qll->tail); + + rtn[k].quantile = qll->quantile; + rtn[k].has_value = 0; + + /* check that qll->head and qll->tail have value */ + if (qll->head == NULL || qll->tail == NULL) + continue; + + /* AL-GEQ */ + if (qll->algeq) + qle = qll->head; + /* AM-GT */ + else + qle = qll->tail; + + exists = 0; + for (j = i + 1; j < *qlls_count; j++) { + if (FLT_EQ((*qlls)[j].quantile, qll->quantile)) { + + RASTER_DEBUGF(5, "qlls[%d]: (algeq, quantile, count, tau, sum1, sum2) = (%d, %f, %d, %d, %d, %d)", + j, (*qlls)[j].algeq, (*qlls)[j].quantile, (*qlls)[j].count, (*qlls)[j].tau, (*qlls)[j].sum1, (*qlls)[j].sum2); + RASTER_DEBUGF(5, "qlls[%d]: (head, tail) = (%p, %p)", j, (*qlls)[j].head, (*qlls)[j].tail); + + exists = 1; + break; + } + } + + /* weighted average for quantile */ + if (exists) { + if ((*qlls)[j].algeq) { + rtn[k].value = ((qle->value * qle->count) + ((*qlls)[j].head->value * (*qlls)[j].head->count)) / (qle->count + (*qlls)[j].head->count); + RASTER_DEBUGF(5, "qlls[%d].head: (value, count) = (%f, %d)", j, (*qlls)[j].head->value, (*qlls)[j].head->count); + } + else { + rtn[k].value = ((qle->value * qle->count) + ((*qlls)[j].tail->value * (*qlls)[j].tail->count)) / (qle->count + (*qlls)[j].tail->count); + RASTER_DEBUGF(5, "qlls[%d].tail: (value, count) = (%f, %d)", j, (*qlls)[j].tail->value, (*qlls)[j].tail->count); + } + } + /* straight value for quantile */ + else { + rtn[k].value = qle->value; + } + rtn[k].has_value = 1; + RASTER_DEBUGF(3, "(quantile, value) = (%f, %f)\n\n", rtn[k].quantile, rtn[k].value); + + k++; + } + + RASTER_DEBUG(3, "done"); + return rtn; +} + +/****************************************************************************** +* rt_band_get_value_count() +******************************************************************************/ + +/** + * Count the number of times provided value(s) occur in + * the band + * + * @param band : the band to query for minimum and maximum pixel values + * @param exclude_nodata_value : if non-zero, ignore nodata values + * @param search_values : array of values to count + * @param search_values_count : the number of search values + * @param roundto : the decimal place to round the values to + * @param rtn_total : the number of pixels examined in the band + * @param rtn_count : the number of value counts being returned + * + * @return the number of times the provide value(s) occur or NULL + */ +rt_valuecount +rt_band_get_value_count( + rt_band band, int exclude_nodata_value, + double *search_values, uint32_t search_values_count, double roundto, + uint32_t *rtn_total, uint32_t *rtn_count +) { + rt_valuecount vcnts = NULL; + rt_pixtype pixtype = PT_END; + uint8_t *data = NULL; + double nodata = 0; + + int scale = 0; + int doround = 0; + double tmpd = 0; + int i = 0; + + uint32_t x = 0; + uint32_t y = 0; + int rtn; + double pxlval; + int isnodata = 0; + double rpxlval; + uint32_t total = 0; + int vcnts_count = 0; + int new_valuecount = 0; + +#if POSTGIS_DEBUG_LEVEL > 0 + clock_t start, stop; + double elapsed = 0; +#endif + + RASTER_DEBUG(3, "starting"); +#if POSTGIS_DEBUG_LEVEL > 0 + start = clock(); +#endif + + assert(NULL != band); + assert(NULL != rtn_count); + + data = rt_band_get_data(band); + if (data == NULL) { + rterror("rt_band_get_summary_stats: Cannot get band data"); + return NULL; + } + + pixtype = band->pixtype; + + if (rt_band_get_hasnodata_flag(band)) { + rt_band_get_nodata(band, &nodata); + RASTER_DEBUGF(3, "hasnodata, nodataval = 1, %f", nodata); + } + else { + exclude_nodata_value = 0; + RASTER_DEBUG(3, "hasnodata, nodataval = 0, 0"); + } + + RASTER_DEBUGF(3, "exclude_nodata_value = %d", exclude_nodata_value); + + /* process roundto */ + if (roundto < 0 || FLT_EQ(roundto, 0.0)) { + roundto = 0; + scale = 0; + } + /* tenths, hundredths, thousandths, etc */ + else if (roundto < 1) { + switch (pixtype) { + /* integer band types don't have digits after the decimal place */ + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BSI: + case PT_8BUI: + case PT_16BSI: + case PT_16BUI: + case PT_32BSI: + case PT_32BUI: + roundto = 0; + break; + /* floating points, check the rounding */ + case PT_32BF: + case PT_64BF: + for (scale = 0; scale <= 20; scale++) { + tmpd = roundto * pow(10, scale); + if (FLT_EQ((tmpd - ((int) tmpd)), 0.0)) break; + } + break; + case PT_END: + break; + } + } + /* ones, tens, hundreds, etc */ + else { + for (scale = 0; scale >= -20; scale--) { + tmpd = roundto * pow(10, scale); + if (tmpd < 1 || FLT_EQ(tmpd, 1.0)) { + if (scale == 0) doround = 1; + break; + } + } + } + + if (scale != 0 || doround) + doround = 1; + else + doround = 0; + RASTER_DEBUGF(3, "scale = %d", scale); + RASTER_DEBUGF(3, "doround = %d", doround); + + /* process search_values */ + if (search_values_count > 0 && NULL != search_values) { + vcnts = (rt_valuecount) rtalloc(sizeof(struct rt_valuecount_t) * search_values_count); + if (NULL == vcnts) { + rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); + *rtn_count = 0; + return NULL; + } + + for (i = 0; i < search_values_count; i++) { + vcnts[i].count = 0; + vcnts[i].percent = 0; + if (!doround) + vcnts[i].value = search_values[i]; + else + vcnts[i].value = ROUND(search_values[i], scale); + } + vcnts_count = i; + } + else + search_values_count = 0; + RASTER_DEBUGF(3, "search_values_count = %d", search_values_count); + + /* entire band is nodata */ + if (rt_band_get_isnodata_flag(band) != FALSE) { + if (exclude_nodata_value) { + rtwarn("All pixels of band have the NODATA value"); + return NULL; + } + else { + if (search_values_count > 0) { + /* check for nodata match */ + for (i = 0; i < search_values_count; i++) { + if (!doround) + tmpd = nodata; + else + tmpd = ROUND(nodata, scale); + + if (FLT_NEQ(tmpd, vcnts[i].value)) + continue; + + vcnts[i].count = band->width * band->height; + if (NULL != rtn_total) *rtn_total = vcnts[i].count; + vcnts->percent = 1.0; + } + + *rtn_count = vcnts_count; + } + /* no defined search values */ + else { + vcnts = (rt_valuecount) rtalloc(sizeof(struct rt_valuecount_t)); + if (NULL == vcnts) { + rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); + *rtn_count = 0; + return NULL; + } + + vcnts->value = nodata; + vcnts->count = band->width * band->height; + if (NULL != rtn_total) *rtn_total = vcnts[i].count; + vcnts->percent = 1.0; + + *rtn_count = 1; + } + + return vcnts; + } + } + + for (x = 0; x < band->width; x++) { + for (y = 0; y < band->height; y++) { + rtn = rt_band_get_pixel(band, x, y, &pxlval, &isnodata); + + /* error getting value, continue */ + if (rtn != ES_NONE) + continue; + + if (!exclude_nodata_value || (exclude_nodata_value && !isnodata)) { + total++; + if (doround) { + rpxlval = ROUND(pxlval, scale); + } + else + rpxlval = pxlval; + RASTER_DEBUGF(5, "(pxlval, rpxlval) => (%0.6f, %0.6f)", pxlval, rpxlval); + + new_valuecount = 1; + /* search for match in existing valuecounts */ + for (i = 0; i < vcnts_count; i++) { + /* match found */ + if (FLT_EQ(vcnts[i].value, rpxlval)) { + vcnts[i].count++; + new_valuecount = 0; + RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[i].value, vcnts[i].count); + break; + } + } + + /* + don't add new valuecount either because + - no need for new one + - user-defined search values + */ + if (!new_valuecount || search_values_count > 0) continue; + + /* add new valuecount */ + vcnts = rtrealloc(vcnts, sizeof(struct rt_valuecount_t) * (vcnts_count + 1)); + if (NULL == vcnts) { + rterror("rt_band_get_count_of_values: Could not allocate memory for value counts"); + *rtn_count = 0; + return NULL; + } + + vcnts[vcnts_count].value = rpxlval; + vcnts[vcnts_count].count = 1; + vcnts[vcnts_count].percent = 0; + RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[vcnts_count].value, vcnts[vcnts_count].count); + vcnts_count++; + } + } + } + +#if POSTGIS_DEBUG_LEVEL > 0 + stop = clock(); + elapsed = ((double) (stop - start)) / CLOCKS_PER_SEC; + RASTER_DEBUGF(3, "elapsed time = %0.4f", elapsed); +#endif + + for (i = 0; i < vcnts_count; i++) { + vcnts[i].percent = (double) vcnts[i].count / total; + RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[i].value, vcnts[i].count); + } + + RASTER_DEBUG(3, "done"); + if (NULL != rtn_total) *rtn_total = total; + *rtn_count = vcnts_count; + return vcnts; +} diff --git a/raster/rt_core/rt_util.c b/raster/rt_core/rt_util.c new file mode 100644 index 000000000..a1c43ade2 --- /dev/null +++ b/raster/rt_core/rt_util.c @@ -0,0 +1,680 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +uint8_t +rt_util_clamp_to_1BB(double value) { + return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_1BBMAX); +} + +uint8_t +rt_util_clamp_to_2BUI(double value) { + return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_2BUIMAX); +} + +uint8_t +rt_util_clamp_to_4BUI(double value) { + return (uint8_t)fmin(fmax((value), 0), POSTGIS_RT_4BUIMAX); +} + +int8_t +rt_util_clamp_to_8BSI(double value) { + return (int8_t)fmin(fmax((value), SCHAR_MIN), SCHAR_MAX); +} + +uint8_t +rt_util_clamp_to_8BUI(double value) { + return (uint8_t)fmin(fmax((value), 0), UCHAR_MAX); +} + +int16_t +rt_util_clamp_to_16BSI(double value) { + return (int16_t)fmin(fmax((value), SHRT_MIN), SHRT_MAX); +} + +uint16_t +rt_util_clamp_to_16BUI(double value) { + return (uint16_t)fmin(fmax((value), 0), USHRT_MAX); +} + +int32_t +rt_util_clamp_to_32BSI(double value) { + return (int32_t)fmin(fmax((value), INT_MIN), INT_MAX); +} + +uint32_t +rt_util_clamp_to_32BUI(double value) { + return (uint32_t)fmin(fmax((value), 0), UINT_MAX); +} + +float +rt_util_clamp_to_32F(double value) { + return (float)fmin(fmax((value), -FLT_MAX), FLT_MAX); +} + +/** + * Convert cstring name to GDAL Resample Algorithm + * + * @param algname : cstring name to convert + * + * @return valid GDAL resampling algorithm + */ +GDALResampleAlg +rt_util_gdal_resample_alg(const char *algname) { + assert(algname != NULL && strlen(algname) > 0); + + if (strcmp(algname, "NEARESTNEIGHBOUR") == 0) + return GRA_NearestNeighbour; + else if (strcmp(algname, "NEARESTNEIGHBOR") == 0) + return GRA_NearestNeighbour; + else if (strcmp(algname, "BILINEAR") == 0) + return GRA_Bilinear; + else if (strcmp(algname, "CUBICSPLINE") == 0) + return GRA_CubicSpline; + else if (strcmp(algname, "CUBIC") == 0) + return GRA_Cubic; + else if (strcmp(algname, "LANCZOS") == 0) + return GRA_Lanczos; + + return GRA_NearestNeighbour; +} + +/** + * Convert rt_pixtype to GDALDataType + * + * @param pt : pixeltype to convert + * + * @return valid GDALDataType + */ +GDALDataType +rt_util_pixtype_to_gdal_datatype(rt_pixtype pt) { + switch (pt) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: + return GDT_Byte; + case PT_8BSI: + case PT_16BSI: + return GDT_Int16; + case PT_16BUI: + return GDT_UInt16; + case PT_32BSI: + return GDT_Int32; + case PT_32BUI: + return GDT_UInt32; + case PT_32BF: + return GDT_Float32; + case PT_64BF: + return GDT_Float64; + default: + return GDT_Unknown; + } + + return GDT_Unknown; +} + +/** + * Convert GDALDataType to rt_pixtype + * + * @param gdt : GDAL datatype to convert + * + * @return valid rt_pixtype + */ +rt_pixtype +rt_util_gdal_datatype_to_pixtype(GDALDataType gdt) { + switch (gdt) { + case GDT_Byte: + return PT_8BUI; + case GDT_UInt16: + return PT_16BUI; + case GDT_Int16: + return PT_16BSI; + case GDT_UInt32: + return PT_32BUI; + case GDT_Int32: + return PT_32BSI; + case GDT_Float32: + return PT_32BF; + case GDT_Float64: + return PT_64BF; + default: + return PT_END; + } + + return PT_END; +} + +/* + get GDAL runtime version information +*/ +const char* +rt_util_gdal_version(const char *request) { + if (NULL == request || !strlen(request)) + return GDALVersionInfo("RELEASE_NAME"); + else + return GDALVersionInfo(request); +} + +/* + computed extent type +*/ +rt_extenttype +rt_util_extent_type(const char *name) { + assert(name != NULL && strlen(name) > 0); + + if (strcmp(name, "UNION") == 0) + return ET_UNION; + else if (strcmp(name, "FIRST") == 0) + return ET_FIRST; + else if (strcmp(name, "SECOND") == 0) + return ET_SECOND; + else if (strcmp(name, "LAST") == 0) + return ET_LAST; + else if (strcmp(name, "CUSTOM") == 0) + return ET_CUSTOM; + else + return ET_INTERSECTION; +} + +/* + convert the spatial reference string from a GDAL recognized format to either WKT or Proj4 +*/ +char* +rt_util_gdal_convert_sr(const char *srs, int proj4) { + OGRSpatialReferenceH hsrs; + char *rtn = NULL; + + assert(srs != NULL); + + hsrs = OSRNewSpatialReference(NULL); + if (OSRSetFromUserInput(hsrs, srs) == OGRERR_NONE) { + if (proj4) + OSRExportToProj4(hsrs, &rtn); + else + OSRExportToWkt(hsrs, &rtn); + } + else { + rterror("rt_util_gdal_convert_sr: Could not process the provided srs: %s", srs); + return NULL; + } + + OSRDestroySpatialReference(hsrs); + if (rtn == NULL) { + rterror("rt_util_gdal_convert_sr: Could not process the provided srs: %s", srs); + return NULL; + } + + return rtn; +} + +/* + is the spatial reference string supported by GDAL +*/ +int +rt_util_gdal_supported_sr(const char *srs) { + OGRSpatialReferenceH hsrs; + OGRErr rtn = OGRERR_NONE; + + assert(srs != NULL); + + hsrs = OSRNewSpatialReference(NULL); + rtn = OSRSetFromUserInput(hsrs, srs); + OSRDestroySpatialReference(hsrs); + + if (rtn == OGRERR_NONE) + return 1; + else + return 0; +} + +/** + * Get auth name and code + * + * @param authname: authority organization of code. calling function + * is expected to free the memory allocated for value + * @param authcode: code assigned by authority organization. calling function + * is expected to free the memory allocated for value + * + * @return ES_NONE on success, ES_ERROR on error + */ +rt_errorstate +rt_util_gdal_sr_auth_info(GDALDatasetH hds, char **authname, char **authcode) { + const char *srs = NULL; + + assert(authname != NULL); + assert(authcode != NULL); + + *authname = NULL; + *authcode = NULL; + + srs = GDALGetProjectionRef(hds); + if (srs != NULL && srs[0] != '\0') { + OGRSpatialReferenceH hSRS = OSRNewSpatialReference(NULL); + + if (OSRSetFromUserInput(hSRS, srs) == OGRERR_NONE) { + const char* pszAuthorityName = OSRGetAuthorityName(hSRS, NULL); + const char* pszAuthorityCode = OSRGetAuthorityCode(hSRS, NULL); + + if (pszAuthorityName != NULL && pszAuthorityCode != NULL) { + *authname = rtalloc(sizeof(char) * (strlen(pszAuthorityName) + 1)); + *authcode = rtalloc(sizeof(char) * (strlen(pszAuthorityCode) + 1)); + + if (*authname == NULL || *authcode == NULL) { + rterror("rt_util_gdal_sr_auth_info: Could not allocate memory for auth name and code"); + if (*authname != NULL) rtdealloc(*authname); + if (*authcode != NULL) rtdealloc(*authcode); + OSRDestroySpatialReference(hSRS); + return ES_ERROR; + } + + strncpy(*authname, pszAuthorityName, strlen(pszAuthorityName) + 1); + strncpy(*authcode, pszAuthorityCode, strlen(pszAuthorityCode) + 1); + } + } + + OSRDestroySpatialReference(hSRS); + } + + return ES_NONE; +} + +/* + is GDAL configured correctly? +*/ +int rt_util_gdal_configured(void) { + + /* set of EPSG codes */ + if (!rt_util_gdal_supported_sr("EPSG:4326")) + return 0; + if (!rt_util_gdal_supported_sr("EPSG:4269")) + return 0; + if (!rt_util_gdal_supported_sr("EPSG:4267")) + return 0; + if (!rt_util_gdal_supported_sr("EPSG:3310")) + return 0; + if (!rt_util_gdal_supported_sr("EPSG:2163")) + return 0; + + return 1; +} + +/* + register all GDAL drivers +*/ +void +rt_util_gdal_register_all(void) { + static int registered = 0; + + if (registered) + return; + + GDALAllRegister(); + registered = 1; +} + +/* + is the driver registered? +*/ +int +rt_util_gdal_driver_registered(const char *drv) { + int count = GDALGetDriverCount(); + int i = 0; + GDALDriverH hdrv = NULL; + + if (drv == NULL || !strlen(drv) || count < 1) + return 0; + + for (i = 0; i < count; i++) { + hdrv = GDALGetDriver(i); + if (hdrv == NULL) continue; + + if (strcmp(drv, GDALGetDriverShortName(hdrv)) == 0) + return 1; + } + + return 0; +} + +void +rt_util_from_ogr_envelope( + OGREnvelope env, + rt_envelope *ext +) { + assert(ext != NULL); + + ext->MinX = env.MinX; + ext->MaxX = env.MaxX; + ext->MinY = env.MinY; + ext->MaxY = env.MaxY; + + ext->UpperLeftX = env.MinX; + ext->UpperLeftY = env.MaxY; +} + +void +rt_util_to_ogr_envelope( + rt_envelope ext, + OGREnvelope *env +) { + assert(env != NULL); + + env->MinX = ext.MinX; + env->MaxX = ext.MaxX; + env->MinY = ext.MinY; + env->MaxY = ext.MaxY; +} + +LWPOLY * +rt_util_envelope_to_lwpoly( + rt_envelope env +) { + LWPOLY *npoly = NULL; + POINTARRAY **rings = NULL; + POINTARRAY *pts = NULL; + POINT4D p4d; + + rings = (POINTARRAY **) rtalloc(sizeof (POINTARRAY*)); + if (!rings) { + rterror("rt_util_envelope_to_lwpoly: Out of memory building envelope's geometry"); + return NULL; + } + rings[0] = ptarray_construct(0, 0, 5); + if (!rings[0]) { + rterror("rt_util_envelope_to_lwpoly: Out of memory building envelope's geometry ring"); + return NULL; + } + + pts = rings[0]; + + /* Upper-left corner (first and last points) */ + p4d.x = env.MinX; + p4d.y = env.MaxY; + ptarray_set_point4d(pts, 0, &p4d); + ptarray_set_point4d(pts, 4, &p4d); + + /* Upper-right corner (we go clockwise) */ + p4d.x = env.MaxX; + p4d.y = env.MaxY; + ptarray_set_point4d(pts, 1, &p4d); + + /* Lower-right corner */ + p4d.x = env.MaxX; + p4d.y = env.MinY; + ptarray_set_point4d(pts, 2, &p4d); + + /* Lower-left corner */ + p4d.x = env.MinX; + p4d.y = env.MinY; + ptarray_set_point4d(pts, 3, &p4d); + + npoly = lwpoly_construct(SRID_UNKNOWN, 0, 1, rings); + if (npoly == NULL) { + rterror("rt_util_envelope_to_lwpoly: Could not build envelope's geometry"); + return NULL; + } + + return npoly; +} + +int +rt_util_same_geotransform_matrix(double *gt1, double *gt2) { + int k = 0; + + if (gt1 == NULL || gt2 == NULL) + return FALSE; + + for (k = 0; k < 6; k++) { + if (FLT_NEQ(gt1[k], gt2[k])) + return FALSE; + } + + return TRUE; +} + +/* coordinates in RGB and HSV are floating point values between 0 and 1 */ +rt_errorstate +rt_util_rgb_to_hsv(double rgb[3], double hsv[3]) { + int i; + + double minc; + double maxc; + + double h = 0.; + double s = 0.; + double v = 0.; + + minc = rgb[0]; + maxc = rgb[0]; + + /* get min and max values from RGB */ + for (i = 1; i < 3; i++) { + if (rgb[i] > maxc) + maxc = rgb[i]; + if (rgb[i] < minc) + minc = rgb[i]; + } + v = maxc; + + if (maxc != minc) { + double diff = 0.; + double rc = 0.; + double gc = 0.; + double bc = 0.; + double junk = 0.; + + diff = maxc - minc; + s = diff / maxc; + rc = (maxc - rgb[0]) / diff; + gc = (maxc - rgb[1]) / diff; + bc = (maxc - rgb[2]) / diff; + + if (DBL_EQ(rgb[0], maxc)) + h = bc - gc; + else if (DBL_EQ(rgb[1], maxc)) + h = 2.0 + rc - bc; + else + h = 4.0 + gc - rc; + + h = modf((h / 6.0), &junk); + } + + hsv[0] = h; + hsv[1] = s; + hsv[2] = v; + + return ES_NONE; +} + +/* coordinates in RGB and HSV are floating point values between 0 and 1 */ +rt_errorstate +rt_util_hsv_to_rgb(double hsv[3], double rgb[3]) { + double r = 0; + double g = 0; + double b = 0; + double v = hsv[2]; + + if (DBL_EQ(hsv[1], 0.)) + r = g = b = v; + else { + double i; + double f; + double p; + double q; + double t; + + int a; + + i = floor(hsv[0] * 6.); + f = (hsv[0] * 6.0) - i; + p = v * (1. - hsv[1]); + q = v * (1. - hsv[1] * f); + t = v * (1. - hsv[1] * (1. - f)); + + a = (int) i; + switch (a) { + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + case 5: + r = v; + g = p; + b = q; + break; + case 0: + case 6: + default: + r = v; + g = t; + b = p; + break; + } + } + + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + + return ES_NONE; +} + +int +rt_util_dbl_trunc_warning( + double initialvalue, + int32_t checkvalint, uint32_t checkvaluint, + float checkvalfloat, double checkvaldouble, + rt_pixtype pixtype +) { + int result = 0; + + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BSI: + case PT_8BUI: + case PT_16BSI: + case PT_16BUI: + case PT_32BSI: { + if (fabs(checkvalint - initialvalue) >= 1) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got clamped from %f to %d", + rt_pixtype_name(pixtype), + initialvalue, checkvalint + ); +#endif + result = 1; + } + else if (FLT_NEQ(checkvalint, initialvalue)) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got truncated from %f to %d", + rt_pixtype_name(pixtype), + initialvalue, checkvalint + ); +#endif + result = 1; + } + break; + } + case PT_32BUI: { + if (fabs(checkvaluint - initialvalue) >= 1) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got clamped from %f to %u", + rt_pixtype_name(pixtype), + initialvalue, checkvaluint + ); +#endif + result = 1; + } + else if (FLT_NEQ(checkvaluint, initialvalue)) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got truncated from %f to %u", + rt_pixtype_name(pixtype), + initialvalue, checkvaluint + ); +#endif + result = 1; + } + break; + } + case PT_32BF: { + /* + For float, because the initial value is a double, + there is very often a difference between the desired value and the obtained one + */ + if (FLT_NEQ(checkvalfloat, initialvalue)) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got converted from %f to %f", + rt_pixtype_name(pixtype), + initialvalue, checkvalfloat + ); +#endif + result = 1; + } + break; + } + case PT_64BF: { + if (FLT_NEQ(checkvaldouble, initialvalue)) { +#if POSTGIS_RASTER_WARN_ON_TRUNCATION > 0 + rtwarn("Value set for %s band got converted from %f to %f", + rt_pixtype_name(pixtype), + initialvalue, checkvaldouble + ); +#endif + result = 1; + } + break; + } + case PT_END: + break; + } + + return result; +} + diff --git a/raster/rt_core/rt_warp.c b/raster/rt_core/rt_warp.c new file mode 100644 index 000000000..f583c0d1f --- /dev/null +++ b/raster/rt_core/rt_warp.c @@ -0,0 +1,974 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" + +/****************************************************************************** +* rt_raster_gdal_warp() +******************************************************************************/ + +typedef struct _rti_warp_arg_t* _rti_warp_arg; +struct _rti_warp_arg_t { + + struct { + GDALDriverH drv; + GDALDatasetH ds; + char *srs; + } src, dst; + + GDALWarpOptions *wopts; + + struct { + struct { + char **item; + int len; + } option; + + struct { + void *transform; + void *imgproj; + void *approx; + } arg; + + GDALTransformerFunc func; + } transform; + +}; + +static _rti_warp_arg +_rti_warp_arg_init() { + _rti_warp_arg arg = NULL; + + arg = rtalloc(sizeof(struct _rti_warp_arg_t)); + if (arg == NULL) { + rterror("_rti_warp_arg_init: Could not allocate memory for _rti_warp_arg"); + return NULL; + } + + arg->src.drv = NULL; + arg->src.ds = NULL; + arg->src.srs = NULL; + + arg->dst.drv = NULL; + arg->dst.ds = NULL; + arg->dst.srs = NULL; + + arg->wopts = NULL; + + arg->transform.option.item = NULL; + arg->transform.option.len = 0; + + arg->transform.arg.transform = NULL; + arg->transform.arg.imgproj = NULL; + arg->transform.arg.approx = NULL; + + arg->transform.func = NULL; + + return arg; +} + +static void +_rti_warp_arg_destroy(_rti_warp_arg arg) { + int i = 0; + + if (arg->dst.ds != NULL) + GDALClose(arg->dst.ds); + if (arg->dst.srs != NULL) + CPLFree(arg->dst.srs); + + if (arg->src.ds != NULL) + GDALClose(arg->src.ds); + if (arg->src.srs != NULL) + CPLFree(arg->src.srs); + + if (arg->transform.func == GDALApproxTransform) { + if (arg->transform.arg.imgproj != NULL) + GDALDestroyGenImgProjTransformer(arg->transform.arg.imgproj); + } + + if (arg->wopts != NULL) + GDALDestroyWarpOptions(arg->wopts); + + if (arg->transform.option.len > 0 && arg->transform.option.item != NULL) { + for (i = 0; i < arg->transform.option.len; i++) { + if (arg->transform.option.item[i] != NULL) + rtdealloc(arg->transform.option.item[i]); + } + rtdealloc(arg->transform.option.item); + } + + rtdealloc(arg); + arg = NULL; +} + +/** + * Return a warped raster using GDAL Warp API + * + * @param raster : raster to transform + * @param src_srs : the raster's coordinate system in OGC WKT + * @param dst_srs : the warped raster's coordinate system in OGC WKT + * @param scale_x : the x size of pixels of the warped raster's pixels in + * units of dst_srs + * @param scale_y : the y size of pixels of the warped raster's pixels in + * units of dst_srs + * @param width : the number of columns of the warped raster. note that + * width/height CANNOT be used with scale_x/scale_y + * @param height : the number of rows of the warped raster. note that + * width/height CANNOT be used with scale_x/scale_y + * @param ul_xw : the X value of upper-left corner of the warped raster in + * units of dst_srs + * @param ul_yw : the Y value of upper-left corner of the warped raster in + * units of dst_srs + * @param grid_xw : the X value of point on a grid to align warped raster + * to in units of dst_srs + * @param grid_yw : the Y value of point on a grid to align warped raster + * to in units of dst_srs + * @param skew_x : the X skew of the warped raster in units of dst_srs + * @param skew_y : the Y skew of the warped raster in units of dst_srs + * @param resample_alg : the resampling algorithm + * @param max_err : maximum error measured in input pixels permitted + * (0.0 for exact calculations) + * + * @return the warped raster or NULL + */ +rt_raster rt_raster_gdal_warp( + rt_raster raster, + const char *src_srs, const char *dst_srs, + double *scale_x, double *scale_y, + int *width, int *height, + double *ul_xw, double *ul_yw, + double *grid_xw, double *grid_yw, + double *skew_x, double *skew_y, + GDALResampleAlg resample_alg, double max_err +) { + CPLErr cplerr; + char *dst_options[] = {"SUBCLASS=VRTWarpedDataset", NULL}; + _rti_warp_arg arg = NULL; + + int hasnodata = 0; + + GDALRasterBandH band; + rt_band rtband = NULL; + rt_pixtype pt = PT_END; + GDALDataType gdal_pt = GDT_Unknown; + double nodata = 0.0; + + double _gt[6] = {0}; + double dst_extent[4]; + rt_envelope extent; + + int _dim[2] = {0}; + double _skew[2] = {0}; + double _scale[2] = {0}; + int ul_user = 0; + + rt_raster rast = NULL; + int i = 0; + int numBands = 0; + + int subgt = 0; + + RASTER_DEBUG(3, "starting"); + + assert(NULL != raster); + + /* internal variables */ + arg = _rti_warp_arg_init(); + if (arg == NULL) { + rterror("rt_raster_gdal_warp: Could not initialize internal variables"); + return NULL; + } + + /* + max_err must be gte zero + + the value 0.125 is the default used in gdalwarp.cpp on line 283 + */ + if (max_err < 0.) max_err = 0.125; + RASTER_DEBUGF(4, "max_err = %f", max_err); + + /* handle srs */ + if (src_srs != NULL) { + /* reprojection taking place */ + if (dst_srs != NULL && strcmp(src_srs, dst_srs) != 0) { + RASTER_DEBUG(4, "Warp operation does include a reprojection"); + arg->src.srs = rt_util_gdal_convert_sr(src_srs, 0); + arg->dst.srs = rt_util_gdal_convert_sr(dst_srs, 0); + + if (arg->src.srs == NULL || arg->dst.srs == NULL) { + rterror("rt_raster_gdal_warp: Could not convert srs values to GDAL accepted format"); + _rti_warp_arg_destroy(arg); + return NULL; + } + } + /* no reprojection, a stub just for clarity */ + else { + RASTER_DEBUG(4, "Warp operation does NOT include reprojection"); + } + } + else if (dst_srs != NULL) { + /* dst_srs provided but not src_srs */ + rterror("rt_raster_gdal_warp: SRS required for input raster if SRS provided for warped raster"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* load raster into a GDAL MEM dataset */ + arg->src.ds = rt_raster_to_gdal_mem(raster, arg->src.srs, NULL, NULL, 0, &(arg->src.drv)); + if (NULL == arg->src.ds) { + rterror("rt_raster_gdal_warp: Could not convert raster to GDAL MEM format"); + _rti_warp_arg_destroy(arg); + return NULL; + } + RASTER_DEBUG(3, "raster loaded into GDAL MEM dataset"); + + /* special case when src_srs and dst_srs is NULL and raster's geotransform matrix is default */ + if (src_srs == NULL && dst_srs == NULL && rt_raster_get_srid(raster) == SRID_UNKNOWN) { + double gt[6]; + +#if POSTGIS_DEBUG_LEVEL > 3 + GDALGetGeoTransform(arg->src.ds, gt); + RASTER_DEBUGF(3, "GDAL MEM geotransform: %f, %f, %f, %f, %f, %f", + gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); +#endif + + /* default geotransform */ + rt_raster_get_geotransform_matrix(raster, gt); + RASTER_DEBUGF(3, "raster geotransform: %f, %f, %f, %f, %f, %f", + gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); + + if ( + FLT_EQ(gt[0], 0) && + FLT_EQ(gt[1], 1) && + FLT_EQ(gt[2], 0) && + FLT_EQ(gt[3], 0) && + FLT_EQ(gt[4], 0) && + FLT_EQ(gt[5], -1) + ) { + double ngt[6] = {0, 10, 0, 0, 0, -10}; + + rtinfo("Raster has default geotransform. Adjusting metadata for use of GDAL Warp API"); + + GDALSetGeoTransform(arg->src.ds, ngt); + GDALFlushCache(arg->src.ds); + + subgt = 1; + +#if POSTGIS_DEBUG_LEVEL > 3 + GDALGetGeoTransform(arg->src.ds, gt); + RASTER_DEBUGF(3, "GDAL MEM geotransform: %f, %f, %f, %f, %f, %f", + gt[0], gt[1], gt[2], gt[3], gt[4], gt[5]); +#endif + } + } + + /* set transform options */ + if (arg->src.srs != NULL || arg->dst.srs != NULL) { + arg->transform.option.len = 2; + arg->transform.option.item = rtalloc(sizeof(char *) * (arg->transform.option.len + 1)); + if (NULL == arg->transform.option.item) { + rterror("rt_raster_gdal_warp: Could not allocation memory for transform options"); + _rti_warp_arg_destroy(arg); + return NULL; + } + memset(arg->transform.option.item, 0, sizeof(char *) * (arg->transform.option.len + 1)); + + for (i = 0; i < arg->transform.option.len; i++) { + switch (i) { + case 1: + if (arg->dst.srs != NULL) + arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("DST_SRS=") + strlen(arg->dst.srs) + 1)); + else + arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("DST_SRS=") + 1)); + break; + case 0: + if (arg->src.srs != NULL) + arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("SRC_SRS=") + strlen(arg->src.srs) + 1)); + else + arg->transform.option.item[i] = (char *) rtalloc(sizeof(char) * (strlen("SRC_SRS=") + 1)); + break; + } + if (NULL == arg->transform.option.item[i]) { + rterror("rt_raster_gdal_warp: Could not allocation memory for transform options"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + switch (i) { + case 1: + if (arg->dst.srs != NULL) { + snprintf( + arg->transform.option.item[i], + sizeof(char) * (strlen("DST_SRS=") + strlen(arg->dst.srs) + 1), + "DST_SRS=%s", + arg->dst.srs + ); + } + else + sprintf(arg->transform.option.item[i], "%s", "DST_SRS="); + break; + case 0: + if (arg->src.srs != NULL) { + snprintf( + arg->transform.option.item[i], + sizeof(char) * (strlen("SRC_SRS=") + strlen(arg->src.srs) + 1), + "SRC_SRS=%s", + arg->src.srs + ); + } + else + sprintf(arg->transform.option.item[i], "%s", "SRC_SRS="); + break; + } + RASTER_DEBUGF(4, "arg->transform.option.item[%d] = %s", i, arg->transform.option.item[i]); + } + } + else + arg->transform.option.len = 0; + + /* transformation object for building dst dataset */ + arg->transform.arg.transform = GDALCreateGenImgProjTransformer2(arg->src.ds, NULL, arg->transform.option.item); + if (NULL == arg->transform.arg.transform) { + rterror("rt_raster_gdal_warp: Could not create GDAL transformation object for output dataset creation"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* get approximate output georeferenced bounds and resolution */ + cplerr = GDALSuggestedWarpOutput2( + arg->src.ds, GDALGenImgProjTransform, + arg->transform.arg.transform, _gt, &(_dim[0]), &(_dim[1]), dst_extent, 0); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_warp: Could not get GDAL suggested warp output for output dataset creation"); + _rti_warp_arg_destroy(arg); + return NULL; + } + GDALDestroyGenImgProjTransformer(arg->transform.arg.transform); + arg->transform.arg.transform = NULL; + + /* + don't use suggested dimensions as use of suggested scales + on suggested extent will result in suggested dimensions + */ + _dim[0] = 0; + _dim[1] = 0; + + RASTER_DEBUGF(3, "Suggested geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); + + /* store extent in easier-to-use object */ + extent.MinX = dst_extent[0]; + extent.MinY = dst_extent[1]; + extent.MaxX = dst_extent[2]; + extent.MaxY = dst_extent[3]; + + extent.UpperLeftX = dst_extent[0]; + extent.UpperLeftY = dst_extent[3]; + + RASTER_DEBUGF(3, "Suggested extent: %f, %f, %f, %f", + extent.MinX, extent.MinY, extent.MaxX, extent.MaxY); + + /* scale and width/height are mutually exclusive */ + if ( + ((NULL != scale_x) || (NULL != scale_y)) && + ((NULL != width) || (NULL != height)) + ) { + rterror("rt_raster_gdal_warp: Scale X/Y and width/height are mutually exclusive. Only provide one"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* user-defined width */ + if (NULL != width) { + _dim[0] = abs(*width); + _scale[0] = fabs((extent.MaxX - extent.MinX) / ((double) _dim[0])); + } + /* user-defined height */ + if (NULL != height) { + _dim[1] = abs(*height); + _scale[1] = fabs((extent.MaxY - extent.MinY) / ((double) _dim[1])); + } + + /* user-defined scale */ + if ( + ((NULL != scale_x) && (FLT_NEQ(*scale_x, 0.0))) && + ((NULL != scale_y) && (FLT_NEQ(*scale_y, 0.0))) + ) { + _scale[0] = fabs(*scale_x); + _scale[1] = fabs(*scale_y); + + /* special override */ + if (subgt) { + _scale[0] *= 10; + _scale[1] *= 10; + } + } + else if ( + ((NULL != scale_x) && (NULL == scale_y)) || + ((NULL == scale_x) && (NULL != scale_y)) + ) { + rterror("rt_raster_gdal_warp: Both X and Y scale values must be provided for scale"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* scale not defined, use suggested */ + if (FLT_EQ(_scale[0], 0) && FLT_EQ(_scale[1], 0)) { + _scale[0] = fabs(_gt[1]); + _scale[1] = fabs(_gt[5]); + } + + RASTER_DEBUGF(4, "Using scale: %f x %f", _scale[0], -1 * _scale[1]); + + /* user-defined skew */ + if (NULL != skew_x) { + _skew[0] = *skew_x; + + /* + negative scale-x affects skew + for now, force skew to be in left-right, top-down orientation + */ + if ( + NULL != scale_x && + *scale_x < 0. + ) { + _skew[0] *= -1; + } + } + if (NULL != skew_y) { + _skew[1] = *skew_y; + + /* + positive scale-y affects skew + for now, force skew to be in left-right, top-down orientation + */ + if ( + NULL != scale_y && + *scale_y > 0. + ) { + _skew[1] *= -1; + } + } + + RASTER_DEBUGF(4, "Using skew: %f x %f", _skew[0], _skew[1]); + + /* reprocess extent if skewed */ + if ( + FLT_NEQ(_skew[0], 0) || + FLT_NEQ(_skew[1], 0) + ) { + rt_raster skewedrast; + + RASTER_DEBUG(3, "Computing skewed extent's envelope"); + + skewedrast = rt_raster_compute_skewed_raster( + extent, + _skew, + _scale, + 0.01 + ); + if (skewedrast == NULL) { + rterror("rt_raster_gdal_warp: Could not compute skewed raster"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + if (_dim[0] == 0) + _dim[0] = skewedrast->width; + if (_dim[1] == 0) + _dim[1] = skewedrast->height; + + extent.UpperLeftX = skewedrast->ipX; + extent.UpperLeftY = skewedrast->ipY; + + rt_raster_destroy(skewedrast); + } + + /* dimensions not defined, compute */ + if (!_dim[0]) + _dim[0] = (int) fmax((fabs(extent.MaxX - extent.MinX) + (_scale[0] / 2.)) / _scale[0], 1); + if (!_dim[1]) + _dim[1] = (int) fmax((fabs(extent.MaxY - extent.MinY) + (_scale[1] / 2.)) / _scale[1], 1); + + /* temporary raster */ + rast = rt_raster_new(_dim[0], _dim[1]); + if (rast == NULL) { + rterror("rt_raster_gdal_warp: Out of memory allocating temporary raster"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* set raster's spatial attributes */ + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + rt_raster_set_scale(rast, _scale[0], -1 * _scale[1]); + rt_raster_set_skews(rast, _skew[0], _skew[1]); + + rt_raster_get_geotransform_matrix(rast, _gt); + RASTER_DEBUGF(3, "Temp raster's geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); + RASTER_DEBUGF(3, "Temp raster's dimensions (width x height): %d x %d", + _dim[0], _dim[1]); + + /* user-defined upper-left corner */ + if ( + NULL != ul_xw && + NULL != ul_yw + ) { + ul_user = 1; + + RASTER_DEBUGF(4, "Using user-specified upper-left corner: %f, %f", *ul_xw, *ul_yw); + + /* set upper-left corner */ + rt_raster_set_offsets(rast, *ul_xw, *ul_yw); + extent.UpperLeftX = *ul_xw; + extent.UpperLeftY = *ul_yw; + } + else if ( + ((NULL != ul_xw) && (NULL == ul_yw)) || + ((NULL == ul_xw) && (NULL != ul_yw)) + ) { + rterror("rt_raster_gdal_warp: Both X and Y upper-left corner values must be provided"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* alignment only considered if upper-left corner not provided */ + if ( + !ul_user && ( + (NULL != grid_xw) || (NULL != grid_yw) + ) + ) { + + if ( + ((NULL != grid_xw) && (NULL == grid_yw)) || + ((NULL == grid_xw) && (NULL != grid_yw)) + ) { + rterror("rt_raster_gdal_warp: Both X and Y alignment values must be provided"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + RASTER_DEBUGF(4, "Aligning extent to user-specified grid: %f, %f", *grid_xw, *grid_yw); + + do { + double _r[2] = {0}; + double _w[2] = {0}; + + /* raster is already aligned */ + if (FLT_EQ(*grid_xw, extent.UpperLeftX) && FLT_EQ(*grid_yw, extent.UpperLeftY)) { + RASTER_DEBUG(3, "Skipping raster alignment as it is already aligned to grid"); + break; + } + + extent.UpperLeftX = rast->ipX; + extent.UpperLeftY = rast->ipY; + rt_raster_set_offsets(rast, *grid_xw, *grid_yw); + + /* process upper-left corner */ + if (rt_raster_geopoint_to_cell( + rast, + extent.UpperLeftX, extent.UpperLeftY, + &(_r[0]), &(_r[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute raster pixel for spatial coordinates"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + if (rt_raster_cell_to_geopoint( + rast, + _r[0], _r[1], + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* shift occurred */ + if (FLT_NEQ(_w[0], extent.UpperLeftX)) { + if (NULL == width) + rast->width++; + else if (NULL == scale_x) { + double _c[2] = {0}; + + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + + /* get upper-right corner */ + if (rt_raster_cell_to_geopoint( + rast, + rast->width, 0, + &(_c[0]), &(_c[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + rast->scaleX = fabs((_c[0] - _w[0]) / ((double) rast->width)); + } + } + if (FLT_NEQ(_w[1], extent.UpperLeftY)) { + if (NULL == height) + rast->height++; + else if (NULL == scale_y) { + double _c[2] = {0}; + + rt_raster_set_offsets(rast, extent.UpperLeftX, extent.UpperLeftY); + + /* get upper-right corner */ + if (rt_raster_cell_to_geopoint( + rast, + 0, rast->height, + &(_c[0]), &(_c[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); + + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + rast->scaleY = -1 * fabs((_c[1] - _w[1]) / ((double) rast->height)); + } + } + + rt_raster_set_offsets(rast, _w[0], _w[1]); + RASTER_DEBUGF(4, "aligned offsets: %f x %f", _w[0], _w[1]); + } + while (0); + } + + /* + after this point, rt_envelope extent is no longer used + */ + + /* get key attributes from rast */ + _dim[0] = rast->width; + _dim[1] = rast->height; + rt_raster_get_geotransform_matrix(rast, _gt); + + /* scale-x is negative or scale-y is positive */ + if (( + (NULL != scale_x) && (*scale_x < 0.) + ) || ( + (NULL != scale_y) && (*scale_y > 0) + )) { + double _w[2] = {0}; + + /* negative scale-x */ + if ( + (NULL != scale_x) && + (*scale_x < 0.) + ) { + if (rt_raster_cell_to_geopoint( + rast, + rast->width, 0, + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + _gt[0] = _w[0]; + _gt[1] = *scale_x; + + /* check for skew */ + if (NULL != skew_x && FLT_NEQ(*skew_x, 0)) + _gt[2] = *skew_x; + } + /* positive scale-y */ + if ( + (NULL != scale_y) && + (*scale_y > 0) + ) { + if (rt_raster_cell_to_geopoint( + rast, + 0, rast->height, + &(_w[0]), &(_w[1]), + NULL + ) != ES_NONE) { + rterror("rt_raster_gdal_warp: Could not compute spatial coordinates for raster pixel"); + rt_raster_destroy(rast); + _rti_warp_arg_destroy(arg); + return NULL; + } + + _gt[3] = _w[1]; + _gt[5] = *scale_y; + + /* check for skew */ + if (NULL != skew_y && FLT_NEQ(*skew_y, 0)) + _gt[4] = *skew_y; + } + } + + rt_raster_destroy(rast); + rast = NULL; + + RASTER_DEBUGF(3, "Applied geotransform: %f, %f, %f, %f, %f, %f", + _gt[0], _gt[1], _gt[2], _gt[3], _gt[4], _gt[5]); + RASTER_DEBUGF(3, "Raster dimensions (width x height): %d x %d", + _dim[0], _dim[1]); + + if (FLT_EQ(_dim[0], 0) || FLT_EQ(_dim[1], 0)) { + rterror("rt_raster_gdal_warp: The width (%f) or height (%f) of the warped raster is zero", _dim[0], _dim[1]); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* load VRT driver */ + if (!rt_util_gdal_driver_registered("VRT")) + GDALRegister_VRT(); + arg->dst.drv = GDALGetDriverByName("VRT"); + if (NULL == arg->dst.drv) { + rterror("rt_raster_gdal_warp: Could not load the output GDAL VRT driver"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* create dst dataset */ + arg->dst.ds = GDALCreate(arg->dst.drv, "", _dim[0], _dim[1], 0, GDT_Byte, dst_options); + if (NULL == arg->dst.ds) { + rterror("rt_raster_gdal_warp: Could not create GDAL VRT dataset"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* set dst srs */ + if (arg->dst.srs != NULL) { + cplerr = GDALSetProjection(arg->dst.ds, arg->dst.srs); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_warp: Could not set projection"); + _rti_warp_arg_destroy(arg); + return NULL; + } + RASTER_DEBUGF(3, "Applied SRS: %s", GDALGetProjectionRef(arg->dst.ds)); + } + + /* set dst geotransform */ + cplerr = GDALSetGeoTransform(arg->dst.ds, _gt); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_warp: Could not set geotransform"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* add bands to dst dataset */ + numBands = rt_raster_get_num_bands(raster); + for (i = 0; i < numBands; i++) { + rtband = rt_raster_get_band(raster, i); + if (NULL == rtband) { + rterror("rt_raster_gdal_warp: Could not get band %d for adding to VRT dataset", i); + _rti_warp_arg_destroy(arg); + return NULL; + } + + pt = rt_band_get_pixtype(rtband); + gdal_pt = rt_util_pixtype_to_gdal_datatype(pt); + if (gdal_pt == GDT_Unknown) + rtwarn("rt_raster_gdal_warp: Unknown pixel type for band %d", i); + + cplerr = GDALAddBand(arg->dst.ds, gdal_pt, NULL); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_warp: Could not add band to VRT dataset"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* get band to write data to */ + band = NULL; + band = GDALGetRasterBand(arg->dst.ds, i + 1); + if (NULL == band) { + rterror("rt_raster_gdal_warp: Could not get GDAL band for additional processing"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* set nodata */ + if (rt_band_get_hasnodata_flag(rtband) != FALSE) { + hasnodata = 1; + rt_band_get_nodata(rtband, &nodata); + if (GDALSetRasterNoDataValue(band, nodata) != CE_None) + rtwarn("rt_raster_gdal_warp: Could not set nodata value for band %d", i); + RASTER_DEBUGF(3, "nodata value set to %f", GDALGetRasterNoDataValue(band, NULL)); + } + } + + /* create transformation object */ + arg->transform.arg.transform = arg->transform.arg.imgproj = GDALCreateGenImgProjTransformer2( + arg->src.ds, arg->dst.ds, + arg->transform.option.item + ); + if (NULL == arg->transform.arg.transform) { + rterror("rt_raster_gdal_warp: Could not create GDAL transformation object"); + _rti_warp_arg_destroy(arg); + return NULL; + } + arg->transform.func = GDALGenImgProjTransform; + + /* use approximate transformation object */ + if (max_err > 0.0) { + arg->transform.arg.transform = arg->transform.arg.approx = GDALCreateApproxTransformer( + GDALGenImgProjTransform, + arg->transform.arg.imgproj, max_err + ); + if (NULL == arg->transform.arg.transform) { + rterror("rt_raster_gdal_warp: Could not create GDAL approximate transformation object"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + arg->transform.func = GDALApproxTransform; + } + + /* warp options */ + arg->wopts = GDALCreateWarpOptions(); + if (NULL == arg->wopts) { + rterror("rt_raster_gdal_warp: Could not create GDAL warp options object"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + /* set options */ + arg->wopts->eResampleAlg = resample_alg; + arg->wopts->hSrcDS = arg->src.ds; + arg->wopts->hDstDS = arg->dst.ds; + arg->wopts->pfnTransformer = arg->transform.func; + arg->wopts->pTransformerArg = arg->transform.arg.transform; + arg->wopts->papszWarpOptions = (char **) CPLMalloc(sizeof(char *) * 2); + arg->wopts->papszWarpOptions[0] = (char *) CPLMalloc(sizeof(char) * (strlen("INIT_DEST=NO_DATA") + 1)); + strcpy(arg->wopts->papszWarpOptions[0], "INIT_DEST=NO_DATA"); + arg->wopts->papszWarpOptions[1] = NULL; + + /* set band mapping */ + arg->wopts->nBandCount = numBands; + arg->wopts->panSrcBands = (int *) CPLMalloc(sizeof(int) * arg->wopts->nBandCount); + arg->wopts->panDstBands = (int *) CPLMalloc(sizeof(int) * arg->wopts->nBandCount); + for (i = 0; i < arg->wopts->nBandCount; i++) + arg->wopts->panDstBands[i] = arg->wopts->panSrcBands[i] = i + 1; + + /* set nodata mapping */ + if (hasnodata) { + RASTER_DEBUG(3, "Setting nodata mapping"); + arg->wopts->padfSrcNoDataReal = (double *) CPLMalloc(numBands * sizeof(double)); + arg->wopts->padfDstNoDataReal = (double *) CPLMalloc(numBands * sizeof(double)); + arg->wopts->padfSrcNoDataImag = (double *) CPLMalloc(numBands * sizeof(double)); + arg->wopts->padfDstNoDataImag = (double *) CPLMalloc(numBands * sizeof(double)); + if ( + NULL == arg->wopts->padfSrcNoDataReal || + NULL == arg->wopts->padfDstNoDataReal || + NULL == arg->wopts->padfSrcNoDataImag || + NULL == arg->wopts->padfDstNoDataImag + ) { + rterror("rt_raster_gdal_warp: Out of memory allocating nodata mapping"); + _rti_warp_arg_destroy(arg); + return NULL; + } + for (i = 0; i < numBands; i++) { + band = rt_raster_get_band(raster, i); + if (!band) { + rterror("rt_raster_gdal_warp: Could not process bands for nodata values"); + _rti_warp_arg_destroy(arg); + return NULL; + } + + if (!rt_band_get_hasnodata_flag(band)) { + /* + based on line 1004 of gdalwarp.cpp + the problem is that there is a chance that this number is a legitimate value + */ + arg->wopts->padfSrcNoDataReal[i] = -123456.789; + } + else { + rt_band_get_nodata(band, &(arg->wopts->padfSrcNoDataReal[i])); + } + + arg->wopts->padfDstNoDataReal[i] = arg->wopts->padfSrcNoDataReal[i]; + arg->wopts->padfDstNoDataImag[i] = arg->wopts->padfSrcNoDataImag[i] = 0.0; + RASTER_DEBUGF(4, "Mapped nodata value for band %d: %f (%f) => %f (%f)", + i, + arg->wopts->padfSrcNoDataReal[i], arg->wopts->padfSrcNoDataImag[i], + arg->wopts->padfDstNoDataReal[i], arg->wopts->padfDstNoDataImag[i] + ); + } + } + + /* warp raster */ + RASTER_DEBUG(3, "Warping raster"); + cplerr = GDALInitializeWarpedVRT(arg->dst.ds, arg->wopts); + if (cplerr != CE_None) { + rterror("rt_raster_gdal_warp: Could not warp raster"); + _rti_warp_arg_destroy(arg); + return NULL; + } + /* + GDALSetDescription(arg->dst.ds, "/tmp/warped.vrt"); + */ + GDALFlushCache(arg->dst.ds); + RASTER_DEBUG(3, "Raster warped"); + + /* convert gdal dataset to raster */ + RASTER_DEBUG(3, "Converting GDAL dataset to raster"); + rast = rt_raster_from_gdal_dataset(arg->dst.ds); + + _rti_warp_arg_destroy(arg); + + if (NULL == rast) { + rterror("rt_raster_gdal_warp: Could not warp raster"); + return NULL; + } + + /* substitute geotransform matrix, reset back to default */ + if (subgt) { + double gt[6] = {0, 1, 0, 0, 0, -1}; + + rt_raster_set_geotransform_matrix(rast, gt); + } + + RASTER_DEBUG(3, "done"); + + return rast; +} + diff --git a/raster/rt_core/rt_wkb.c b/raster/rt_core/rt_wkb.c new file mode 100644 index 000000000..cf77c6da7 --- /dev/null +++ b/raster/rt_core/rt_wkb.c @@ -0,0 +1,702 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://trac.osgeo.org/postgis/wiki/WKTRaster + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "librtcore.h" +#include "librtcore_internal.h" +#include "rt_serialize.h" + +/* Read band from WKB as at start of band */ +static rt_band +rt_band_from_wkb( + uint16_t width, uint16_t height, + const uint8_t** ptr, const uint8_t* end, + uint8_t littleEndian +) { + rt_band band = NULL; + int pixbytes = 0; + uint8_t type = 0; + unsigned long sz = 0; + uint32_t v = 0; + + assert(NULL != ptr); + assert(NULL != end); + + band = rtalloc(sizeof (struct rt_band_t)); + if (!band) { + rterror("rt_band_from_wkb: Out of memory allocating rt_band during WKB parsing"); + return NULL; + } + band->ownsdata = 0; /* assume we don't own data */ + + if (end - *ptr < 1) { + rterror("rt_band_from_wkb: Premature end of WKB on band reading (%s:%d)", + __FILE__, __LINE__); + rt_band_destroy(band); + return NULL; + } + type = read_uint8(ptr); + + if ((type & BANDTYPE_PIXTYPE_MASK) >= PT_END) { + rterror("rt_band_from_wkb: Invalid pixtype %d", type & BANDTYPE_PIXTYPE_MASK); + rt_band_destroy(band); + return NULL; + } + + band->pixtype = type & BANDTYPE_PIXTYPE_MASK; + band->offline = BANDTYPE_IS_OFFDB(type) ? 1 : 0; + band->hasnodata = BANDTYPE_HAS_NODATA(type) ? 1 : 0; + band->isnodata = band->hasnodata ? (BANDTYPE_IS_NODATA(type) ? 1 : 0) : 0; + band->width = width; + band->height = height; + + RASTER_DEBUGF(3, " Band pixtype:%s, offline:%d, hasnodata:%d", + rt_pixtype_name(band->pixtype), + band->offline, + band->hasnodata + ); + + /* Check there's enough bytes to read nodata value */ + pixbytes = rt_pixtype_size(band->pixtype); + if (((*ptr) + pixbytes) >= end) { + rterror("rt_band_from_wkb: Premature end of WKB on band novalue reading"); + rt_band_destroy(band); + return NULL; + } + + /* Read nodata value */ + switch (band->pixtype) { + case PT_1BB: { + band->nodataval = ((int) read_uint8(ptr)) & 0x01; + break; + } + case PT_2BUI: { + band->nodataval = ((int) read_uint8(ptr)) & 0x03; + break; + } + case PT_4BUI: { + band->nodataval = ((int) read_uint8(ptr)) & 0x0F; + break; + } + case PT_8BSI: { + band->nodataval = read_int8(ptr); + break; + } + case PT_8BUI: { + band->nodataval = read_uint8(ptr); + break; + } + case PT_16BSI: { + band->nodataval = read_int16(ptr, littleEndian); + break; + } + case PT_16BUI: { + band->nodataval = read_uint16(ptr, littleEndian); + break; + } + case PT_32BSI: { + band->nodataval = read_int32(ptr, littleEndian); + break; + } + case PT_32BUI: { + band->nodataval = read_uint32(ptr, littleEndian); + break; + } + case PT_32BF: { + band->nodataval = read_float32(ptr, littleEndian); + break; + } + case PT_64BF: { + band->nodataval = read_float64(ptr, littleEndian); + break; + } + default: { + rterror("rt_band_from_wkb: Unknown pixeltype %d", band->pixtype); + rt_band_destroy(band); + return NULL; + } + } + + RASTER_DEBUGF(3, " Nodata value: %g, pixbytes: %d, ptr @ %p, end @ %p", + band->nodataval, pixbytes, *ptr, end); + + if (band->offline) { + if (((*ptr) + 1) >= end) { + rterror("rt_band_from_wkb: Premature end of WKB on offline " + "band data bandNum reading (%s:%d)", + __FILE__, __LINE__ + ); + rt_band_destroy(band); + return NULL; + } + + band->data.offline.bandNum = read_int8(ptr); + band->data.offline.mem = NULL; + + { + /* check we have a NULL-termination */ + sz = 0; + while ((*ptr)[sz] && &((*ptr)[sz]) < end) ++sz; + if (&((*ptr)[sz]) >= end) { + rterror("rt_band_from_wkb: Premature end of WKB on band offline path reading"); + rt_band_destroy(band); + return NULL; + } + + /* we never own offline band data */ + band->ownsdata = 0; + + band->data.offline.path = rtalloc(sz + 1); + if (band->data.offline.path == NULL) { + rterror("rt_band_from_wkb: Out of memory allocating for offline path of band"); + rt_band_destroy(band); + return NULL; + } + + memcpy(band->data.offline.path, *ptr, sz); + band->data.offline.path[sz] = '\0'; + + RASTER_DEBUGF(3, "OFFDB band path is %s (size is %d)", + band->data.offline.path, sz); + + *ptr += sz + 1; + + /* TODO: How could we know if the offline band is a nodata band? */ + /* trust in the force */ + /*band->isnodata = FALSE;*/ + } + + return band; + } + + /* This is an on-disk band */ + sz = width * height * pixbytes; + if (((*ptr) + sz) > end) { + rterror("rt_band_from_wkb: Premature end of WKB on band data reading (%s:%d)", + __FILE__, __LINE__); + rt_band_destroy(band); + return NULL; + } + + band->data.mem = rtalloc(sz); + if (!band->data.mem) { + rterror("rt_band_from_wkb: Out of memory during band creation in WKB parser"); + rt_band_destroy(band); + return NULL; + } + + band->ownsdata = 1; /* we DO own this data!!! */ + memcpy(band->data.mem, *ptr, sz); + *ptr += sz; + + /* Should now flip values if > 8bit and + * littleEndian != isMachineLittleEndian */ + if (pixbytes > 1) { + if (isMachineLittleEndian() != littleEndian) { + void (*flipper)(uint8_t*) = 0; + uint8_t *flipme = NULL; + + if (pixbytes == 2) + flipper = flip_endian_16; + else if (pixbytes == 4) + flipper = flip_endian_32; + else if (pixbytes == 8) + flipper = flip_endian_64; + else { + rterror("rt_band_from_wkb: Unexpected pix bytes %d", pixbytes); + rt_band_destroy(band); + return NULL; + } + + flipme = band->data.mem; + sz = width * height; + for (v = 0; v < sz; ++v) { + flipper(flipme); + flipme += pixbytes; + } + } + } + /* And should check for invalid values for < 8bit types */ + else if ( + band->pixtype == PT_1BB || + band->pixtype == PT_2BUI || + band->pixtype == PT_4BUI + ) { + uint8_t maxVal = band->pixtype == PT_1BB ? 1 : (band->pixtype == PT_2BUI ? 3 : 15); + uint8_t val; + + sz = width*height; + for (v = 0; v < sz; ++v) { + val = ((uint8_t*) band->data.mem)[v]; + if (val > maxVal) { + rterror("rt_band_from_wkb: Invalid value %d for pixel of type %s", + val, rt_pixtype_name(band->pixtype)); + rt_band_destroy(band); + return NULL; + } + } + } + + /* And we should check if the band is a nodata band */ + /* TODO: No!! This is too slow */ + /*rt_band_check_is_nodata(band);*/ + + return band; +} + +/* -4 for size, +1 for endian */ +#define RT_WKB_HDR_SZ (sizeof(struct rt_raster_serialized_t)-4+1) + +rt_raster +rt_raster_from_wkb(const uint8_t* wkb, uint32_t wkbsize) { + const uint8_t *ptr = wkb; + const uint8_t *wkbend = NULL; + rt_raster rast = NULL; + uint8_t endian = 0; + uint16_t version = 0; + uint16_t i = 0; + uint16_t j = 0; + + assert(NULL != ptr); + + /* Check that wkbsize is >= sizeof(rt_raster_serialized) */ + if (wkbsize < RT_WKB_HDR_SZ) { + rterror("rt_raster_from_wkb: wkb size (%d) < min size (%d)", + wkbsize, RT_WKB_HDR_SZ); + return NULL; + } + wkbend = wkb + wkbsize; + + RASTER_DEBUGF(3, "Parsing header from wkb position %d (expected 0)", + d_binptr_to_pos(ptr, wkbend, wkbsize)); + + CHECK_BINPTR_POSITION(ptr, wkbend, wkbsize, 0); + + /* Read endianness */ + endian = *ptr; + ptr += 1; + + /* Read version of protocol */ + version = read_uint16(&ptr, endian); + if (version != 0) { + rterror("rt_raster_from_wkb: WKB version %d unsupported", version); + return NULL; + } + + /* Read other components of raster header */ + rast = (rt_raster) rtalloc(sizeof (struct rt_raster_t)); + if (!rast) { + rterror("rt_raster_from_wkb: Out of memory allocating raster for wkb input"); + return NULL; + } + + rast->numBands = read_uint16(&ptr, endian); + rast->scaleX = read_float64(&ptr, endian); + rast->scaleY = read_float64(&ptr, endian); + rast->ipX = read_float64(&ptr, endian); + rast->ipY = read_float64(&ptr, endian); + rast->skewX = read_float64(&ptr, endian); + rast->skewY = read_float64(&ptr, endian); + rast->srid = clamp_srid(read_int32(&ptr, endian)); + rast->width = read_uint16(&ptr, endian); + rast->height = read_uint16(&ptr, endian); + + /* Consistency checking, should have been checked before */ + assert(ptr <= wkbend); + + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster numBands: %d", + rast->numBands); + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster scale: %gx%g", + rast->scaleX, rast->scaleY); + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster ip: %gx%g", + rast->ipX, rast->ipY); + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster skew: %gx%g", + rast->skewX, rast->skewY); + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster srid: %d", + rast->srid); + RASTER_DEBUGF(3, "rt_raster_from_wkb: Raster dims: %dx%d", + rast->width, rast->height); + RASTER_DEBUGF(3, "Parsing raster header finished at wkb position %d (expected 61)", + d_binptr_to_pos(ptr, wkbend, wkbsize)); + + CHECK_BINPTR_POSITION(ptr, wkbend, wkbsize, 61); + + /* Read all bands of raster */ + if (!rast->numBands) { + /* Here ptr should have been left to right after last used byte */ + if (ptr < wkbend) { + rtwarn("%d bytes of WKB remained unparsed", wkbend - ptr); + } + else if (ptr > wkbend) { + /* Easier to get a segfault before I guess */ + rtwarn("We parsed %d bytes more then available!", ptr - wkbend); + } + + rast->bands = NULL; + return rast; + } + + /* Now read the bands */ + rast->bands = (rt_band*) rtalloc(sizeof(rt_band) * rast->numBands); + if (!rast->bands) { + rterror("rt_raster_from_wkb: Out of memory allocating bands for WKB raster decoding"); + rt_raster_destroy(rast); + return NULL; + } + + /* ptr should now point to start of first band */ + /* we should have checked this before */ + assert(ptr <= wkbend); + + for (i = 0; i < rast->numBands; ++i) { + RASTER_DEBUGF(3, "Parsing band %d from wkb position %d", i, + d_binptr_to_pos(ptr, wkbend, wkbsize)); + + rt_band band = rt_band_from_wkb(rast->width, rast->height, + &ptr, wkbend, endian); + if (!band) { + rterror("rt_raster_from_wkb: Error reading WKB form of band %d", i); + for (j = 0; j < i; j++) rt_band_destroy(rast->bands[j]); + rt_raster_destroy(rast); + return NULL; + } + + band->raster = rast; + rast->bands[i] = band; + } + + /* Here ptr should have been left to right after last used byte */ + if (ptr < wkbend) { + rtwarn("%d bytes of WKB remained unparsed", wkbend - ptr); + } + else if (ptr > wkbend) { + /* Easier to get a segfault before I guess */ + rtwarn("We parsed %d bytes more then available!", ptr - wkbend); + } + + return rast; +} + +rt_raster +rt_raster_from_hexwkb(const char* hexwkb, uint32_t hexwkbsize) { + rt_raster ret = NULL; + uint8_t* wkb = NULL; + uint32_t wkbsize = 0; + uint32_t i = 0; + + assert(NULL != hexwkb); + + RASTER_DEBUGF(3, "input wkb: %s", hexwkb); + RASTER_DEBUGF(3, "input wkbsize: %d", hexwkbsize); + + if (hexwkbsize % 2) { + rterror("rt_raster_from_hexwkb: Raster HEXWKB input must have an even number of characters"); + return NULL; + } + wkbsize = hexwkbsize / 2; + + wkb = rtalloc(wkbsize); + if (!wkb) { + rterror("rt_raster_from_hexwkb: Out of memory allocating memory for decoding HEXWKB"); + return NULL; + } + + /* parse full hex */ + for (i = 0; i < wkbsize; ++i) { + wkb[i] = parse_hex((char*) & (hexwkb[i * 2])); + } + + ret = rt_raster_from_wkb(wkb, wkbsize); + rtdealloc(wkb); /* as long as rt_raster_from_wkb copies memory */ + + return ret; +} + +static uint32_t +rt_raster_wkb_size(rt_raster raster, int outasin) { + uint32_t size = RT_WKB_HDR_SZ; + uint16_t i = 0; + + assert(NULL != raster); + + RASTER_DEBUGF(3, "rt_raster_wkb_size: computing size for %d bands", + raster->numBands); + + for (i = 0; i < raster->numBands; ++i) { + rt_band band = raster->bands[i]; + rt_pixtype pixtype = band->pixtype; + int pixbytes = rt_pixtype_size(pixtype); + + RASTER_DEBUGF(3, "rt_raster_wkb_size: adding size of band %d", i); + + if (pixbytes < 1) { + rterror("rt_raster_wkb_size: Corrupted band: unknown pixtype"); + return 0; + } + + /* Add space for band type */ + size += 1; + + /* Add space for nodata value */ + size += pixbytes; + + if (!outasin && band->offline) { + /* Add space for band number */ + size += 1; + + /* Add space for null-terminated path */ + size += strlen(band->data.offline.path) + 1; + } + else { + /* Add space for actual data */ + size += pixbytes * raster->width * raster->height; + } + } + + return size; +} + +/** + * Return this raster in WKB form + * + * @param raster : the raster + * @param outasin : if TRUE, out-db bands are treated as in-db + * @param wkbsize : will be set to the size of returned wkb form + * + * @return WKB of raster or NULL on error + */ +uint8_t * +rt_raster_to_wkb(rt_raster raster, int outasin, uint32_t *wkbsize) { + +#if POSTGIS_DEBUG_LEVEL > 0 + const uint8_t *wkbend = NULL; +#endif + + uint8_t *wkb = NULL; + uint8_t *ptr = NULL; + uint16_t i = 0; + uint8_t littleEndian = isMachineLittleEndian(); + + assert(NULL != raster); + assert(NULL != wkbsize); + + RASTER_DEBUG(2, "rt_raster_to_wkb: about to call rt_raster_wkb_size"); + + *wkbsize = rt_raster_wkb_size(raster, outasin); + RASTER_DEBUGF(3, "rt_raster_to_wkb: found size: %d", *wkbsize); + + wkb = (uint8_t*) rtalloc(*wkbsize); + if (!wkb) { + rterror("rt_raster_to_wkb: Out of memory allocating WKB for raster"); + return NULL; + } + + ptr = wkb; + +#if POSTGIS_DEBUG_LEVEL > 2 + wkbend = ptr + (*wkbsize); +#endif + RASTER_DEBUGF(3, "Writing raster header to wkb on position %d (expected 0)", + d_binptr_to_pos(ptr, wkbend, *wkbsize)); + + /* Write endianness */ + *ptr = littleEndian; + ptr += 1; + + /* Write version(size - (end - ptr)) */ + write_uint16(&ptr, littleEndian, 0); + + /* Copy header (from numBands up) */ + memcpy(ptr, &(raster->numBands), sizeof (struct rt_raster_serialized_t) - 6); + ptr += sizeof (struct rt_raster_serialized_t) - 6; + + RASTER_DEBUGF(3, "Writing bands header to wkb position %d (expected 61)", + d_binptr_to_pos(ptr, wkbend, *wkbsize)); + + /* Serialize bands now */ + for (i = 0; i < raster->numBands; ++i) { + rt_band band = raster->bands[i]; + rt_pixtype pixtype = band->pixtype; + int pixbytes = rt_pixtype_size(pixtype); + + RASTER_DEBUGF(3, "Writing WKB for band %d", i); + RASTER_DEBUGF(3, "Writing band pixel type to wkb position %d", + d_binptr_to_pos(ptr, wkbend, *wkbsize)); + + if (pixbytes < 1) { + rterror("rt_raster_to_wkb: Corrupted band: unknown pixtype"); + rtdealloc(wkb); + return NULL; + } + + /* Add band type */ + *ptr = band->pixtype; + if (!outasin && band->offline) *ptr |= BANDTYPE_FLAG_OFFDB; + if (band->hasnodata) *ptr |= BANDTYPE_FLAG_HASNODATA; + if (band->isnodata) *ptr |= BANDTYPE_FLAG_ISNODATA; + ptr += 1; + +#if 0 + /* no padding required for WKB */ + /* Add padding (if needed) */ + if (pixbytes > 1) { + memset(ptr, '\0', pixbytes - 1); + ptr += pixbytes - 1; + } + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!(((uint64_t) ptr) % pixbytes)); +#endif + + RASTER_DEBUGF(3, "Writing band nodata to wkb position %d", + d_binptr_to_pos(ptr, wkbend, *wkbsize)); + + /* Add nodata value */ + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: { + uint8_t v = band->nodataval; + *ptr = v; + ptr += 1; + break; + } + case PT_8BSI: { + int8_t v = band->nodataval; + *ptr = v; + ptr += 1; + break; + } + case PT_16BSI: + case PT_16BUI: { + uint16_t v = band->nodataval; + memcpy(ptr, &v, 2); + ptr += 2; + break; + } + case PT_32BSI: + case PT_32BUI: { + uint32_t v = band->nodataval; + memcpy(ptr, &v, 4); + ptr += 4; + break; + } + case PT_32BF: { + float v = band->nodataval; + memcpy(ptr, &v, 4); + ptr += 4; + break; + } + case PT_64BF: { + memcpy(ptr, &band->nodataval, 8); + ptr += 8; + break; + } + default: + rterror("rt_raster_to_wkb: Fatal error caused by unknown pixel type. Aborting."); + rtdealloc(wkb); + abort(); /* shoudn't happen */ + return 0; + } + +#if 0 + /* no padding for WKB */ + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((uint64_t) ptr % pixbytes)); +#endif + + if (!outasin && band->offline) { + /* Write band number */ + *ptr = band->data.offline.bandNum; + ptr += 1; + + /* Write path */ + strcpy((char*) ptr, band->data.offline.path); + ptr += strlen(band->data.offline.path) + 1; + } + else { + /* Write data */ + uint32_t datasize = raster->width * raster->height * pixbytes; + RASTER_DEBUGF(4, "rt_raster_to_wkb: Copying %d bytes", datasize); + + memcpy(ptr, rt_band_get_data(band), datasize); + + ptr += datasize; + } + +#if 0 + /* no padding for WKB */ + /* Pad up to 8-bytes boundary */ + while ((uint64_t) ptr % 8) { + *ptr = 0; + ++ptr; + } + + /* Consistency checking (ptr is pixbytes-aligned) */ + assert(!((uint64_t) ptr % pixbytes)); +#endif + } + + return wkb; +} + +char * +rt_raster_to_hexwkb(rt_raster raster, int outasin, uint32_t *hexwkbsize) { + uint8_t *wkb = NULL; + char* hexwkb = NULL; + uint32_t i = 0; + uint32_t wkbsize = 0; + + assert(NULL != raster); + assert(NULL != hexwkbsize); + + RASTER_DEBUG(2, "rt_raster_to_hexwkb: calling rt_raster_to_wkb"); + + wkb = rt_raster_to_wkb(raster, outasin, &wkbsize); + + RASTER_DEBUG(3, "rt_raster_to_hexwkb: rt_raster_to_wkb returned"); + + *hexwkbsize = wkbsize * 2; /* hex is 2 times bytes */ + hexwkb = (char*) rtalloc((*hexwkbsize) + 1); + if (!hexwkb) { + rterror("rt_raster_to_hexwkb: Out of memory hexifying raster WKB"); + rtdealloc(wkb); + return NULL; + } + hexwkb[*hexwkbsize] = '\0'; /* Null-terminate */ + + for (i = 0; i < wkbsize; ++i) { + deparse_hex(wkb[i], &(hexwkb[2 * i])); + } + + rtdealloc(wkb); /* we don't need this anymore */ + + RASTER_DEBUGF(3, "rt_raster_to_hexwkb: output wkb: %s", hexwkb); + return hexwkb; +} diff --git a/raster/rt_pg/Makefile.in b/raster/rt_pg/Makefile.in index fcc77930b..4c6291fe2 100644 --- a/raster/rt_pg/Makefile.in +++ b/raster/rt_pg/Makefile.in @@ -24,7 +24,20 @@ SQLPP = @SQLPP@ SQL_OBJS=rtpostgis.sql rtpostgis_drop.sql rtpostgis_upgrade_cleanup.sql rtpostgis_legacy.sql # Objects to build using PGXS -OBJS=rt_pg.o +OBJS = \ + rtpostgis.o \ + rtpg_internal.o \ + rtpg_spatial_relationship.o \ + rtpg_mapalgebra.o \ + rtpg_utility.o \ + rtpg_inout.o \ + rtpg_geometry.o \ + rtpg_raster_properties.o \ + rtpg_band_properties.o \ + rtpg_pixel.o \ + rtpg_create.o \ + rtpg_gdal.o \ + rtpg_statistics.o # Libraries to link into the module (proj, geos) # diff --git a/raster/rt_pg/rt_pg.c b/raster/rt_pg/rt_pg.c deleted file mode 100644 index e885b0a0b..000000000 --- a/raster/rt_pg/rt_pg.c +++ /dev/null @@ -1,19220 +0,0 @@ -/* - * $Id$ - * - * WKTRaster - Raster Types for PostGIS - * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage - * - * Copyright (C) 2011-2013 Regents of the University of California - * - * Copyright (C) 2010-2011 Jorge Arevalo - * Copyright (C) 2010-2011 David Zwarg - * Copyright (C) 2009-2011 Pierre Racine - * Copyright (C) 2009-2011 Mateusz Loskot - * Copyright (C) 2008-2009 Sandro Santilli - * - * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include -#include -#include -#include /* for strtod in RASTER_reclass */ -#include -#include -#include /* for isspace */ - -#include /* for palloc */ -#include -#include -#include -#include -#include -#include -#include /* for GetAttributeByName in RASTER_reclass */ -#include - -#include "../../postgis_config.h" - -#include "utils/guc.h" -#include "lwgeom_pg.h" -#include "rt_pg.h" -#include "pgsql_compat.h" - -#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ -#include "utils/array.h" /* for ArrayType */ -#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ - -#if POSTGIS_PGSQL_VERSION > 92 -#include "access/htup_details.h" -#endif - -/* maximum char length required to hold any double or long long value */ -#define MAX_DBL_CHARLEN (3 + DBL_MANT_DIG - DBL_MIN_EXP) -#define MAX_INT_CHARLEN 32 - -/* - * This is required for builds against pgsql - */ -PG_MODULE_MAGIC; - -/* Module load callback */ -void _PG_init(void); - -/*************************************************************** - * Internal functions must be prefixed with rtpg_. This is - * keeping inline with the use of pgis_ for ./postgis C utility - * functions. - ****************************************************************/ -/* Internal funcs */ -static char *rtpg_strreplace( - const char *str, - const char *oldstr, const char *newstr, - int *count -); -static char *rtpg_strtoupper(char *str); -static char *rtpg_chartrim(const char* input, char *remove); -static char **rtpg_strsplit(const char *str, const char *delimiter, int *n); -static char *rtpg_removespaces(char *str); -static char *rtpg_trim(const char* input); -static char *rtpg_getSR(int srid); - -static char *gdaldatapath; -static void rtpg_assignHookGDALDataPath(const char *newpath, void *extra); - -/*************************************************************** - * Some rules for returning NOTICE or ERROR... - * - * Send an ERROR like: - * - * elog(ERROR, "RASTER_out: Could not deserialize raster"); - * - * only when: - * - * -something wrong happen with memory, - * -a function got an invalid argument ('3BUI' as pixel type) so that no row can - * be processed - * - * *** IMPORTANT: elog(ERROR, ...) does NOT return to calling function *** - * - * Send a NOTICE like: - * - * elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL"); - * - * when arguments (e.g. x, y, band) are NULL or out of range so that some or - * most rows can be processed anyway - * - * in this case, - * for SET functions or function normally returning a modified raster, return - * the original raster - * for GET functions, return NULL - * try to deduce a valid parameter value if it makes sence (e.g. out of range - * index for addBand) - * - * Do not put the name of the faulty function for NOTICEs, only with ERRORs. - * - ****************************************************************/ - -/****************************************************************************** - * Some notes on memory management... - * - * Every time a SQL function is called, PostgreSQL creates a new memory context. - * So, all the memory allocated with palloc/repalloc in that context is - * automatically free'd at the end of the function. If you want some data to - * live between function calls, you have 2 options: - * - * - Use fcinfo->flinfo->fn_mcxt contex to store the data (by pointing the - * data you want to keep with fcinfo->flinfo->fn_extra) - * - Use SRF funcapi, and storing the data at multi_call_memory_ctx (by pointing - * the data you want to keep with funcctx->user_fctx. funcctx is created by - * funcctx = SPI_FIRSTCALL_INIT()). Recommended way in functions returning rows, - * like RASTER_dumpAsPolygons (see section 34.9.9 at - * http://www.postgresql.org/docs/8.4/static/xfunc-c.html). - * - * But raster code follows the same philosophy than the rest of PostGIS: keep - * memory as clean as possible. So, we free all allocated memory. - * - * TODO: In case of functions returning NULL, we should free the memory too. - *****************************************************************************/ - -/****************************************************************************** - * Notes for use of PG_DETOAST_DATUM(), PG_DETOAST_DATUM_SLICE() - * and PG_DETOAST_DATUM_COPY() - * - * When ONLY getting raster (not band) metadata, use PG_DETOAST_DATUM_SLICE() - * as it is generally quicker to get only the chunk of memory that contains - * the raster metadata. - * - * Example: PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, - * sizeof(struct rt_raster_serialized_t)) - * - * When ONLY setting raster or band(s) metadata OR reading band data, use - * PG_DETOAST_DATUM() as rt_raster_deserialize() allocates local memory - * for the raster and band(s) metadata. - * - * Example: PG_DETOAST_DATUM(PG_GETARG_DATUM(0)) - * - * When SETTING band pixel values, use PG_DETOAST_DATUM_COPY(). This is - * because band data (not metadata) is just a pointer to the correct - * memory location in the detoasted datum. What is returned from - * PG_DETOAST_DATUM() may or may not be a copy of the input datum. - * - * From the comments in postgresql/src/include/fmgr.h... - * - * pg_detoast_datum() gives you either the input datum (if not toasted) - * or a detoasted copy allocated with palloc(). - * - * From the mouth of Tom Lane... - * http://archives.postgresql.org/pgsql-hackers/2002-01/msg01289.php - * - * PG_DETOAST_DATUM_COPY guarantees to give you a copy, even if the - * original wasn't toasted. This allows you to scribble on the input, - * in case that happens to be a useful way of forming your result. - * Without a forced copy, a routine for a pass-by-ref datatype must - * NEVER, EVER scribble on its input ... because very possibly it'd - * be scribbling on a valid tuple in a disk buffer, or a valid entry - * in the syscache. - * - * The key detail above is that the raster datatype is a varlena, a - * passed by reference datatype. - * - * Example: PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)) - * - * If in doubt, use PG_DETOAST_DATUM_COPY() as that guarantees that the input - * datum is copied for use. - *****************************************************************************/ - -/* Prototypes */ - -/* Utility functions */ -Datum RASTER_lib_version(PG_FUNCTION_ARGS); -Datum RASTER_lib_build_date(PG_FUNCTION_ARGS); -Datum RASTER_gdal_version(PG_FUNCTION_ARGS); -Datum RASTER_minPossibleValue(PG_FUNCTION_ARGS); - -/* Input/output and format conversions */ -Datum RASTER_in(PG_FUNCTION_ARGS); -Datum RASTER_out(PG_FUNCTION_ARGS); - -Datum RASTER_to_bytea(PG_FUNCTION_ARGS); -Datum RASTER_to_binary(PG_FUNCTION_ARGS); - -/* Raster as geometry operations */ -Datum RASTER_convex_hull(PG_FUNCTION_ARGS); -Datum RASTER_dumpAsPolygons(PG_FUNCTION_ARGS); - -/* Get all the properties of a raster */ -Datum RASTER_getSRID(PG_FUNCTION_ARGS); -Datum RASTER_getWidth(PG_FUNCTION_ARGS); -Datum RASTER_getHeight(PG_FUNCTION_ARGS); -Datum RASTER_getNumBands(PG_FUNCTION_ARGS); -Datum RASTER_getXScale(PG_FUNCTION_ARGS); -Datum RASTER_getYScale(PG_FUNCTION_ARGS); -Datum RASTER_getXSkew(PG_FUNCTION_ARGS); -Datum RASTER_getYSkew(PG_FUNCTION_ARGS); -Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS); -Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS); -Datum RASTER_getPixelWidth(PG_FUNCTION_ARGS); -Datum RASTER_getPixelHeight(PG_FUNCTION_ARGS); -Datum RASTER_getGeotransform(PG_FUNCTION_ARGS); - -/* Set all the properties of a raster */ -Datum RASTER_setSRID(PG_FUNCTION_ARGS); -Datum RASTER_setScale(PG_FUNCTION_ARGS); -Datum RASTER_setScaleXY(PG_FUNCTION_ARGS); -Datum RASTER_setSkew(PG_FUNCTION_ARGS); -Datum RASTER_setSkewXY(PG_FUNCTION_ARGS); -Datum RASTER_setUpperLeftXY(PG_FUNCTION_ARGS); -Datum RASTER_setRotation(PG_FUNCTION_ARGS); -Datum RASTER_setGeotransform(PG_FUNCTION_ARGS); - -/* Get all the properties of a raster band */ -Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS); -Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS); -Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS); -Datum RASTER_getBandPath(PG_FUNCTION_ARGS); -Datum RASTER_bandIsNoData(PG_FUNCTION_ARGS); -Datum RASTER_isEmpty(PG_FUNCTION_ARGS); -Datum RASTER_hasNoBand(PG_FUNCTION_ARGS); - -/* Set all the properties of a raster band */ -Datum RASTER_setBandIsNoData(PG_FUNCTION_ARGS); -Datum RASTER_setBandNoDataValue(PG_FUNCTION_ARGS); - -/* Get pixel value */ -Datum RASTER_getPixelValue(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); - -/* Get pixel geographical shape */ -Datum RASTER_getPixelPolygons(PG_FUNCTION_ARGS); - -/* Get raster band's polygon */ -Datum RASTER_getPolygon(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); - -/* Raster and band creation */ -Datum RASTER_makeEmpty(PG_FUNCTION_ARGS); -Datum RASTER_addBand(PG_FUNCTION_ARGS); -Datum RASTER_copyBand(PG_FUNCTION_ARGS); -Datum RASTER_addBandRasterArray(PG_FUNCTION_ARGS); -Datum RASTER_addBandOutDB(PG_FUNCTION_ARGS); -Datum RASTER_tile(PG_FUNCTION_ARGS); - -/* create new raster from existing raster's bands */ -Datum RASTER_band(PG_FUNCTION_ARGS); - -/* Get summary stats */ -Datum RASTER_summaryStats(PG_FUNCTION_ARGS); -Datum RASTER_summaryStatsCoverage(PG_FUNCTION_ARGS); - -/* get histogram */ -Datum RASTER_histogram(PG_FUNCTION_ARGS); -Datum RASTER_histogramCoverage(PG_FUNCTION_ARGS); - -/* get quantiles */ -Datum RASTER_quantile(PG_FUNCTION_ARGS); -Datum RASTER_quantileCoverage(PG_FUNCTION_ARGS); - -/* get counts of values */ -Datum RASTER_valueCount(PG_FUNCTION_ARGS); -Datum RASTER_valueCountCoverage(PG_FUNCTION_ARGS); - -/* reclassify specified bands of a raster */ -Datum RASTER_reclass(PG_FUNCTION_ARGS); - -/* apply colormap to specified band of a raster */ -Datum RASTER_colorMap(PG_FUNCTION_ARGS); - -/* convert GDAL raster to raster */ -Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS); - -/* convert raster to GDAL raster */ -Datum RASTER_asGDALRaster(PG_FUNCTION_ARGS); -Datum RASTER_getGDALDrivers(PG_FUNCTION_ARGS); - -/* rasterize a geometry */ -Datum RASTER_asRaster(PG_FUNCTION_ARGS); - -/* warp a raster using GDAL Warp API */ -Datum RASTER_GDALWarp(PG_FUNCTION_ARGS); - -/* get raster's meta data */ -Datum RASTER_metadata(PG_FUNCTION_ARGS); - -/* get raster band's meta data */ -Datum RASTER_bandmetadata(PG_FUNCTION_ARGS); - -/* convert pixel/line to spatial coordinates */ -Datum RASTER_rasterToWorldCoord(PG_FUNCTION_ARGS); - -/* convert spatial coordinates to pixel/line*/ -Datum RASTER_worldToRasterCoord(PG_FUNCTION_ARGS); - -/* determine if two rasters intersect */ -Datum RASTER_intersects(PG_FUNCTION_ARGS); - -/* determine if two rasters overlap */ -Datum RASTER_overlaps(PG_FUNCTION_ARGS); - -/* determine if two rasters touch */ -Datum RASTER_touches(PG_FUNCTION_ARGS); - -/* determine if the first raster contains the second raster */ -Datum RASTER_contains(PG_FUNCTION_ARGS); - -/* determine if the first raster contains properly the second raster */ -Datum RASTER_containsProperly(PG_FUNCTION_ARGS); - -/* determine if the first raster covers the second raster */ -Datum RASTER_covers(PG_FUNCTION_ARGS); - -/* determine if the first raster is covered by the second raster */ -Datum RASTER_coveredby(PG_FUNCTION_ARGS); - -/* determine if the two rasters are within the specified distance of each other */ -Datum RASTER_dwithin(PG_FUNCTION_ARGS); - -/* determine if the two rasters are fully within the specified distance of each other */ -Datum RASTER_dfullywithin(PG_FUNCTION_ARGS); - -/* determine if two rasters are aligned */ -Datum RASTER_sameAlignment(PG_FUNCTION_ARGS); -Datum RASTER_notSameAlignmentReason(PG_FUNCTION_ARGS); - -/* one-raster MapAlgebra */ -Datum RASTER_mapAlgebraExpr(PG_FUNCTION_ARGS); -Datum RASTER_mapAlgebraFct(PG_FUNCTION_ARGS); - -/* one-raster neighborhood MapAlgebra */ -Datum RASTER_mapAlgebraFctNgb(PG_FUNCTION_ARGS); - -/* two-raster MapAlgebra */ -Datum RASTER_mapAlgebra2(PG_FUNCTION_ARGS); - -/* n-raster MapAlgebra */ -Datum RASTER_nMapAlgebra(PG_FUNCTION_ARGS); -Datum RASTER_nMapAlgebraExpr(PG_FUNCTION_ARGS); - -/* raster union aggregate */ -Datum RASTER_union_transfn(PG_FUNCTION_ARGS); -Datum RASTER_union_finalfn(PG_FUNCTION_ARGS); - -/* raster clip */ -Datum RASTER_clip(PG_FUNCTION_ARGS); - -/* Module load callback */ -void -_PG_init(void) { - /* Install liblwgeom handlers */ - pg_install_lwgeom_handlers(); - - /* TODO: Install raster callbacks (see rt_init_allocators)??? */ - - /* Define custom GUC variables. */ - DefineCustomStringVariable( - "postgis.gdal.datapath", /* name */ - "Path to GDAL data files.", /* short_desc */ - "Physical path to directory containing GDAL data files (sets the GDAL_DATA config option).", /* long_desc */ - &gdaldatapath, /* valueAddr */ - NULL, /* bootValue */ - PGC_SUSET, /* GucContext context */ - 0, /* int flags */ -#if POSTGIS_PGSQL_VERSION >= 91 - NULL, /* GucStringCheckHook check_hook */ -#endif - rtpg_assignHookGDALDataPath, /* GucStringAssignHook assign_hook */ - NULL /* GucShowHook show_hook */ - ); -} - -/* string replacement function taken from - * http://ubuntuforums.org/showthread.php?s=aa6f015109fd7e4c7e30d2fd8b717497&t=141670&page=3 - */ -/* --------------------------------------------------------------------------- - Name : replace - Search & replace a substring by another one. - Creation : Thierry Husson, Sept 2010 - Parameters : - str : Big string where we search - oldstr : Substring we are looking for - newstr : Substring we want to replace with - count : Optional pointer to int (input / output value). NULL to ignore. - Input: Maximum replacements to be done. NULL or < 1 to do all. - Output: Number of replacements done or -1 if not enough memory. - Returns : Pointer to the new string or NULL if error. - Notes : - - Case sensitive - Otherwise, replace functions "strstr" by "strcasestr" - - Always allocates memory for the result. ---------------------------------------------------------------------------- */ -static char* -rtpg_strreplace( - const char *str, - const char *oldstr, const char *newstr, - int *count -) { - const char *tmp = str; - char *result; - int found = 0; - int length, reslen; - int oldlen = strlen(oldstr); - int newlen = strlen(newstr); - int limit = (count != NULL && *count > 0) ? *count : -1; - - tmp = str; - while ((tmp = strstr(tmp, oldstr)) != NULL && found != limit) - found++, tmp += oldlen; - - length = strlen(str) + found * (newlen - oldlen); - if ((result = (char *) palloc(length + 1)) == NULL) { - fprintf(stderr, "Not enough memory\n"); - found = -1; - } - else { - tmp = str; - limit = found; /* Countdown */ - reslen = 0; /* length of current result */ - - /* Replace each old string found with new string */ - while ((limit-- > 0) && (tmp = strstr(tmp, oldstr)) != NULL) { - length = (tmp - str); /* Number of chars to keep intouched */ - strncpy(result + reslen, str, length); /* Original part keeped */ - strcpy(result + (reslen += length), newstr); /* Insert new string */ - - reslen += newlen; - tmp += oldlen; - str = tmp; - } - strcpy(result + reslen, str); /* Copies last part and ending null char */ - } - - if (count != NULL) *count = found; - return result; -} - -static char * -rtpg_strtoupper(char * str) { - int j; - - for (j = strlen(str) - 1; j >= 0; j--) - str[j] = toupper(str[j]); - - return str; -} - -static char* -rtpg_chartrim(const char *input, char *remove) { - char *rtn = NULL; - char *ptr = NULL; - uint32_t offset = 0; - - if (!input) - return NULL; - else if (!*input) - return (char *) input; - - /* trim left */ - while (strchr(remove, *input) != NULL) - input++; - - /* trim right */ - ptr = ((char *) input) + strlen(input); - while (strchr(remove, *--ptr) != NULL) - offset++; - - rtn = palloc(sizeof(char) * (strlen(input) - offset + 1)); - if (rtn == NULL) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - strncpy(rtn, input, strlen(input) - offset); - rtn[strlen(input) - offset] = '\0'; - - return rtn; -} - -/* split a string based on a delimiter */ -static char** -rtpg_strsplit(const char *str, const char *delimiter, int *n) { - char *tmp = NULL; - char **rtn = NULL; - char *token = NULL; - - *n = 0; - if (!str) - return NULL; - - /* copy str to tmp as strtok will mangle the string */ - tmp = palloc(sizeof(char) * (strlen(str) + 1)); - if (NULL == tmp) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - strcpy(tmp, str); - - if (!strlen(tmp) || !delimiter || !strlen(delimiter)) { - *n = 1; - rtn = (char **) palloc(*n * sizeof(char *)); - if (NULL == rtn) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - rtn[0] = (char *) palloc(sizeof(char) * (strlen(tmp) + 1)); - if (NULL == rtn[0]) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - strcpy(rtn[0], tmp); - pfree(tmp); - return rtn; - } - - token = strtok(tmp, delimiter); - while (token != NULL) { - if (*n < 1) { - rtn = (char **) palloc(sizeof(char *)); - } - else { - rtn = (char **) repalloc(rtn, (*n + 1) * sizeof(char *)); - } - if (NULL == rtn) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - - rtn[*n] = NULL; - rtn[*n] = (char *) palloc(sizeof(char) * (strlen(token) + 1)); - if (NULL == rtn[*n]) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - - strcpy(rtn[*n], token); - *n = *n + 1; - - token = strtok(NULL, delimiter); - } - - pfree(tmp); - return rtn; -} - -static char * -rtpg_removespaces(char *str) { - char *rtn; - char *tmp; - - rtn = rtpg_strreplace(str, " ", "", NULL); - - tmp = rtpg_strreplace(rtn, "\n", "", NULL); - pfree(rtn); - rtn = rtpg_strreplace(tmp, "\t", "", NULL); - pfree(tmp); - tmp = rtpg_strreplace(rtn, "\f", "", NULL); - pfree(rtn); - rtn = rtpg_strreplace(tmp, "\r", "", NULL); - pfree(tmp); - - return rtn; -} - -static char* -rtpg_trim(const char *input) { - char *rtn; - char *ptr; - uint32_t offset = 0; - int inputlen = 0; - - if (!input) - return NULL; - else if (!*input) - return (char *) input; - - /* trim left */ - while (isspace(*input) && *input != '\0') - input++; - - /* trim right */ - inputlen = strlen(input); - if (inputlen) { - ptr = ((char *) input) + inputlen; - while (isspace(*--ptr)) - offset++; - } - - rtn = palloc(sizeof(char) * (inputlen - offset + 1)); - if (rtn == NULL) { - fprintf(stderr, "Not enough memory\n"); - return NULL; - } - strncpy(rtn, input, inputlen - offset); - rtn[inputlen - offset] = '\0'; - - return rtn; -} - -static char* -rtpg_getSR(int srid) { - int i = 0; - int len = 0; - char *sql = NULL; - int spi_result; - TupleDesc tupdesc; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - char *tmp = NULL; - char *srs = NULL; - -/* -SELECT - CASE - WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 - THEN upper(auth_name) || ':' || auth_srid - WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 - THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') - ELSE '' - END, - proj4text, - srtext -FROM spatial_ref_sys -WHERE srid = X -LIMIT 1 -*/ - - len = sizeof(char) * (strlen("SELECT CASE WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 THEN upper(auth_name) || ':' || auth_srid WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') ELSE '' END, proj4text, srtext FROM spatial_ref_sys WHERE srid = LIMIT 1") + MAX_INT_CHARLEN + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - elog(ERROR, "rtpg_getSR: Could not allocate memory for sql\n"); - return NULL; - } - - spi_result = SPI_connect(); - if (spi_result != SPI_OK_CONNECT) { - pfree(sql); - elog(ERROR, "rtpg_getSR: Could not connect to database using SPI\n"); - return NULL; - } - - /* execute query */ - snprintf(sql, len, "SELECT CASE WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 THEN upper(auth_name) || ':' || auth_srid WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') ELSE '' END, proj4text, srtext FROM spatial_ref_sys WHERE srid = %d LIMIT 1", srid); - POSTGIS_RT_DEBUGF(4, "SRS query: %s", sql); - spi_result = SPI_execute(sql, TRUE, 0); - SPI_pfree(sql); - if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - elog(ERROR, "rtpg_getSR: Cannot find SRID (%d) in spatial_ref_sys", srid); - return NULL; - } - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - /* which column to use? */ - for (i = 1; i < 4; i++) { - tmp = SPI_getvalue(tuple, tupdesc, i); - - /* value AND GDAL supports this SR */ - if ( - SPI_result != SPI_ERROR_NOATTRIBUTE && - SPI_result != SPI_ERROR_NOOUTFUNC && - tmp != NULL && - strlen(tmp) && - rt_util_gdal_supported_sr(tmp) - ) { - POSTGIS_RT_DEBUGF(4, "Value for column %d is %s", i, tmp); - - len = strlen(tmp) + 1; - srs = SPI_palloc(sizeof(char) * len); - if (NULL == srs) { - pfree(tmp); - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - elog(ERROR, "rtpg_getSR: Could not allocate memory for spatial reference text\n"); - return NULL; - } - strncpy(srs, tmp, len); - pfree(tmp); - - break; - } - - if (tmp != NULL) - pfree(tmp); - continue; - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - /* unable to get SR info */ - if (srs == NULL) { - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - elog(ERROR, "rtpg_getSR: Could not find a viable spatial reference for SRID (%d)", srid); - return NULL; - } - - return srs; -} - -static void -rtpg_assignHookGDALDataPath(const char *newpath, void *extra) { - POSTGIS_RT_DEBUGF(4, "newpath = %s", newpath); - - /* clear finder cache */ - CPLFinderClean(); - - /* clear cached OSR */ - OSRCleanup(); - - /* set GDAL_DATA */ - CPLSetConfigOption("GDAL_DATA", newpath); - POSTGIS_RT_DEBUGF(4, "GDAL_DATA = %s", CPLGetConfigOption("GDAL_DATA", NULL)); -} - -PG_FUNCTION_INFO_V1(RASTER_lib_version); -Datum RASTER_lib_version(PG_FUNCTION_ARGS) -{ - char ver[64]; - text *result; - - snprintf(ver, 64, "%s r%d", POSTGIS_LIB_VERSION, POSTGIS_SVN_REVISION); - ver[63] = '\0'; - - result = cstring2text(ver); - PG_RETURN_TEXT_P(result); -} - -PG_FUNCTION_INFO_V1(RASTER_lib_build_date); -Datum RASTER_lib_build_date(PG_FUNCTION_ARGS) -{ - char *ver = POSTGIS_BUILD_DATE; - text *result; - result = palloc(VARHDRSZ + strlen(ver)); - SET_VARSIZE(result, VARHDRSZ + strlen(ver)); - memcpy(VARDATA(result), ver, strlen(ver)); - PG_RETURN_POINTER(result); -} - -PG_FUNCTION_INFO_V1(RASTER_gdal_version); -Datum RASTER_gdal_version(PG_FUNCTION_ARGS) -{ - const char *ver = rt_util_gdal_version("--version"); - text *result; - - /* add indicator if GDAL isn't configured right */ - if (!rt_util_gdal_configured()) { - char *rtn = NULL; - rtn = palloc(strlen(ver) + strlen(" GDAL_DATA not found") + 1); - if (!rtn) - result = cstring2text(ver); - else { - sprintf(rtn, "%s GDAL_DATA not found", ver); - result = cstring2text(rtn); - pfree(rtn); - } - } - else - result = cstring2text(ver); - - PG_RETURN_POINTER(result); -} - -PG_FUNCTION_INFO_V1(RASTER_minPossibleValue); -Datum RASTER_minPossibleValue(PG_FUNCTION_ARGS) -{ - text *pixeltypetext = NULL; - char *pixeltypechar = NULL; - rt_pixtype pixtype = PT_END; - double pixsize = 0; - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - pixeltypetext = PG_GETARG_TEXT_P(0); - pixeltypechar = text_to_cstring(pixeltypetext); - - pixtype = rt_pixtype_index_from_name(pixeltypechar); - if (pixtype == PT_END) { - elog(ERROR, "RASTER_minPossibleValue: Invalid pixel type: %s", pixeltypechar); - PG_RETURN_NULL(); - } - - pixsize = rt_pixtype_get_min_value(pixtype); - - /* - correct pixsize of unsigned pixel types - example: for PT_8BUI, the value is CHAR_MIN but if char is signed, - the value returned is -127 instead of 0. - */ - switch (pixtype) { - case PT_1BB: - case PT_2BUI: - case PT_4BUI: - case PT_8BUI: - case PT_16BUI: - case PT_32BUI: - pixsize = 0; - break; - default: - break; - } - - PG_RETURN_FLOAT8(pixsize); -} - -/** - * Input is a string with hex chars in it. - * Convert to binary and put in the result - */ -PG_FUNCTION_INFO_V1(RASTER_in); -Datum RASTER_in(PG_FUNCTION_ARGS) -{ - rt_raster raster; - char *hexwkb = PG_GETARG_CSTRING(0); - void *result = NULL; - - POSTGIS_RT_DEBUG(3, "Starting"); - - raster = rt_raster_from_hexwkb(hexwkb, strlen(hexwkb)); - if (raster == NULL) - PG_RETURN_NULL(); - - result = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (result == NULL) - PG_RETURN_NULL(); - - SET_VARSIZE(result, ((rt_pgraster*)result)->size); - PG_RETURN_POINTER(result); -} - -/** - * Given a RASTER structure, convert it to Hex and put it in a string - */ -PG_FUNCTION_INFO_V1(RASTER_out); -Datum RASTER_out(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - uint32_t hexwkbsize = 0; - char *hexwkb = NULL; - - POSTGIS_RT_DEBUG(3, "Starting"); - - 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_out: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - hexwkb = rt_raster_to_hexwkb(raster, FALSE, &hexwkbsize); - if (!hexwkb) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_out: Could not HEX-WKBize raster"); - PG_RETURN_NULL(); - } - - /* Free the raster objects used */ - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_CSTRING(hexwkb); -} - -/** - * Return bytea object with raster in Well-Known-Binary form. - */ -PG_FUNCTION_INFO_V1(RASTER_to_bytea); -Datum RASTER_to_bytea(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - uint8_t *wkb = NULL; - uint32_t wkb_size = 0; - bytea *result = NULL; - int result_size = 0; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* Get raster object */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_to_bytea: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Parse raster to wkb object */ - wkb = rt_raster_to_wkb(raster, FALSE, &wkb_size); - if (!wkb) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_to_bytea: Could not allocate and generate WKB data"); - PG_RETURN_NULL(); - } - - /* Create varlena object */ - result_size = wkb_size + VARHDRSZ; - result = (bytea *)palloc(result_size); - SET_VARSIZE(result, result_size); - memcpy(VARDATA(result), wkb, VARSIZE(result) - VARHDRSZ); - - /* Free raster objects used */ - rt_raster_destroy(raster); - pfree(wkb); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_POINTER(result); -} - -/** - * Return bytea object with raster in Well-Known-Binary form requested using ST_AsBinary function. - */ -PG_FUNCTION_INFO_V1(RASTER_to_binary); -Datum RASTER_to_binary(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - uint8_t *wkb = NULL; - uint32_t wkb_size = 0; - char *result = NULL; - int result_size = 0; - int outasin = FALSE; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* Get raster object */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_to_binary: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - if (!PG_ARGISNULL(1)) - outasin = PG_GETARG_BOOL(1); - - /* Parse raster to wkb object */ - wkb = rt_raster_to_wkb(raster, outasin, &wkb_size); - if (!wkb) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_to_binary: Could not allocate and generate WKB data"); - PG_RETURN_NULL(); - } - - /* Create varlena object */ - result_size = wkb_size + VARHDRSZ; - result = (char *)palloc(result_size); - SET_VARSIZE(result, result_size); - memcpy(VARDATA(result), wkb, VARSIZE(result) - VARHDRSZ); - - /* Free raster objects used */ - rt_raster_destroy(raster); - pfree(wkb); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_POINTER(result); -} - -/** - * Return the convex hull of this raster - */ -PG_FUNCTION_INFO_V1(RASTER_convex_hull); -Datum RASTER_convex_hull(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - LWGEOM *geom = NULL; - GSERIALIZED* gser = NULL; - size_t gser_size; - int err = ES_NONE; - - bool minhull = FALSE; - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - /* # of args */ - if (PG_NARGS() > 1) - minhull = TRUE; - - if (!minhull) { - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - raster = rt_raster_deserialize(pgraster, TRUE); - } - else { - 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_convex_hull: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - if (!minhull) - err = rt_raster_get_convex_hull(raster, &geom); - else { - int nband = -1; - - /* get arg 1 */ - if (!PG_ARGISNULL(1)) { - nband = PG_GETARG_INT32(1); - if (!rt_raster_has_band(raster, nband - 1)) { - elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - nband = nband - 1; - } - - err = rt_raster_get_perimeter(raster, nband, &geom); - } - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - if (err != ES_NONE) { - elog(ERROR, "RASTER_convex_hull: Could not get raster's convex hull"); - PG_RETURN_NULL(); - } - else if (geom == NULL) { - elog(NOTICE, "Raster's convex hull is NULL"); - PG_RETURN_NULL(); - } - - gser = gserialized_from_lwgeom(geom, 0, &gser_size); - lwgeom_free(geom); - - SET_VARSIZE(gser, gser_size); - PG_RETURN_POINTER(gser); -} - -PG_FUNCTION_INFO_V1(RASTER_dumpAsPolygons); -Datum RASTER_dumpAsPolygons(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - TupleDesc tupdesc; - rt_geomval geomval; - rt_geomval geomval2; - int call_cntr; - int max_calls; - - /* stuff done only on the first call of the function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - int numbands; - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - int nband; - bool exclude_nodata_value = TRUE; - int nElements; - - POSTGIS_RT_DEBUG(2, "RASTER_dumpAsPolygons 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); - } - - if (!PG_ARGISNULL(1)) - nband = PG_GETARG_UINT32(1); - else - nband = 1; /* By default, first band */ - - POSTGIS_RT_DEBUGF(3, "band %d", nband); - numbands = rt_raster_get_num_bands(raster); - - if (nband < 1 || nband > numbands) { - 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); - } - - if (!PG_ARGISNULL(2)) - exclude_nodata_value = PG_GETARG_BOOL(2); - - /* see if band is NODATA */ - if (rt_band_get_isnodata_flag(rt_raster_get_band(raster, nband - 1))) { - POSTGIS_RT_DEBUGF(3, "Band at index %d is NODATA. Returning NULL", nband); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* Polygonize raster */ - - /** - * Dump raster - */ - geomval = rt_raster_gdal_polygonize(raster, nband - 1, exclude_nodata_value, &nElements); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (NULL == geomval) { - ereport(ERROR, ( - errcode(ERRCODE_NO_DATA_FOUND), - errmsg("Could not polygonize raster") - )); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "raster dump, %d elements returned", nElements); - - /* Store needed information */ - funcctx->user_fctx = geomval; - - /* total number of tuples to be returned */ - funcctx->max_calls = nElements; - - /* 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; - geomval2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - GSERIALIZED *gser = NULL; - size_t gser_size = 0; - - POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* convert LWGEOM to GSERIALIZED */ - gser = gserialized_from_lwgeom(lwpoly_as_lwgeom(geomval2[call_cntr].geom), 0, &gser_size); - lwgeom_free(lwpoly_as_lwgeom(geomval2[call_cntr].geom)); - - values[0] = PointerGetDatum(gser); - values[1] = Float8GetDatum(geomval2[call_cntr].val); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(geomval2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Make a new raster with no bands - */ -PG_FUNCTION_INFO_V1(RASTER_makeEmpty); -Datum RASTER_makeEmpty(PG_FUNCTION_ARGS) -{ - uint16 width = 0, height = 0; - double ipx = 0, ipy = 0, scalex = 0, scaley = 0, skewx = 0, skewy = 0; - int32_t srid = SRID_UNKNOWN; - rt_pgraster *pgraster = NULL; - rt_raster raster; - - if (PG_NARGS() < 9) { - elog(ERROR, "RASTER_makeEmpty: ST_MakeEmptyRaster requires 9 args"); - PG_RETURN_NULL(); - } - - if (!PG_ARGISNULL(0)) - width = PG_GETARG_UINT16(0); - - if (!PG_ARGISNULL(1)) - height = PG_GETARG_UINT16(1); - - if (!PG_ARGISNULL(2)) - ipx = PG_GETARG_FLOAT8(2); - - if (!PG_ARGISNULL(3)) - ipy = PG_GETARG_FLOAT8(3); - - if (!PG_ARGISNULL(4)) - scalex = PG_GETARG_FLOAT8(4); - - if (!PG_ARGISNULL(5)) - scaley = PG_GETARG_FLOAT8(5); - - if (!PG_ARGISNULL(6)) - skewx = PG_GETARG_FLOAT8(6); - - if (!PG_ARGISNULL(7)) - skewy = PG_GETARG_FLOAT8(7); - - if (!PG_ARGISNULL(8)) - srid = PG_GETARG_INT32(8); - - POSTGIS_RT_DEBUGF(4, "%dx%d, ip:%g,%g, scale:%g,%g, skew:%g,%g srid:%d", - width, height, ipx, ipy, scalex, scaley, - skewx, skewy, srid); - - raster = rt_raster_new(width, height); - if (raster == NULL) - PG_RETURN_NULL(); /* error was supposedly printed already */ - - rt_raster_set_scale(raster, scalex, scaley); - rt_raster_set_offsets(raster, ipx, ipy); - rt_raster_set_skews(raster, skewx, skewy); - rt_raster_set_srid(raster, srid); - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgraster) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); -} - -/** - * Return the SRID associated with the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getSRID); -Datum RASTER_getSRID(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - int32_t srid; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getSRID: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - srid = rt_raster_get_srid(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_INT32(srid); -} - -/** - * Set the SRID associated with the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setSRID); -Datum RASTER_setSRID(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - int32_t newSRID = PG_GETARG_INT32(1); - - 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_setSRID: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_srid(raster, newSRID); - - 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); -} - -/** - * Return the width of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getWidth); -Datum RASTER_getWidth(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - uint16_t width; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getWidth: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - width = rt_raster_get_width(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_INT32(width); -} - -/** - * Return the height of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getHeight); -Datum RASTER_getHeight(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - uint16_t height; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getHeight: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - height = rt_raster_get_height(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_INT32(height); -} - -/** - * Return the number of bands included in the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getNumBands); -Datum RASTER_getNumBands(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - int32_t num_bands; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getNumBands: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - num_bands = rt_raster_get_num_bands(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_INT32(num_bands); -} - -/** - * Return X scale from georeference of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getXScale); -Datum RASTER_getXScale(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double xsize; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getXScale: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - xsize = rt_raster_get_x_scale(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(xsize); -} - -/** - * Return Y scale from georeference of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getYScale); -Datum RASTER_getYScale(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double ysize; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getYScale: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - ysize = rt_raster_get_y_scale(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(ysize); -} - -/** - * Set the scale of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setScale); -Datum RASTER_setScale(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double size = PG_GETARG_FLOAT8(1); - - 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_setScale: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(raster, size, size); - - 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 the pixel size of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setScaleXY); -Datum RASTER_setScaleXY(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double xscale = PG_GETARG_FLOAT8(1); - double yscale = PG_GETARG_FLOAT8(2); - - 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_setScaleXY: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(raster, xscale, yscale); - 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); -} - -/** - * Return value of the raster skew about the X axis. - */ -PG_FUNCTION_INFO_V1(RASTER_getXSkew); -Datum RASTER_getXSkew(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double xskew; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getXSkew: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - xskew = rt_raster_get_x_skew(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(xskew); -} - -/** - * Return value of the raster skew about the Y axis. - */ -PG_FUNCTION_INFO_V1(RASTER_getYSkew); -Datum RASTER_getYSkew(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double yskew; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getYSkew: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - yskew = rt_raster_get_y_skew(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(yskew); -} - -/** - * Set the skew of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setSkew); -Datum RASTER_setSkew(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double skew = PG_GETARG_FLOAT8(1); - - 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_setSkew: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_skews(raster, skew, skew); - - 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 the skew of the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setSkewXY); -Datum RASTER_setSkewXY(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double xskew = PG_GETARG_FLOAT8(1); - double yskew = PG_GETARG_FLOAT8(2); - - 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_setSkewXY: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_skews(raster, xskew, yskew); - - 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); -} - -/** - * Return value of the raster offset in the X dimension. - */ -PG_FUNCTION_INFO_V1(RASTER_getXUpperLeft); -Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double xul; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getXUpperLeft: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - xul = rt_raster_get_x_offset(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(xul); -} - -/** - * Return value of the raster offset in the Y dimension. - */ -PG_FUNCTION_INFO_V1(RASTER_getYUpperLeft); -Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double yul; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getYUpperLeft: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - yul = rt_raster_get_y_offset(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(yul); -} - -/** - * Set the raster offset in the X and Y dimension. - */ -PG_FUNCTION_INFO_V1(RASTER_setUpperLeftXY); -Datum RASTER_setUpperLeftXY(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double xoffset = PG_GETARG_FLOAT8(1); - double yoffset = PG_GETARG_FLOAT8(2); - - 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_setUpperLeftXY: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_offsets(raster, xoffset, yoffset); - - 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); -} - -/** - * Return the pixel width of the raster. The pixel width is - * a read-only, dynamically computed value derived from the - * X Scale and the Y Skew. - * - * Pixel Width = sqrt( X Scale * X Scale + Y Skew * Y Skew ) - */ -PG_FUNCTION_INFO_V1(RASTER_getPixelWidth); -Datum RASTER_getPixelWidth(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double xscale; - double yskew; - double pwidth; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getPixelWidth: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - xscale = rt_raster_get_x_scale(raster); - yskew = rt_raster_get_y_skew(raster); - pwidth = sqrt(xscale*xscale + yskew*yskew); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(pwidth); -} - -/** - * Return the pixel height of the raster. The pixel height is - * a read-only, dynamically computed value derived from the - * Y Scale and the X Skew. - * - * Pixel Height = sqrt( Y Scale * Y Scale + X Skew * X Skew ) - */ -PG_FUNCTION_INFO_V1(RASTER_getPixelHeight); -Datum RASTER_getPixelHeight(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_raster raster; - double yscale; - double xskew; - double pheight; - - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getPixelHeight: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - yscale = rt_raster_get_y_scale(raster); - xskew = rt_raster_get_x_skew(raster); - pheight = sqrt(yscale*yscale + xskew*xskew); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(pheight); -} - -/** - * Set the geotransform of the supplied raster. Returns the raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setGeotransform); -Datum RASTER_setGeotransform(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - float8 imag, jmag, theta_i, theta_ij, xoffset, yoffset; - - if ( - PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || - PG_ARGISNULL(3) || PG_ARGISNULL(4) || - PG_ARGISNULL(5) || PG_ARGISNULL(6) - ) { - PG_RETURN_NULL(); - } - - /* get the inputs */ - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - imag = PG_GETARG_FLOAT8(1); - jmag = PG_GETARG_FLOAT8(2); - theta_i = PG_GETARG_FLOAT8(3); - theta_ij = PG_GETARG_FLOAT8(4); - xoffset = PG_GETARG_FLOAT8(5); - yoffset = PG_GETARG_FLOAT8(6); - - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_setGeotransform: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* store the new geotransform */ - rt_raster_set_phys_params(raster, imag,jmag,theta_i,theta_ij); - rt_raster_set_offsets(raster, xoffset, yoffset); - - /* prep the return value */ - 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); -} - -/** - * Calculates the physically relevant parameters of the supplied raster's - * geotransform. Returns them as a set. - */ -PG_FUNCTION_INFO_V1(RASTER_getGeotransform); -Datum RASTER_getGeotransform(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - - double imag; - double jmag; - double theta_i; - double theta_ij; - /* - double xoffset; - double yoffset; - */ - - TupleDesc result_tuple; /* for returning a composite */ - int values_length = 6; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple heap_tuple ; /* instance of the tuple to return */ - Datum result; - - POSTGIS_RT_DEBUG(3, "RASTER_getGeotransform: Starting"); - - /* get argument */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getGeotransform: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* do the calculation */ - rt_raster_calc_phys_params( - rt_raster_get_x_scale(raster), - rt_raster_get_x_skew(raster), - rt_raster_get_y_skew(raster), - rt_raster_get_y_scale(raster), - &imag, &jmag, &theta_i, &theta_ij) ; - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* setup the return value infrastructure */ - if (get_call_result_type(fcinfo, NULL, &result_tuple) != TYPEFUNC_COMPOSITE) { - ereport(ERROR, ( - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RASTER_getGeotransform(): function returning record called in context that cannot accept type record" - ) - )); - PG_RETURN_NULL(); - } - - BlessTupleDesc(result_tuple); - - /* get argument */ - /* prep the composite return value */ - /* construct datum array */ - values[0] = Float8GetDatum(imag); - values[1] = Float8GetDatum(jmag); - values[2] = Float8GetDatum(theta_i); - values[3] = Float8GetDatum(theta_ij); - values[4] = Float8GetDatum(rt_raster_get_x_offset(raster)); - values[5] = Float8GetDatum(rt_raster_get_y_offset(raster)); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* stick em on the heap */ - heap_tuple = heap_form_tuple(result_tuple, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(heap_tuple); - - PG_RETURN_DATUM(result); -} - - -/** - * Set the rotation of the raster. This method will change the X Scale, - * Y Scale, X Skew and Y Skew properties all at once to keep the rotations - * about the X and Y axis uniform. - * - * This method will set the rotation about the X axis and Y axis based on - * the pixel size. This pixel size may not be uniform if rasters have different - * skew values (the raster cells are diamond-shaped). If a raster has different - * skew values has a rotation set upon it, this method will remove the - * diamond distortions of the cells, as each axis will have the same rotation. - */ -PG_FUNCTION_INFO_V1(RASTER_setRotation); -Datum RASTER_setRotation(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster; - double rotation = PG_GETARG_FLOAT8(1); - double imag, jmag, theta_i, theta_ij; - - 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_setRotation: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* preserve all defining characteristics of the grid except for rotation */ - rt_raster_get_phys_params(raster, &imag, &jmag, &theta_i, &theta_ij); - rt_raster_set_phys_params(raster, imag, jmag, rotation, theta_ij); - - 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); -} - -/** - * Return pixel type of the specified band of raster. - * Band index is 1-based. - */ -PG_FUNCTION_INFO_V1(RASTER_getBandPixelType); -Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - rt_pixtype pixtype; - int32_t bandindex; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - raster = rt_raster_deserialize(pgraster, FALSE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getBandPixelType: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Fetch requested band and its pixel type */ - band = rt_raster_get_band(raster, bandindex - 1); - if ( ! band ) { - elog(NOTICE, "Could not find raster band of index %d when getting pixel type. Returning NULL", bandindex); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - pixtype = rt_band_get_pixtype(band); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_INT32(pixtype); -} - -/** - * Return name of pixel type of the specified band of raster. - * Band index is 1-based. - * NOTE: This is unofficial utility not included in the spec. - */ -PG_FUNCTION_INFO_V1(RASTER_getBandPixelTypeName); -Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - rt_pixtype pixtype; - int32_t bandindex; - const size_t name_size = 8; /* size of type name */ - size_t size = 0; - char *ptr = NULL; - text *result = NULL; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - raster = rt_raster_deserialize(pgraster, FALSE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getBandPixelTypeName: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Fetch requested band and its pixel type */ - band = rt_raster_get_band(raster, bandindex - 1); - if ( ! band ) { - elog(NOTICE, "Could not find raster band of index %d when getting pixel type name. Returning NULL", bandindex); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - pixtype = rt_band_get_pixtype(band); - - result = palloc(VARHDRSZ + name_size); - /* We don't need to check for NULL pointer, because if out of memory, palloc - * exit via elog(ERROR). It never returns NULL. - */ - - memset(VARDATA(result), 0, name_size); - ptr = (char *)result + VARHDRSZ; - strcpy(ptr, rt_pixtype_name(pixtype)); - - size = VARHDRSZ + strlen(ptr); - SET_VARSIZE(result, size); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_TEXT_P(result); -} - -/** - * Return nodata value of the specified band of raster. - * The value is always returned as FLOAT32 even if the pixel type is INTEGER. - */ -PG_FUNCTION_INFO_V1(RASTER_getBandNoDataValue); -Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex; - double nodata; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - raster = rt_raster_deserialize(pgraster, FALSE); - if ( ! raster ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_getBandNoDataValue: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Fetch requested band and its nodata value */ - band = rt_raster_get_band(raster, bandindex - 1); - if ( ! band ) { - elog(NOTICE, "Could not find raster band of index %d when getting band nodata value. Returning NULL", bandindex); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - if ( ! rt_band_get_hasnodata_flag(band) ) { - /* Raster does not have a nodata value set so we return NULL */ - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - rt_band_get_nodata(band, &nodata); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_FLOAT8(nodata); -} - - -/** - * Set the nodata value of the specified band of raster. - */ -PG_FUNCTION_INFO_V1(RASTER_setBandNoDataValue); -Datum RASTER_setBandNoDataValue(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - double nodata; - int32_t bandindex; - bool forcechecking = FALSE; - bool skipset = FALSE; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* Check index is not NULL or smaller than 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). Nodata value not set. Returning original raster"); - skipset = TRUE; - } - - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_setBandNoDataValue: 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. Nodata value not set. Returning original raster", bandindex); - } - else { - if (!PG_ARGISNULL(3)) - forcechecking = PG_GETARG_BOOL(3); - - if (PG_ARGISNULL(2)) { - /* Set the hasnodata flag to FALSE */ - rt_band_set_hasnodata_flag(band, FALSE); - POSTGIS_RT_DEBUGF(3, "Raster band %d does not have a nodata value", bandindex); - } - else { - /* Get the nodata value */ - nodata = PG_GETARG_FLOAT8(2); - - /* Set the band's nodata value */ - rt_band_set_nodata(band, nodata, NULL); - - /* Recheck all pixels if requested */ - if (forcechecking) - rt_band_check_is_nodata(band); - } - } - } - - 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); -} - -PG_FUNCTION_INFO_V1(RASTER_setBandIsNoData); -Datum RASTER_setBandIsNoData(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex; - - 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_setBandIsNoData: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Check index is not NULL or smaller than 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). Isnodata flag not set. Returning original raster"); - else { - /* Fetch requested band */ - band = rt_raster_get_band(raster, bandindex - 1); - - if (!band) - elog(NOTICE, "Could not find raster band of index %d. Isnodata flag not set. Returning original raster", bandindex); - else { - if (!rt_band_get_hasnodata_flag(band)) { - elog(NOTICE, "Band of index %d has no NODATA so cannot be NODATA. Returning original raster", bandindex); - } - /* Set the band's nodata value */ - else { - rt_band_set_isnodata_flag(band, 1); - } - } - } - - /* Serialize raster again */ - 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); -} - -PG_FUNCTION_INFO_V1(RASTER_bandIsNoData); -Datum RASTER_bandIsNoData(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex; - bool forcechecking = FALSE; - bool bandisnodata = FALSE; - - /* 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(); - } - - /* 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_bandIsNoData: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Fetch requested band and its nodata value */ - band = rt_raster_get_band(raster, bandindex - 1); - if ( ! band ) { - elog(NOTICE, "Could not find raster band of index %d when determining if band is nodata. Returning NULL", bandindex); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - forcechecking = PG_GETARG_BOOL(2); - - bandisnodata = (forcechecking) ? - rt_band_check_is_nodata(band) : rt_band_get_isnodata_flag(band); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_BOOL(bandisnodata); -} - -/** - * Return the path of the raster for out-db raster. - */ -PG_FUNCTION_INFO_V1(RASTER_getBandPath); -Datum RASTER_getBandPath(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex; - const char *bandpath; - text *result; - - /* 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(); - } - - /* 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_getBandPath: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* Fetch requested band and its nodata value */ - band = rt_raster_get_band(raster, bandindex - 1); - if ( ! band ) { - elog(NOTICE, "Could not find raster band of index %d when getting band path. Returning NULL", bandindex); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - bandpath = rt_band_get_ext_path(band); - rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if ( ! bandpath ) - { - PG_RETURN_NULL(); - } - - result = (text *) palloc(VARHDRSZ + strlen(bandpath) + 1); - - SET_VARSIZE(result, VARHDRSZ + strlen(bandpath) + 1); - - strcpy((char *) VARDATA(result), bandpath); - - PG_RETURN_TEXT_P(result); -} - - -/** - * 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); -} - -/* ---------------------------------------------------------------- */ -/* 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) { - if (arg->nbands != NULL) - pfree(arg->nbands); - - 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]); - } - - if (arg->values != NULL) - pfree(arg->values); - if (arg->nodata != NULL) - pfree(arg->nodata); - } - - pfree(arg); -} - -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); - } - } - - } - 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++) { - 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) { - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - ArrayType *mdValues = NULL; - 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); - - /* assemble 3-dimension array of values */ - mdValues = construct_md_array( - arg2->values[call_cntr], arg2->nodata[call_cntr], - 2, 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; - int 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; - unsigned char *wkb = NULL; - size_t wkb_len; - - int i = 0; - int 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 intialize 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(arg->gv[arg->ngv].geom, WKB_SFSQL, &wkb_len); - - /* rasterize geometry */ - arg->gv[arg->ngv].mask = rt_raster_gdal_rasterize( - wkb, wkb_len, - 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, - 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); -} - -/** - * Return the geographical shape of all pixels - */ -PG_FUNCTION_INFO_V1(RASTER_getPixelPolygons); -Datum RASTER_getPixelPolygons(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - rt_pixel pix = NULL; - rt_pixel pix2; - int call_cntr; - int max_calls; - int i = 0; - - /* 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 nband = 1; - int numbands; - bool noband = FALSE; - bool exclude_nodata_value = TRUE; - bool nocolumnx = FALSE; - bool norowy = FALSE; - int x = 0; - int y = 0; - int bounds[4] = {0}; - int pixcount = 0; - int isnodata = 0; - - LWPOLY *poly; - - /* 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)); - - /* band */ - if (PG_ARGISNULL(1)) - noband = TRUE; - else { - nband = PG_GETARG_INT32(1); - noband = FALSE; - } - - /* column */ - if (PG_ARGISNULL(2)) - nocolumnx = TRUE; - else { - bounds[0] = PG_GETARG_INT32(2); - bounds[1] = bounds[0]; - } - - /* row */ - if (PG_ARGISNULL(3)) - norowy = TRUE; - else { - bounds[2] = PG_GETARG_INT32(3); - bounds[3] = bounds[2]; - } - - /* exclude NODATA */ - if (!PG_ARGISNULL(4)) - exclude_nodata_value = PG_GETARG_BOOL(4); - - 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); - } - - /* raster empty, return NULL */ - if (rt_raster_is_empty(raster)) { - elog(NOTICE, "Raster is empty. Returning NULL"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* band specified, load band and info */ - if (!noband) { - do { - numbands = rt_raster_get_num_bands(raster); - POSTGIS_RT_DEBUGF(3, "band %d", nband); - POSTGIS_RT_DEBUGF(3, "# of bands %d", numbands); - - if (nband < 1 || nband > numbands) { - elog(NOTICE, "Invalid band index (must use 1-based). Returning pixel values will be NULL"); - noband = TRUE; - break; - } - - band = rt_raster_get_band(raster, nband - 1); - if (!band) { - elog(NOTICE, "Could not find band at index %d. Returning pixel values will be NULL", nband); - noband = TRUE; - break; - } - - if (!rt_band_get_hasnodata_flag(band)) - exclude_nodata_value = FALSE; - } - while (0); - } - - /* set bounds if columnx, rowy not set */ - if (nocolumnx) { - bounds[0] = 1; - bounds[1] = rt_raster_get_width(raster); - } - if (norowy) { - bounds[2] = 1; - bounds[3] = rt_raster_get_height(raster); - } - POSTGIS_RT_DEBUGF(3, "bounds (min x, max x, min y, max y) = (%d, %d, %d, %d)", - bounds[0], bounds[1], bounds[2], bounds[3]); - - /* rowy */ - pixcount = 0; - for (y = bounds[2]; y <= bounds[3]; y++) { - /* columnx */ - for (x = bounds[0]; x <= bounds[1]; x++) { - /* geometry */ - poly = rt_raster_pixel_as_polygon(raster, x - 1, y - 1); - if (!poly) { - for (i = 0; i < pixcount; i++) - lwgeom_free(pix[i].geom); - if (pixcount) pfree(pix); - - if (!noband) rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_getPixelPolygons: Could not get pixel polygon"); - SRF_RETURN_DONE(funcctx); - } - - if (!pixcount) - pix = palloc(sizeof(struct rt_pixel_t) * (pixcount + 1)); - else - pix = repalloc(pix, sizeof(struct rt_pixel_t) * (pixcount + 1)); - if (pix == NULL) { - - lwpoly_free(poly); - if (!noband) rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_getPixelPolygons: Could not allocate memory for storing pixel polygons"); - SRF_RETURN_DONE(funcctx); - } - pix[pixcount].geom = (LWGEOM *) poly; - /* - POSTGIS_RT_DEBUGF(4, "poly @ %p", poly); - POSTGIS_RT_DEBUGF(4, "geom @ %p", pix[pixcount].geom); - */ - - /* x, y */ - pix[pixcount].x = x; - pix[pixcount].y = y; - - /* value, NODATA flag */ - if (!noband) { - if (rt_band_get_pixel(band, x - 1, y - 1, &(pix[pixcount].value), &isnodata) != ES_NONE) { - - for (i = 0; i < pixcount; i++) - lwgeom_free(pix[i].geom); - if (pixcount) pfree(pix); - - if (!noband) rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_getPixelPolygons: Could not get pixel value"); - SRF_RETURN_DONE(funcctx); - } - - if (!exclude_nodata_value || !isnodata) { - pix[pixcount].nodata = 0; - } - else { - pix[pixcount].nodata = 1; - } - } - else { - pix[pixcount].nodata = 1; - } - - pixcount++; - } - } - - if (!noband) rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Store needed information */ - funcctx->user_fctx = pix; - - /* total number of tuples to be returned */ - funcctx->max_calls = pixcount; - - /* 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; - pix2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 4; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - GSERIALIZED *gser = NULL; - size_t gser_size = 0; - - POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* convert LWGEOM to GSERIALIZED */ - gser = gserialized_from_lwgeom(pix2[call_cntr].geom, 0, &gser_size); - lwgeom_free(pix2[call_cntr].geom); - - values[0] = PointerGetDatum(gser); - if (pix2[call_cntr].nodata) - nulls[1] = TRUE; - else - values[1] = Float8GetDatum(pix2[call_cntr].value); - values[2] = Int32GetDatum(pix2[call_cntr].x); - values[3] = Int32GetDatum(pix2[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); - } - /* do when there is no more left */ - else { - pfree(pix2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Get raster band's polygon - */ -PG_FUNCTION_INFO_V1(RASTER_getPolygon); -Datum RASTER_getPolygon(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - int num_bands = 0; - int nband = 1; - int err; - LWMPOLY *surface = NULL; - GSERIALIZED *rtn = NULL; - - /* 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_getPolygon: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* 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); - PG_RETURN_NULL(); - } - - /* 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); - PG_RETURN_NULL(); - } - - /* get band surface */ - err = rt_raster_surface(raster, nband - 1, &surface); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - if (err != ES_NONE) { - elog(ERROR, "RASTER_getPolygon: Could not get raster band's surface"); - PG_RETURN_NULL(); - } - else if (surface == NULL) { - elog(NOTICE, "Raster is empty or all pixels of band are NODATA. Returning NULL"); - PG_RETURN_NULL(); - } - - rtn = geometry_serialize(lwmpoly_as_lwgeom(surface)); - lwmpoly_free(surface); - - PG_RETURN_POINTER(rtn); -} - -/** - * 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) { - int values_length = 3; - 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 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(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, - _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); -} - -/** - * Add band(s) to the given raster at the given position(s). - */ -PG_FUNCTION_INFO_V1(RASTER_addBand); -Datum RASTER_addBand(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - int bandindex = 0; - int maxbandindex = 0; - int numbands = 0; - int lastnumbands = 0; - - text *text_pixtype = NULL; - char *char_pixtype = NULL; - - struct addbandarg { - int index; - bool append; - rt_pixtype pixtype; - double initialvalue; - bool hasnodata; - double nodatavalue; - }; - struct addbandarg *arg = NULL; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - - HeapTupleHeader tup; - bool isnull; - Datum tupv; - - int i = 0; - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* process set of addbandarg */ - POSTGIS_RT_DEBUG(3, "Processing Arg 1 (addbandargset)"); - array = PG_GETARG_ARRAYTYPE_P(1); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (!n) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset"); - PG_RETURN_NULL(); - } - - /* allocate addbandarg */ - arg = (struct addbandarg *) palloc(sizeof(struct addbandarg) * n); - if (arg == NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Could not allocate memory for addbandarg"); - PG_RETURN_NULL(); - } - - /* - process each element of addbandargset - each element is the index of where to add the new band, - new band's pixeltype, the new band's initial value and - the new band's NODATA value if NOT NULL - */ - for (i = 0; i < n; i++) { - if (nulls[i]) continue; - - POSTGIS_RT_DEBUGF(4, "Processing addbandarg at index %d", i); - - /* each element is a tuple */ - tup = (HeapTupleHeader) DatumGetPointer(e[i]); - if (NULL == tup) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset"); - PG_RETURN_NULL(); - } - - /* new band index, 1-based */ - arg[i].index = 0; - arg[i].append = TRUE; - tupv = GetAttributeByName(tup, "index", &isnull); - if (!isnull) { - arg[i].index = DatumGetInt32(tupv); - arg[i].append = FALSE; - } - - /* for now, only check that band index is 1-based */ - if (!arg[i].append && arg[i].index < 1) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Invalid band index (must be 1-based) for addbandarg of index %d", i); - PG_RETURN_NULL(); - } - - /* new band pixeltype */ - arg[i].pixtype = PT_END; - tupv = GetAttributeByName(tup, "pixeltype", &isnull); - if (isnull) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Pixel type cannot be NULL for addbandarg of index %d", i); - PG_RETURN_NULL(); - } - text_pixtype = (text *) DatumGetPointer(tupv); - if (text_pixtype == NULL) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Pixel type cannot be NULL for addbandarg of index %d", i); - PG_RETURN_NULL(); - } - char_pixtype = text_to_cstring(text_pixtype); - - arg[i].pixtype = rt_pixtype_index_from_name(char_pixtype); - pfree(char_pixtype); - if (arg[i].pixtype == PT_END) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Invalid pixel type for addbandarg of index %d", i); - PG_RETURN_NULL(); - } - - /* new band initialvalue */ - arg[i].initialvalue = 0; - tupv = GetAttributeByName(tup, "initialvalue", &isnull); - if (!isnull) - arg[i].initialvalue = DatumGetFloat8(tupv); - - /* new band NODATA value */ - arg[i].hasnodata = FALSE; - arg[i].nodatavalue = 0; - tupv = GetAttributeByName(tup, "nodataval", &isnull); - if (!isnull) { - arg[i].hasnodata = TRUE; - arg[i].nodatavalue = DatumGetFloat8(tupv); - } - } - - /* add new bands to raster */ - lastnumbands = rt_raster_get_num_bands(raster); - for (i = 0; i < n; i++) { - if (nulls[i]) continue; - - POSTGIS_RT_DEBUGF(3, "%d bands in old raster", lastnumbands); - maxbandindex = lastnumbands + 1; - - /* check that new band's index doesn't exceed maxbandindex */ - if (!arg[i].append) { - if (arg[i].index > maxbandindex) { - elog(NOTICE, "Band index for addbandarg of index %d exceeds possible value. Adding band at index %d", i, maxbandindex); - arg[i].index = maxbandindex; - } - } - /* append, so use maxbandindex */ - else - arg[i].index = maxbandindex; - - POSTGIS_RT_DEBUGF(4, "new band (index, pixtype, initialvalue, hasnodata, nodatavalue) = (%d, %s, %f, %s, %f)", - arg[i].index, - rt_pixtype_name(arg[i].pixtype), - arg[i].initialvalue, - arg[i].hasnodata ? "TRUE" : "FALSE", - arg[i].nodatavalue - ); - - bandindex = rt_raster_generate_new_band( - raster, - arg[i].pixtype, arg[i].initialvalue, - arg[i].hasnodata, arg[i].nodatavalue, - arg[i].index - 1 - ); - - numbands = rt_raster_get_num_bands(raster); - if (numbands == lastnumbands || bandindex == -1) { - pfree(arg); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBand: Could not add band defined by addbandarg of index %d to raster", i); - PG_RETURN_NULL(); - } - - lastnumbands = numbands; - POSTGIS_RT_DEBUGF(3, "%d bands in new raster", lastnumbands); - } - - pfree(arg); - - 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); -} - -/** - * Add bands from array of rasters to a destination raster - */ -PG_FUNCTION_INFO_V1(RASTER_addBandRasterArray); -Datum RASTER_addBandRasterArray(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgsrc = NULL; - rt_pgraster *pgrtn = NULL; - - rt_raster raster = NULL; - rt_raster src = NULL; - - int srcnband = 1; - bool appendband = FALSE; - int dstnband = 1; - int srcnumbands = 0; - int dstnumbands = 0; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - - int rtn = 0; - int i = 0; - - /* destination raster */ - if (!PG_ARGISNULL(0)) { - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandRasterArray: Could not deserialize destination raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUG(4, "destination raster isn't NULL"); - } - - /* source rasters' band index, 1-based */ - if (!PG_ARGISNULL(2)) - srcnband = PG_GETARG_INT32(2); - if (srcnband < 1) { - elog(NOTICE, "Invalid band index for source rasters (must be 1-based). Returning original raster"); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - else - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(4, "srcnband = %d", srcnband); - - /* destination raster's band index, 1-based */ - if (!PG_ARGISNULL(3)) { - dstnband = PG_GETARG_INT32(3); - appendband = FALSE; - - if (dstnband < 1) { - elog(NOTICE, "Invalid band index for destination raster (must be 1-based). Returning original raster"); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - else - PG_RETURN_NULL(); - } - } - else - appendband = TRUE; - - /* additional processing of dstnband */ - if (raster != NULL) { - dstnumbands = rt_raster_get_num_bands(raster); - - if (dstnumbands < 1) { - appendband = TRUE; - dstnband = 1; - } - else if (appendband) - dstnband = dstnumbands + 1; - else if (dstnband > dstnumbands) { - elog(NOTICE, "Band index provided for destination raster is greater than the number of bands in the raster. Bands will be appended"); - appendband = TRUE; - dstnband = dstnumbands + 1; - } - } - POSTGIS_RT_DEBUGF(4, "appendband = %d", appendband); - POSTGIS_RT_DEBUGF(4, "dstnband = %d", dstnband); - - /* process set of source rasters */ - POSTGIS_RT_DEBUG(3, "Processing array of source rasters"); - array = PG_GETARG_ARRAYTYPE_P(1); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - /* decrement srcnband and dstnband by 1, now 0-based */ - srcnband--; - dstnband--; - POSTGIS_RT_DEBUGF(4, "0-based nband (src, dst) = (%d, %d)", srcnband, dstnband); - - /* time to copy bands */ - for (i = 0; i < n; i++) { - if (nulls[i]) continue; - src = NULL; - - pgsrc = (rt_pgraster *) PG_DETOAST_DATUM(e[i]); - src = rt_raster_deserialize(pgsrc, FALSE); - if (src == NULL) { - pfree(nulls); - pfree(e); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandRasterArray: Could not deserialize source raster at index %d", i + 1); - PG_RETURN_NULL(); - } - - srcnumbands = rt_raster_get_num_bands(src); - POSTGIS_RT_DEBUGF(4, "source raster %d has %d bands", i + 1, srcnumbands); - - /* band index isn't valid */ - if (srcnband > srcnumbands - 1) { - elog(NOTICE, "Invalid band index for source raster at index %d. Returning original raster", i + 1); - pfree(nulls); - pfree(e); - rt_raster_destroy(src); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - else - PG_RETURN_NULL(); - } - - /* destination raster is empty, new raster */ - if (raster == NULL) { - uint32_t srcnbands[1] = {srcnband}; - - POSTGIS_RT_DEBUG(4, "empty destination raster, using rt_raster_from_band"); - - raster = rt_raster_from_band(src, srcnbands, 1); - rt_raster_destroy(src); - if (raster == NULL) { - pfree(nulls); - pfree(e); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandRasterArray: Could not create raster from source raster at index %d", i + 1); - PG_RETURN_NULL(); - } - } - /* copy band */ - else { - rtn = rt_raster_copy_band( - raster, src, - srcnband, dstnband - ); - rt_raster_destroy(src); - - if (rtn == -1 || rt_raster_get_num_bands(raster) == dstnumbands) { - elog(NOTICE, "Could not add band from source raster at index %d to destination raster. Returning original raster", i + 1); - rt_raster_destroy(raster); - pfree(nulls); - pfree(e); - if (pgraster != NULL) - PG_RETURN_POINTER(pgraster); - else - PG_RETURN_NULL(); - } - } - - dstnband++; - dstnumbands++; - } - - if (raster != NULL) { - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - if (!pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - PG_RETURN_NULL(); -} - -/** - * Add out-db band to the given raster at the given position - */ -PG_FUNCTION_INFO_V1(RASTER_addBandOutDB); -Datum RASTER_addBandOutDB(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - - rt_raster raster = NULL; - rt_band band = NULL; - int numbands = 0; - int dstnband = 1; /* 1-based */ - int appendband = FALSE; - char *outdbfile = NULL; - int *srcnband = NULL; /* 1-based */ - int numsrcnband = 0; - int allbands = FALSE; - int hasnodata = FALSE; - double nodataval = 0.; - uint16_t width = 0; - uint16_t height = 0; - char *authname = NULL; - char *authcode = NULL; - - int i = 0; - int j = 0; - - GDALDatasetH hdsOut; - GDALRasterBandH hbandOut; - GDALDataType gdpixtype; - - rt_pixtype pt = PT_END; - double gt[6] = {0.}; - double ogt[6] = {0.}; - rt_raster _rast = NULL; - int aligned = 0; - int err = 0; - - /* destination raster */ - if (!PG_ARGISNULL(0)) { - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not deserialize destination raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUG(4, "destination raster isn't NULL"); - } - - /* destination band index (1) */ - if (!PG_ARGISNULL(1)) - dstnband = PG_GETARG_INT32(1); - else - appendband = TRUE; - - /* outdb file (2) */ - if (PG_ARGISNULL(2)) { - elog(NOTICE, "Out-db raster file not provided. Returning original raster"); - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - else - PG_RETURN_NULL(); - } - else { - outdbfile = text_to_cstring(PG_GETARG_TEXT_P(2)); - if (!strlen(outdbfile)) { - elog(NOTICE, "Out-db raster file not provided. Returning original raster"); - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - else - PG_RETURN_NULL(); - } - } - - /* outdb band index (3) */ - if (!PG_ARGISNULL(3)) { - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - - int16 typlen; - bool typbyval; - char typalign; - - allbands = FALSE; - - array = PG_GETARG_ARRAYTYPE_P(3); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case INT2OID: - case INT4OID: - break; - default: - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - } - elog(ERROR, "RASTER_addBandOutDB: Invalid data type for band indexes"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, &nulls, &numsrcnband); - - srcnband = palloc(sizeof(int) * numsrcnband); - if (srcnband == NULL) { - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - } - elog(ERROR, "RASTER_addBandOutDB: Could not allocate memory for band indexes"); - PG_RETURN_NULL(); - } - - for (i = 0, j = 0; i < numsrcnband; i++) { - if (nulls[i]) continue; - - switch (etype) { - case INT2OID: - srcnband[j] = DatumGetInt16(e[i]); - break; - case INT4OID: - srcnband[j] = DatumGetInt32(e[i]); - break; - } - j++; - } - - if (j < numsrcnband) { - srcnband = repalloc(srcnband, sizeof(int) * j); - if (srcnband == NULL) { - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - } - elog(ERROR, "RASTER_addBandOutDB: Could not reallocate memory for band indexes"); - PG_RETURN_NULL(); - } - - numsrcnband = j; - } - } - else - allbands = TRUE; - - /* nodataval (4) */ - if (!PG_ARGISNULL(4)) { - hasnodata = TRUE; - nodataval = PG_GETARG_FLOAT8(4); - } - else - hasnodata = FALSE; - - /* validate input */ - - /* make sure dstnband is valid */ - if (raster != NULL) { - numbands = rt_raster_get_num_bands(raster); - if (!appendband) { - if (dstnband < 1) { - elog(NOTICE, "Invalid band index %d for adding bands. Using band index 1", dstnband); - dstnband = 1; - } - else if (dstnband > numbands) { - elog(NOTICE, "Invalid band index %d for adding bands. Using band index %d", dstnband, numbands); - dstnband = numbands + 1; - } - } - else - dstnband = numbands + 1; - } - - /* open outdb raster file */ - rt_util_gdal_register_all(); - hdsOut = GDALOpenShared(outdbfile, GA_ReadOnly); - if (hdsOut == NULL) { - if (pgraster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - } - elog(ERROR, "RASTER_addBandOutDB: Could not open out-db file with GDAL"); - PG_RETURN_NULL(); - } - - /* get offline raster's geotransform */ - if (GDALGetGeoTransform(hdsOut, ogt) != CE_None) { - ogt[0] = 0; - ogt[1] = 1; - ogt[2] = 0; - ogt[3] = 0; - ogt[4] = 0; - ogt[5] = -1; - } - - /* raster doesn't exist, create it now */ - if (raster == NULL) { - raster = rt_raster_new(GDALGetRasterXSize(hdsOut), GDALGetRasterYSize(hdsOut)); - if (rt_raster_is_empty(raster)) { - elog(ERROR, "RASTER_addBandOutDB: Could not create new raster"); - PG_RETURN_NULL(); - } - rt_raster_set_geotransform_matrix(raster, ogt); - rt_raster_get_geotransform_matrix(raster, gt); - - if (rt_util_gdal_sr_auth_info(hdsOut, &authname, &authcode) == ES_NONE) { - if ( - authname != NULL && - strcmp(authname, "EPSG") == 0 && - authcode != NULL - ) { - rt_raster_set_srid(raster, atoi(authcode)); - } - else - elog(INFO, "Unknown SRS auth name and code from out-db file. Defaulting SRID of new raster to %d", SRID_UNKNOWN); - } - else - elog(INFO, "Could not get SRS auth name and code from out-db file. Defaulting SRID of new raster to %d", SRID_UNKNOWN); - } - - /* some raster info */ - width = rt_raster_get_width(raster); - height = rt_raster_get_width(raster); - - /* are rasters aligned? */ - _rast = rt_raster_new(1, 1); - rt_raster_set_geotransform_matrix(_rast, ogt); - rt_raster_set_srid(_rast, rt_raster_get_srid(raster)); - err = rt_raster_same_alignment(raster, _rast, &aligned, NULL); - rt_raster_destroy(_rast); - - if (err != ES_NONE) { - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not test alignment of out-db file"); - return ES_ERROR; - } - else if (!aligned) - elog(WARNING, "The in-db representation of the out-db raster is not aligned. Band data may be incorrect"); - - numbands = GDALGetRasterCount(hdsOut); - - /* build up srcnband */ - if (allbands) { - numsrcnband = numbands; - srcnband = palloc(sizeof(int) * numsrcnband); - if (srcnband == NULL) { - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not allocate memory for band indexes"); - PG_RETURN_NULL(); - } - - for (i = 0, j = 1; i < numsrcnband; i++, j++) - srcnband[i] = j; - } - - /* check band properties and add band */ - for (i = 0, j = dstnband - 1; i < numsrcnband; i++, j++) { - /* valid index? */ - if (srcnband[i] < 1 || srcnband[i] > numbands) { - elog(NOTICE, "Out-db file does not have a band at index %d. Returning original raster", srcnband[i]); - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_RETURN_POINTER(pgraster); - else - PG_RETURN_NULL(); - } - - /* get outdb band */ - hbandOut = NULL; - hbandOut = GDALGetRasterBand(hdsOut, srcnband[i]); - if (NULL == hbandOut) { - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not get band %d from GDAL dataset", srcnband[i]); - PG_RETURN_NULL(); - } - - /* supported pixel type */ - gdpixtype = GDALGetRasterDataType(hbandOut); - pt = rt_util_gdal_datatype_to_pixtype(gdpixtype); - if (pt == PT_END) { - elog(NOTICE, "Pixel type %s of band %d from GDAL dataset is not supported. Returning original raster", GDALGetDataTypeName(gdpixtype), srcnband[i]); - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_RETURN_POINTER(pgraster); - else - PG_RETURN_NULL(); - } - - /* use out-db band's nodata value if nodataval not already set */ - if (!hasnodata) - nodataval = GDALGetRasterNoDataValue(hbandOut, &hasnodata); - - /* add band */ - band = rt_band_new_offline( - width, height, - pt, - hasnodata, nodataval, - srcnband[i] - 1, outdbfile - ); - if (band == NULL) { - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not create new out-db band"); - PG_RETURN_NULL(); - } - - if (rt_raster_add_band(raster, band, j) < 0) { - GDALClose(hdsOut); - if (raster != NULL) - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_addBandOutDB: Could not add new out-db band to raster"); - PG_RETURN_NULL(); - } - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (pgraster != NULL) - PG_FREE_IF_COPY(pgraster, 0); - if (!pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/** - * Break up a raster into smaller tiles. SRF function - */ -PG_FUNCTION_INFO_V1(RASTER_tile); -Datum RASTER_tile(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - int call_cntr; - int max_calls; - int i = 0; - int j = 0; - - struct tile_arg_t { - - struct { - rt_raster raster; - double gt[6]; - int srid; - int width; - int height; - } raster; - - struct { - int width; - int height; - - int nx; - int ny; - } tile; - - int numbands; - int *nbands; - - struct { - int pad; - double hasnodata; - double nodataval; - } pad; - }; - struct tile_arg_t *arg1 = NULL; - struct tile_arg_t *arg2 = NULL; - - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - rt_pgraster *pgraster = NULL; - int numbands; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - - int16 typlen; - bool typbyval; - char typalign; - - POSTGIS_RT_DEBUG(2, "RASTER_tile: 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); - } - - /* allocate arg1 */ - arg1 = palloc(sizeof(struct tile_arg_t)); - if (arg1 == NULL) { - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_tile: Could not allocate memory for arguments"); - SRF_RETURN_DONE(funcctx); - } - - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)); - arg1->raster.raster = rt_raster_deserialize(pgraster, FALSE); - if (!arg1->raster.raster) { - ereport(ERROR, ( - errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("Could not deserialize raster") - )); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* raster has bands */ - numbands = rt_raster_get_num_bands(arg1->raster.raster); - /* - if (!numbands) { - elog(NOTICE, "Raster provided has no bands"); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - */ - - /* width (1) */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Width cannot be NULL. Returning NULL"); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - arg1->tile.width = PG_GETARG_INT32(1); - if (arg1->tile.width < 1) { - elog(NOTICE, "Width must be greater than zero. Returning NULL"); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* height (2) */ - if (PG_ARGISNULL(2)) { - elog(NOTICE, "Height cannot be NULL. Returning NULL"); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - arg1->tile.height = PG_GETARG_INT32(2); - if (arg1->tile.height < 1) { - elog(NOTICE, "Height must be greater than zero. Returning NULL"); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* nband, array (3) */ - if (numbands && !PG_ARGISNULL(3)) { - array = PG_GETARG_ARRAYTYPE_P(3); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case INT2OID: - case INT4OID: - break; - default: - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_tile: 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) { - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_tile: 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) { - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_tile: 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(arg1->raster.raster, arg1->nbands[i])) { - elog(NOTICE, "Band at index %d not found in raster", arg1->nbands[i] + 1); - rt_raster_destroy(arg1->raster.raster); - pfree(arg1->nbands); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - } - } - else { - arg1->numbands = numbands; - - if (numbands) { - arg1->nbands = palloc(sizeof(int) * arg1->numbands); - - if (arg1->nbands == NULL) { - rt_raster_destroy(arg1->raster.raster); - pfree(arg1); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_dumpValues: Could not allocate memory for pixel values"); - 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); - } - } - } - - /* pad (4) and padnodata (5) */ - if (!PG_ARGISNULL(4)) { - arg1->pad.pad = PG_GETARG_BOOL(4) ? 1 : 0; - - if (arg1->pad.pad && !PG_ARGISNULL(5)) { - arg1->pad.hasnodata = 1; - arg1->pad.nodataval = PG_GETARG_FLOAT8(5); - } - else { - arg1->pad.hasnodata = 0; - arg1->pad.nodataval = 0; - } - } - else { - arg1->pad.pad = 0; - arg1->pad.hasnodata = 0; - arg1->pad.nodataval = 0; - } - - /* store some additional metadata */ - arg1->raster.srid = rt_raster_get_srid(arg1->raster.raster); - arg1->raster.width = rt_raster_get_width(arg1->raster.raster); - arg1->raster.height = rt_raster_get_height(arg1->raster.raster); - rt_raster_get_geotransform_matrix(arg1->raster.raster, arg1->raster.gt); - - /* determine maximum number of tiles from raster */ - arg1->tile.nx = ceil(arg1->raster.width / (double) arg1->tile.width); - arg1->tile.ny = ceil(arg1->raster.height / (double) arg1->tile.height); - POSTGIS_RT_DEBUGF(4, "# of tiles (x, y) = (%d, %d)", arg1->tile.nx, arg1->tile.ny); - - /* Store needed information */ - funcctx->user_fctx = arg1; - - /* total number of tuples to be returned */ - funcctx->max_calls = (arg1->tile.nx * arg1->tile.ny); - - MemoryContextSwitchTo(oldcontext); - } - - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); - - call_cntr = funcctx->call_cntr; - max_calls = funcctx->max_calls; - arg2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - rt_pgraster *pgtile = NULL; - rt_raster tile = NULL; - rt_band _band = NULL; - rt_band band = NULL; - rt_pixtype pixtype = PT_END; - int hasnodata = 0; - double nodataval = 0; - int width = 0; - int height = 0; - - int k = 0; - int tx = 0; - int ty = 0; - int rx = 0; - int ry = 0; - int ex = 0; /* edge tile on right */ - int ey = 0; /* edge tile on bottom */ - double ulx = 0; - double uly = 0; - uint16_t len = 0; - void *vals = NULL; - uint16_t nvals; - - POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); - - /* - find offset based upon tile # - - 0 1 2 - 3 4 5 - 6 7 8 - */ - ty = call_cntr / arg2->tile.nx; - tx = call_cntr % arg2->tile.nx; - POSTGIS_RT_DEBUGF(4, "tile (x, y) = (%d, %d)", tx, ty); - - /* edge tile? only important if padding is false */ - if (!arg2->pad.pad) { - if (ty + 1 == arg2->tile.ny) - ey = 1; - if (tx + 1 == arg2->tile.nx) - ex = 1; - } - - /* upper-left of tile in raster coordinates */ - rx = tx * arg2->tile.width; - ry = ty * arg2->tile.height; - POSTGIS_RT_DEBUGF(4, "raster coordinates = %d, %d", rx, ry); - - /* determine tile width and height */ - /* default to user-defined */ - width = arg2->tile.width; - height = arg2->tile.height; - - /* override user-defined if edge tile (only possible if padding is false */ - if (ex || ey) { - /* right edge */ - if (ex) - width = arg2->raster.width - rx; - /* bottom edge */ - if (ey) - height = arg2->raster.height - ry; - } - - /* create empty raster */ - tile = rt_raster_new(width, height); - rt_raster_set_geotransform_matrix(tile, arg2->raster.gt); - rt_raster_set_srid(tile, arg2->raster.srid); - - /* upper-left of tile in spatial coordinates */ - if (rt_raster_cell_to_geopoint(arg2->raster.raster, rx, ry, &ulx, &uly, arg2->raster.gt) != ES_NONE) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not compute the coordinates of the upper-left corner of the output tile"); - SRF_RETURN_DONE(funcctx); - } - rt_raster_set_offsets(tile, ulx, uly); - POSTGIS_RT_DEBUGF(4, "spatial coordinates = %f, %f", ulx, uly); - - /* compute length of pixel line to read */ - len = arg2->tile.width; - if (rx + arg2->tile.width >= arg2->raster.width) - len = arg2->raster.width - rx; - POSTGIS_RT_DEBUGF(3, "read line len = %d", len); - - /* copy bands to tile */ - for (i = 0; i < arg2->numbands; i++) { - POSTGIS_RT_DEBUGF(4, "copying band %d to tile %d", arg2->nbands[i], call_cntr); - - _band = rt_raster_get_band(arg2->raster.raster, arg2->nbands[i]); - if (_band == NULL) { - int nband = arg2->nbands[i] + 1; - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not get band %d from source raster", nband); - SRF_RETURN_DONE(funcctx); - } - - pixtype = rt_band_get_pixtype(_band); - hasnodata = rt_band_get_hasnodata_flag(_band); - if (hasnodata) - rt_band_get_nodata(_band, &nodataval); - else if (arg2->pad.pad && arg2->pad.hasnodata) { - hasnodata = 1; - nodataval = arg2->pad.nodataval; - } - else - nodataval = rt_band_get_min_value(_band); - - /* inline band */ - if (!rt_band_is_offline(_band)) { - if (rt_raster_generate_new_band(tile, pixtype, nodataval, hasnodata, nodataval, i) < 0) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not add new band to output tile"); - SRF_RETURN_DONE(funcctx); - } - band = rt_raster_get_band(tile, i); - if (band == NULL) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not get newly added band from output tile"); - SRF_RETURN_DONE(funcctx); - } - - /* if isnodata, set flag and continue */ - if (rt_band_get_isnodata_flag(_band)) { - rt_band_set_isnodata_flag(band, 1); - continue; - } - - /* copy data */ - for (j = 0; j < arg2->tile.height; j++) { - k = ry + j; - - if (k >= arg2->raster.height) { - POSTGIS_RT_DEBUGF(4, "row %d is beyond extent of source raster. skipping", k); - continue; - } - - POSTGIS_RT_DEBUGF(4, "getting pixel line %d, %d for %d pixels", rx, k, len); - if (rt_band_get_pixel_line(_band, rx, k, len, &vals, &nvals) != ES_NONE) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not get pixel line from source raster"); - SRF_RETURN_DONE(funcctx); - } - - if (nvals && rt_band_set_pixel_line(band, 0, j, vals, nvals) != ES_NONE) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not set pixel line of output tile"); - SRF_RETURN_DONE(funcctx); - } - } - } - /* offline */ - else { - uint8_t bandnum = 0; - rt_band_get_ext_band_num(_band, &bandnum); - - band = rt_band_new_offline( - width, height, - pixtype, - hasnodata, nodataval, - bandnum, rt_band_get_ext_path(_band) - ); - - if (band == NULL) { - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not create new offline band for output tile"); - SRF_RETURN_DONE(funcctx); - } - - if (rt_raster_add_band(tile, band, i) < 0) { - rt_band_destroy(band); - rt_raster_destroy(tile); - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - elog(ERROR, "RASTER_tile: Could not add new offline band to output tile"); - SRF_RETURN_DONE(funcctx); - } - } - } - - pgtile = rt_raster_serialize(tile); - rt_raster_destroy(tile); - if (!pgtile) { - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - SRF_RETURN_DONE(funcctx); - } - - SET_VARSIZE(pgtile, pgtile->size); - SRF_RETURN_NEXT(funcctx, PointerGetDatum(pgtile)); - } - /* do when there is no more left */ - else { - rt_raster_destroy(arg2->raster.raster); - if (arg2->numbands) pfree(arg2->nbands); - pfree(arg2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Copy a band from one raster to another one at the given position. - */ -PG_FUNCTION_INFO_V1(RASTER_copyBand); -Datum RASTER_copyBand(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgto = NULL; - rt_pgraster *pgfrom = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster torast = NULL; - rt_raster fromrast = NULL; - int toindex = 0; - int fromband = 0; - int oldtorastnumbands = 0; - int newtorastnumbands = 0; - int newbandindex = 0; - - /* Deserialize torast */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgto = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - torast = rt_raster_deserialize(pgto, FALSE); - if (!torast) { - PG_FREE_IF_COPY(pgto, 0); - elog(ERROR, "RASTER_copyBand: Could not deserialize first raster"); - PG_RETURN_NULL(); - } - - /* Deserialize fromrast */ - if (!PG_ARGISNULL(1)) { - pgfrom = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - - fromrast = rt_raster_deserialize(pgfrom, FALSE); - if (!fromrast) { - rt_raster_destroy(torast); - PG_FREE_IF_COPY(pgfrom, 1); - PG_FREE_IF_COPY(pgto, 0); - elog(ERROR, "RASTER_copyBand: Could not deserialize second raster"); - PG_RETURN_NULL(); - } - - oldtorastnumbands = rt_raster_get_num_bands(torast); - - if (PG_ARGISNULL(2)) - fromband = 1; - else - fromband = PG_GETARG_INT32(2); - - if (PG_ARGISNULL(3)) - toindex = oldtorastnumbands + 1; - else - toindex = PG_GETARG_INT32(3); - - /* Copy band fromrast torast */ - newbandindex = rt_raster_copy_band( - torast, fromrast, - fromband - 1, toindex - 1 - ); - - newtorastnumbands = rt_raster_get_num_bands(torast); - if (newtorastnumbands == oldtorastnumbands || newbandindex == -1) { - elog(NOTICE, "RASTER_copyBand: Could not add band to raster. " - "Returning original raster." - ); - } - - rt_raster_destroy(fromrast); - PG_FREE_IF_COPY(pgfrom, 1); - } - - /* Serialize and return torast */ - pgrtn = rt_raster_serialize(torast); - rt_raster_destroy(torast); - PG_FREE_IF_COPY(pgto, 0); - if (!pgrtn) PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/** - * Check if raster is empty or not - */ -PG_FUNCTION_INFO_V1(RASTER_isEmpty); -Datum RASTER_isEmpty(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - bool isempty = FALSE; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) - { - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("RASTER_isEmpty: Could not deserialize raster"))); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - isempty = rt_raster_is_empty(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_BOOL(isempty); -} - -/** - * Check if the raster has a given band or not - */ -PG_FUNCTION_INFO_V1(RASTER_hasNoBand); -Datum RASTER_hasNoBand(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - int bandindex = 0; - bool hasnoband = FALSE; - - /* Deserialize raster */ - if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - raster = rt_raster_deserialize(pgraster, TRUE); - if ( ! raster ) - { - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("RASTER_hasNoBand: Could not deserialize raster"))); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - /* Get band number */ - bandindex = PG_GETARG_INT32(1); - hasnoband = !rt_raster_has_band(raster, bandindex - 1); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - PG_RETURN_BOOL(hasnoband); -} - -PG_FUNCTION_INFO_V1(RASTER_mapAlgebraExpr); -Datum RASTER_mapAlgebraExpr(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_raster newrast = NULL; - rt_band band = NULL; - rt_band newband = NULL; - int x, y, nband, width, height; - double r; - double newnodatavalue = 0.0; - double newinitialvalue = 0.0; - double newval = 0.0; - char *newexpr = NULL; - char *initexpr = NULL; - char *expression = NULL; - int hasnodataval = 0; - double nodataval = 0.; - rt_pixtype newpixeltype; - int skipcomputation = 0; - int len = 0; - const int argkwcount = 3; - enum KEYWORDS { kVAL=0, kX=1, kY=2 }; - char *argkw[] = {"[rast]", "[rast.x]", "[rast.y]"}; - Oid argkwtypes[] = { FLOAT8OID, INT4OID, INT4OID }; - int argcount = 0; - Oid argtype[] = { FLOAT8OID, INT4OID, INT4OID }; - uint8_t argpos[3] = {0}; - char place[5]; - int idx = 0; - int ret = -1; - TupleDesc tupdesc; - SPIPlanPtr spi_plan = NULL; - SPITupleTable * tuptable = NULL; - HeapTuple tuple; - char * strFromText = NULL; - Datum *values = NULL; - Datum datum = (Datum)NULL; - char *nulls = NULL; - bool isnull = FALSE; - int i = 0; - int j = 0; - - POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraExpr: Starting..."); - - /* Check raster */ - if (PG_ARGISNULL(0)) { - elog(NOTICE, "Raster is NULL. Returning NULL"); - PG_RETURN_NULL(); - } - - - /* Deserialize raster */ - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - raster = rt_raster_deserialize(pgraster, FALSE); - if (NULL == raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraExpr: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Getting arguments..."); - - if (PG_ARGISNULL(1)) - nband = 1; - else - nband = PG_GETARG_INT32(1); - - if (nband < 1) - nband = 1; - - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Creating new empty raster..."); - - /** - * Create a new empty raster with having the same georeference as the - * provided raster - **/ - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - - newrast = rt_raster_new(width, height); - - if ( NULL == newrast ) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraExpr: Could not create a new raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(newrast, - rt_raster_get_x_scale(raster), - rt_raster_get_y_scale(raster)); - - rt_raster_set_offsets(newrast, - rt_raster_get_x_offset(raster), - rt_raster_get_y_offset(raster)); - - rt_raster_set_skews(newrast, - rt_raster_get_x_skew(raster), - rt_raster_get_y_skew(raster)); - - rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); - - - /** - * If this new raster is empty (width = 0 OR height = 0) then there is - * nothing to compute and we return it right now - **/ - if (rt_raster_is_empty(newrast)) - { - elog(NOTICE, "Raster is empty. Returning an empty raster"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Getting raster band %d...", nband); - - /** - * Check if the raster has the required band. Otherwise, return a raster - * without band - **/ - if (!rt_raster_has_band(raster, nband - 1)) { - elog(NOTICE, "Raster does not have the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* Get the raster band */ - band = rt_raster_get_band(raster, nband - 1); - if ( NULL == band ) { - elog(NOTICE, "Could not get the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* - * Get NODATA value - */ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Getting NODATA value for band..."); - - if (rt_band_get_hasnodata_flag(band)) { - rt_band_get_nodata(band, &newnodatavalue); - } - - else { - newnodatavalue = rt_band_get_min_value(band); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: NODATA value for band: = %f", - newnodatavalue); - - /** - * We set the initial value of the future band to nodata value. If nodata - * value is null, then the raster will be initialized to - * rt_band_get_min_value but all the values should be recomputed anyway - **/ - newinitialvalue = newnodatavalue; - - /** - * Set the new pixeltype - **/ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Setting pixeltype..."); - - if (PG_ARGISNULL(2)) { - newpixeltype = rt_band_get_pixtype(band); - } - - else { - strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); - newpixeltype = rt_pixtype_index_from_name(strFromText); - pfree(strFromText); - if (newpixeltype == PT_END) - newpixeltype = rt_band_get_pixtype(band); - } - - if (newpixeltype == PT_END) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraExpr: Invalid pixeltype"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Pixeltype set to %s", - rt_pixtype_name(newpixeltype)); - - - /* Construct expression for raster values */ - if (!PG_ARGISNULL(3)) { - expression = text_to_cstring(PG_GETARG_TEXT_P(3)); - len = strlen("SELECT (") + strlen(expression) + strlen(")::double precision"); - initexpr = (char *)palloc(len + 1); - - strncpy(initexpr, "SELECT (", strlen("SELECT (")); - strncpy(initexpr + strlen("SELECT ("), expression, strlen(expression)); - strncpy(initexpr + strlen("SELECT (") + strlen(expression), ")::double precision", strlen(")::double precision")); - initexpr[len] = '\0'; - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Expression is %s", initexpr); - - /* We don't need this memory */ - /* - pfree(expression); - expression = NULL; - */ - } - - - - /** - * Optimization: If a nodataval is provided, use it for newinitialvalue. - * Then, we can initialize the raster with this value and skip the - * computation of nodata values one by one in the main computing loop - **/ - if (!PG_ARGISNULL(4)) { - hasnodataval = 1; - nodataval = PG_GETARG_FLOAT8(4); - newinitialvalue = nodataval; - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: new initial value = %f", - newinitialvalue); - } - else - hasnodataval = 0; - - - - /** - * Optimization: If the raster is only filled with nodata values return - * right now a raster filled with the newinitialvalue - * TODO: Call rt_band_check_isnodata instead? - **/ - if (rt_band_get_isnodata_flag(band)) { - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Band is a nodata band, returning " - "a raster filled with nodata"); - - ret = rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - /* Free memory */ - if (initexpr) - pfree(initexpr); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - - /** - * Optimization: If expression resume to 'RAST' and hasnodataval is zero, - * we can just return the band from the original raster - **/ - if (initexpr != NULL && ( !strcmp(initexpr, "SELECT [rast]") || !strcmp(initexpr, "SELECT [rast.val]") ) && !hasnodataval) { - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Expression resumes to RAST. " - "Returning raster with band %d from original raster", nband); - - POSTGIS_RT_DEBUGF(4, "RASTER_mapAlgebraExpr: New raster has %d bands", - rt_raster_get_num_bands(newrast)); - - rt_raster_copy_band(newrast, raster, nband - 1, 0); - - POSTGIS_RT_DEBUGF(4, "RASTER_mapAlgebraExpr: New raster now has %d bands", - rt_raster_get_num_bands(newrast)); - - if (initexpr) - pfree(initexpr); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /** - * Optimization: If expression resume to a constant (it does not contain - * [rast) - **/ - if (initexpr != NULL && strstr(initexpr, "[rast") == NULL) { - ret = SPI_connect(); - if (ret != SPI_OK_CONNECT) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraExpr: Could not connect to the SPI manager"); - PG_RETURN_NULL(); - }; - - /* Execute the expresion into newval */ - ret = SPI_execute(initexpr, FALSE, 0); - - if (ret != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - - /* Free memory allocated out of the current context */ - if (SPI_tuptable) - SPI_freetuptable(tuptable); - PG_FREE_IF_COPY(pgraster, 0); - - SPI_finish(); - elog(ERROR, "RASTER_mapAlgebraExpr: Invalid construction for expression"); - PG_RETURN_NULL(); - } - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - - tuple = tuptable->vals[0]; - newexpr = SPI_getvalue(tuple, tupdesc, 1); - if ( ! newexpr ) { - POSTGIS_RT_DEBUG(3, "Constant expression evaluated to NULL, keeping initvalue"); - newval = newinitialvalue; - } else { - newval = atof(newexpr); - } - - SPI_freetuptable(tuptable); - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: New raster value = %f", - newval); - - SPI_finish(); - - skipcomputation = 1; - - /** - * Compute the new value, set it and we will return after creating the - * new raster - **/ - if (!hasnodataval) { - newinitialvalue = newval; - skipcomputation = 2; - } - - /* Return the new raster as it will be before computing pixel by pixel */ - else if (FLT_NEQ(newval, newinitialvalue)) { - skipcomputation = 2; - } - } - - /** - * Create the raster receiving all the computed values. Initialize it to the - * new initial value - **/ - ret = rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - /** - * Optimization: If expression is NULL, or all the pixels could be set in - * one step, return the initialized raster now - **/ - /*if (initexpr == NULL || skipcomputation == 2) {*/ - if (expression == NULL || skipcomputation == 2) { - - /* Free memory */ - if (initexpr) - pfree(initexpr); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - RASTER_DEBUG(3, "RASTER_mapAlgebraExpr: Creating new raster band..."); - - /* Get the new raster band */ - newband = rt_raster_get_band(newrast, 0); - if ( NULL == newband ) { - elog(NOTICE, "Could not modify band for new raster. Returning new " - "raster with the original band"); - - if (initexpr) - pfree(initexpr); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Main computing loop (%d x %d)", - width, height); - - if (initexpr != NULL) { - /* Convert [rast.val] to [rast] */ - newexpr = rtpg_strreplace(initexpr, "[rast.val]", "[rast]", NULL); - pfree(initexpr); initexpr=newexpr; - - sprintf(place,"$1"); - for (i = 0, j = 1; i < argkwcount; i++) { - len = 0; - newexpr = rtpg_strreplace(initexpr, argkw[i], place, &len); - pfree(initexpr); initexpr=newexpr; - if (len > 0) { - argtype[argcount] = argkwtypes[i]; - argcount++; - argpos[i] = j++; - - sprintf(place, "$%d", j); - } - else { - argpos[i] = 0; - } - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: initexpr = %s", initexpr); - - /* define values */ - values = (Datum *) palloc(sizeof(Datum) * argcount); - if (values == NULL) { - - SPI_finish(); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraExpr: Could not allocate memory for value parameters of prepared statement"); - PG_RETURN_NULL(); - } - - /* define nulls */ - nulls = (char *)palloc(argcount); - if (nulls == NULL) { - - SPI_finish(); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraExpr: Could not allocate memory for null parameters of prepared statement"); - PG_RETURN_NULL(); - } - - /* Connect to SPI and prepare the expression */ - ret = SPI_connect(); - if (ret != SPI_OK_CONNECT) { - - if (initexpr) - pfree(initexpr); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraExpr: Could not connect to the SPI manager"); - PG_RETURN_NULL(); - }; - - /* Type of all arguments is FLOAT8OID */ - spi_plan = SPI_prepare(initexpr, argcount, argtype); - - if (spi_plan == NULL) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - SPI_finish(); - - pfree(initexpr); - - elog(ERROR, "RASTER_mapAlgebraExpr: Could not prepare expression"); - PG_RETURN_NULL(); - } - } - - for (x = 0; x < width; x++) { - for(y = 0; y < height; y++) { - ret = rt_band_get_pixel(band, x, y, &r, NULL); - - /** - * We compute a value only for the withdata value pixel since the - * nodata value has already been set by the first optimization - **/ - if (ret == ES_NONE && FLT_NEQ(r, newnodatavalue)) { - if (skipcomputation == 0) { - if (initexpr != NULL) { - /* Reset the null arg flags. */ - memset(nulls, 'n', argcount); - - for (i = 0; i < argkwcount; i++) { - idx = argpos[i]; - if (idx < 1) continue; - idx--; - - if (i == kX ) { - /* x is 0 based index, but SQL expects 1 based index */ - values[idx] = Int32GetDatum(x+1); - nulls[idx] = ' '; - } - else if (i == kY) { - /* y is 0 based index, but SQL expects 1 based index */ - values[idx] = Int32GetDatum(y+1); - nulls[idx] = ' '; - } - else if (i == kVAL ) { - values[idx] = Float8GetDatum(r); - nulls[idx] = ' '; - } - - } - - ret = SPI_execute_plan(spi_plan, values, nulls, FALSE, 0); - if (ret != SPI_OK_SELECT || SPI_tuptable == NULL || - SPI_processed != 1) { - if (SPI_tuptable) - SPI_freetuptable(tuptable); - - SPI_freeplan(spi_plan); - SPI_finish(); - - pfree(values); - pfree(nulls); - pfree(initexpr); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraExpr: Error executing prepared plan"); - - PG_RETURN_NULL(); - } - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - - tuple = tuptable->vals[0]; - datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); - if ( SPI_result == SPI_ERROR_NOATTRIBUTE ) { - POSTGIS_RT_DEBUGF(3, "Expression for pixel %d,%d (value %g) errored, skip setting", x+1,y+1,r); - newval = newinitialvalue; - } - else if ( isnull ) { - POSTGIS_RT_DEBUGF(3, "Expression for pixel %d,%d (value %g) evaluated to NULL, skip setting", x+1,y+1,r); - newval = newinitialvalue; - } else { - newval = DatumGetFloat8(datum); - } - - SPI_freetuptable(tuptable); - } - - else - newval = newinitialvalue; - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: new value = %f", - newval); - } - - - rt_band_set_pixel(newband, x, y, newval, NULL); - } - - } - } - - if (initexpr != NULL) { - SPI_freeplan(spi_plan); - SPI_finish(); - - pfree(values); - pfree(nulls); - pfree(initexpr); - } - else { - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: no SPI cleanup"); - } - - - /* The newrast band has been modified */ - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: raster modified, serializing it."); - /* Serialize created raster */ - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: raster serialized"); - - - POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraExpr: returning raster"); - - - PG_RETURN_POINTER(pgrtn); -} - -/* - * One raster user function MapAlgebra. - */ -PG_FUNCTION_INFO_V1(RASTER_mapAlgebraFct); -Datum RASTER_mapAlgebraFct(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_raster newrast = NULL; - rt_band band = NULL; - rt_band newband = NULL; - int x, y, nband, width, height; - double r; - double newnodatavalue = 0.0; - double newinitialvalue = 0.0; - double newval = 0.0; - rt_pixtype newpixeltype; - int ret = -1; - Oid oid; - FmgrInfo cbinfo; - FunctionCallInfoData cbdata; - Datum tmpnewval; - char * strFromText = NULL; - int k = 0; - - POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraFct: STARTING..."); - - /* Check raster */ - if (PG_ARGISNULL(0)) { - elog(WARNING, "Raster is NULL. Returning NULL"); - PG_RETURN_NULL(); - } - - - /* Deserialize raster */ - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - raster = rt_raster_deserialize(pgraster, FALSE); - if (NULL == raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraFct: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Getting arguments..."); - - /* Get the rest of the arguments */ - - if (PG_ARGISNULL(1)) - nband = 1; - else - nband = PG_GETARG_INT32(1); - - if (nband < 1) - nband = 1; - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Creating new empty raster..."); - - /** - * Create a new empty raster with having the same georeference as the - * provided raster - **/ - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - - newrast = rt_raster_new(width, height); - - if ( NULL == newrast ) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - elog(ERROR, "RASTER_mapAlgebraFct: Could not create a new raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(newrast, - rt_raster_get_x_scale(raster), - rt_raster_get_y_scale(raster)); - - rt_raster_set_offsets(newrast, - rt_raster_get_x_offset(raster), - rt_raster_get_y_offset(raster)); - - rt_raster_set_skews(newrast, - rt_raster_get_x_skew(raster), - rt_raster_get_y_skew(raster)); - - rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); - - - /** - * If this new raster is empty (width = 0 OR height = 0) then there is - * nothing to compute and we return it right now - **/ - if (rt_raster_is_empty(newrast)) - { - elog(NOTICE, "Raster is empty. Returning an empty raster"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Getting raster band %d...", nband); - - /** - * Check if the raster has the required band. Otherwise, return a raster - * without band - **/ - if (!rt_raster_has_band(raster, nband - 1)) { - elog(NOTICE, "Raster does not have the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* Get the raster band */ - band = rt_raster_get_band(raster, nband - 1); - if ( NULL == band ) { - elog(NOTICE, "Could not get the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* - * Get NODATA value - */ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Getting NODATA value for band..."); - - if (rt_band_get_hasnodata_flag(band)) { - rt_band_get_nodata(band, &newnodatavalue); - } - - else { - newnodatavalue = rt_band_get_min_value(band); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: NODATA value for band: %f", - newnodatavalue); - /** - * We set the initial value of the future band to nodata value. If nodata - * value is null, then the raster will be initialized to - * rt_band_get_min_value but all the values should be recomputed anyway - **/ - newinitialvalue = newnodatavalue; - - /** - * Set the new pixeltype - **/ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Setting pixeltype..."); - - if (PG_ARGISNULL(2)) { - newpixeltype = rt_band_get_pixtype(band); - } - - else { - strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); - newpixeltype = rt_pixtype_index_from_name(strFromText); - pfree(strFromText); - if (newpixeltype == PT_END) - newpixeltype = rt_band_get_pixtype(band); - } - - if (newpixeltype == PT_END) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Invalid pixeltype"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Pixeltype set to %s", - rt_pixtype_name(newpixeltype)); - - /* Get the name of the callback user function for raster values */ - if (PG_ARGISNULL(3)) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Required function is missing. Returning NULL"); - PG_RETURN_NULL(); - } - - oid = PG_GETARG_OID(3); - if (oid == InvalidOid) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Got invalid function object id. Returning NULL"); - PG_RETURN_NULL(); - } - - fmgr_info(oid, &cbinfo); - - /* function cannot return set */ - if (cbinfo.fn_retset) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Function provided must return double precision not resultset"); - PG_RETURN_NULL(); - } - /* function should have correct # of args */ - else if (cbinfo.fn_nargs < 2 || cbinfo.fn_nargs > 3) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Function does not have two or three input parameters"); - PG_RETURN_NULL(); - } - - if (cbinfo.fn_nargs == 2) - k = 1; - else - k = 2; - - if (func_volatile(oid) == 'v') { - elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); - } - - /* prep function call data */ -#if POSTGIS_PGSQL_VERSION <= 90 - InitFunctionCallInfoData(cbdata, &cbinfo, 2, InvalidOid, NULL); -#else - InitFunctionCallInfoData(cbdata, &cbinfo, 2, InvalidOid, NULL, NULL); -#endif - memset(cbdata.argnull, FALSE, sizeof(bool) * cbinfo.fn_nargs); - - /* check that the function isn't strict if the args are null. */ - if (PG_ARGISNULL(4)) { - if (cbinfo.fn_strict) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFct: Strict callback functions cannot have null parameters"); - PG_RETURN_NULL(); - } - - cbdata.arg[k] = (Datum)NULL; - cbdata.argnull[k] = TRUE; - } - else { - cbdata.arg[k] = PG_GETARG_DATUM(4); - } - - /** - * Optimization: If the raster is only filled with nodata values return - * right now a raster filled with the nodatavalueexpr - * TODO: Call rt_band_check_isnodata instead? - **/ - if (rt_band_get_isnodata_flag(band)) { - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Band is a nodata band, returning " - "a raster filled with nodata"); - - ret = rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - - /** - * Create the raster receiving all the computed values. Initialize it to the - * new initial value - **/ - ret = rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - /* Get the new raster band */ - newband = rt_raster_get_band(newrast, 0); - if ( NULL == newband ) { - elog(NOTICE, "Could not modify band for new raster. Returning new " - "raster with the original band"); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Main computing loop (%d x %d)", - width, height); - - for (x = 0; x < width; x++) { - for(y = 0; y < height; y++) { - ret = rt_band_get_pixel(band, x, y, &r, NULL); - - /** - * We compute a value only for the withdata value pixel since the - * nodata value has already been set by the first optimization - **/ - if (ret == ES_NONE) { - if (FLT_EQ(r, newnodatavalue)) { - if (cbinfo.fn_strict) { - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Strict callbacks cannot accept NULL arguments, skipping NODATA cell."); - continue; - } - cbdata.argnull[0] = TRUE; - cbdata.arg[0] = (Datum)NULL; - } - else { - cbdata.argnull[0] = FALSE; - cbdata.arg[0] = Float8GetDatum(r); - } - - /* Add pixel positions if callback has proper # of args */ - if (cbinfo.fn_nargs == 3) { - Datum d[2]; - ArrayType *a; - - d[0] = Int32GetDatum(x+1); - d[1] = Int32GetDatum(y+1); - - a = construct_array(d, 2, INT4OID, sizeof(int32), true, 'i'); - - cbdata.argnull[1] = FALSE; - cbdata.arg[1] = PointerGetDatum(a); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: (%dx%d), r = %f", - x, y, r); - - tmpnewval = FunctionCallInvoke(&cbdata); - - if (cbdata.isnull) { - newval = newnodatavalue; - } - else { - newval = DatumGetFloat8(tmpnewval); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: new value = %f", - newval); - - rt_band_set_pixel(newband, x, y, newval, NULL); - } - - } - } - - /* The newrast band has been modified */ - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: raster modified, serializing it."); - /* Serialize created raster */ - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) - PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: raster serialized"); - - POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraFct: returning raster"); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/** - * Return new raster from selected bands of existing raster through ST_Band. - * second argument is an array of band numbers (1 based) - */ -PG_FUNCTION_INFO_V1(RASTER_band); -Datum RASTER_band(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster; - rt_pgraster *pgrast; - rt_raster raster; - rt_raster rast; - - bool skip = FALSE; - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - - uint32_t numBands; - uint32_t *bandNums; - uint32 idx = 0; - int n; - int i = 0; - int j = 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_band: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* process bandNums */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Band number(s) not provided. Returning original raster"); - skip = TRUE; - } - do { - if (skip) break; - - numBands = rt_raster_get_num_bands(raster); - - array = PG_GETARG_ARRAYTYPE_P(1); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case INT2OID: - case INT4OID: - break; - default: - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_band: Invalid data type for band number(s)"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - bandNums = palloc(sizeof(uint32_t) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case INT2OID: - idx = (uint32_t) DatumGetInt16(e[i]); - break; - case INT4OID: - idx = (uint32_t) DatumGetInt32(e[i]); - break; - } - - POSTGIS_RT_DEBUGF(3, "band idx (before): %d", idx); - if (idx > numBands || idx < 1) { - elog(NOTICE, "Invalid band index (must use 1-based). Returning original raster"); - skip = TRUE; - break; - } - - bandNums[j] = idx - 1; - POSTGIS_RT_DEBUGF(3, "bandNums[%d] = %d", j, bandNums[j]); - j++; - } - - if (skip || j < 1) { - pfree(bandNums); - skip = TRUE; - } - } - while (0); - - if (!skip) { - rast = rt_raster_from_band(raster, bandNums, j); - pfree(bandNums); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (!rast) { - elog(ERROR, "RASTER_band: Could not create new raster"); - PG_RETURN_NULL(); - } - - pgrast = rt_raster_serialize(rast); - rt_raster_destroy(rast); - - if (!pgrast) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrast, pgrast->size); - PG_RETURN_POINTER(pgrast); - } - - PG_RETURN_POINTER(pgraster); -} - -/** - * Get summary stats of a band - */ -PG_FUNCTION_INFO_V1(RASTER_summaryStats); -Datum RASTER_summaryStats(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex = 1; - bool exclude_nodata_value = TRUE; - int num_bands = 0; - double sample = 0; - rt_bandstats stats = NULL; - - TupleDesc tupdesc; - int values_length = 6; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - /* pgraster is null, return null */ - 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_summaryStats: 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(); - } - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(2)) - exclude_nodata_value = PG_GETARG_BOOL(2); - - /* sample % */ - if (!PG_ARGISNULL(3)) { - sample = PG_GETARG_FLOAT8(3); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* 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(); - } - - /* we don't need the raw values, hence the zero parameter */ - stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 0, NULL, NULL, NULL); - rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (NULL == stats) { - elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); - PG_RETURN_NULL(); - } - - /* 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); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Int64GetDatum(stats->count); - if (stats->count > 0) { - values[1] = Float8GetDatum(stats->sum); - values[2] = Float8GetDatum(stats->mean); - values[3] = Float8GetDatum(stats->stddev); - values[4] = Float8GetDatum(stats->min); - values[5] = Float8GetDatum(stats->max); - } - else { - nulls[1] = TRUE; - nulls[2] = TRUE; - nulls[3] = TRUE; - nulls[4] = TRUE; - nulls[5] = TRUE; - } - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - /* clean up */ - pfree(stats); - - PG_RETURN_DATUM(result); -} - -/** - * Get summary stats of a coverage for a specific band - */ -PG_FUNCTION_INFO_V1(RASTER_summaryStatsCoverage); -Datum RASTER_summaryStatsCoverage(PG_FUNCTION_ARGS) -{ - text *tablenametext = NULL; - char *tablename = NULL; - text *colnametext = NULL; - char *colname = NULL; - int32_t bandindex = 1; - bool exclude_nodata_value = TRUE; - double sample = 0; - - int len = 0; - char *sql = NULL; - int spi_result; - Portal portal; - TupleDesc tupdesc; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isNull = FALSE; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int num_bands = 0; - uint64_t cK = 0; - double cM = 0; - double cQ = 0; - rt_bandstats stats = NULL; - rt_bandstats rtn = NULL; - - int values_length = 6; - Datum values[values_length]; - bool nulls[values_length]; - Datum result; - - /* tablename is null, return null */ - if (PG_ARGISNULL(0)) { - elog(NOTICE, "Table name must be provided"); - PG_RETURN_NULL(); - } - tablenametext = PG_GETARG_TEXT_P(0); - tablename = text_to_cstring(tablenametext); - if (!strlen(tablename)) { - elog(NOTICE, "Table name must be provided"); - PG_RETURN_NULL(); - } - - /* column name is null, return null */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Column name must be provided"); - PG_RETURN_NULL(); - } - colnametext = PG_GETARG_TEXT_P(1); - colname = text_to_cstring(colnametext); - if (!strlen(colname)) { - elog(NOTICE, "Column name must be provided"); - PG_RETURN_NULL(); - } - - /* band index is 1-based */ - if (!PG_ARGISNULL(2)) - bandindex = PG_GETARG_INT32(2); - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(3)) - exclude_nodata_value = PG_GETARG_BOOL(3); - - /* sample % */ - if (!PG_ARGISNULL(4)) { - sample = PG_GETARG_FLOAT8(4); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - rt_raster_destroy(raster); - PG_RETURN_NULL(); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* iterate through rasters of coverage */ - /* connect to database */ - spi_result = SPI_connect(); - if (spi_result != SPI_OK_CONNECT) { - pfree(sql); - elog(ERROR, "RASTER_summaryStatsCoverage: Could not connect to database using SPI"); - PG_RETURN_NULL(); - } - - /* create sql */ - len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - elog(ERROR, "RASTER_summaryStatsCoverage: Could not allocate memory for sql"); - PG_RETURN_NULL(); - } - - /* get cursor */ - snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); - portal = SPI_cursor_open_with_args( - "coverage", - sql, - 0, NULL, - NULL, NULL, - TRUE, 0 - ); - pfree(sql); - - /* process resultset */ - SPI_cursor_fetch(portal, TRUE, 1); - while (SPI_processed == 1 && SPI_tuptable != NULL) { - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != rtn) pfree(rtn); - elog(ERROR, "RASTER_summaryStatsCoverage: Could not get raster of coverage"); - PG_RETURN_NULL(); - } - else if (isNull) { - SPI_cursor_fetch(portal, TRUE, 1); - continue; - } - - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); - - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != rtn) pfree(rtn); - elog(ERROR, "RASTER_summaryStatsCoverage: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* inspect number of bands */ - 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != rtn) pfree(rtn); - 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != rtn) pfree(rtn); - PG_RETURN_NULL(); - } - - /* we don't need the raw values, hence the zero parameter */ - stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 0, &cK, &cM, &cQ); - - rt_band_destroy(band); - rt_raster_destroy(raster); - - if (NULL == stats) { - elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != rtn) pfree(rtn); - PG_RETURN_NULL(); - } - - /* initialize rtn */ - if (stats->count > 0) { - if (NULL == rtn) { - rtn = (rt_bandstats) SPI_palloc(sizeof(struct rt_bandstats_t)); - if (NULL == rtn) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - elog(ERROR, "RASTER_summaryStatsCoverage: Could not allocate memory for summary stats of coverage"); - PG_RETURN_NULL(); - } - - rtn->sample = stats->sample; - rtn->count = stats->count; - rtn->min = stats->min; - rtn->max = stats->max; - rtn->sum = stats->sum; - rtn->mean = stats->mean; - rtn->stddev = -1; - - rtn->values = NULL; - rtn->sorted = 0; - } - else { - rtn->count += stats->count; - rtn->sum += stats->sum; - - if (stats->min < rtn->min) - rtn->min = stats->min; - if (stats->max > rtn->max) - rtn->max = stats->max; - } - } - - pfree(stats); - - /* next record */ - SPI_cursor_fetch(portal, TRUE, 1); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL == rtn) { - elog(ERROR, "RASTER_summaryStatsCoverage: Could not compute coverage summary stats"); - PG_RETURN_NULL(); - } - - /* coverage mean and deviation */ - rtn->mean = rtn->sum / rtn->count; - /* sample deviation */ - if (rtn->sample > 0 && rtn->sample < 1) - rtn->stddev = sqrt(cQ / (rtn->count - 1)); - /* standard deviation */ - else - rtn->stddev = sqrt(cQ / rtn->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); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Int64GetDatum(rtn->count); - if (rtn->count > 0) { - values[1] = Float8GetDatum(rtn->sum); - values[2] = Float8GetDatum(rtn->mean); - values[3] = Float8GetDatum(rtn->stddev); - values[4] = Float8GetDatum(rtn->min); - values[5] = Float8GetDatum(rtn->max); - } - else { - nulls[1] = TRUE; - nulls[2] = TRUE; - nulls[3] = TRUE; - nulls[4] = TRUE; - nulls[5] = TRUE; - } - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - /* clean up */ - pfree(rtn); - - PG_RETURN_DATUM(result); -} - -/** - * Returns histogram for a band - */ -PG_FUNCTION_INFO_V1(RASTER_histogram); -Datum RASTER_histogram(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - rt_histogram hist; - rt_histogram hist2; - int call_cntr; - int max_calls; - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex = 1; - int num_bands = 0; - bool exclude_nodata_value = TRUE; - double sample = 0; - uint32_t bin_count = 0; - double *bin_width = NULL; - uint32_t bin_width_count = 0; - double width = 0; - bool right = FALSE; - double min = 0; - double max = 0; - rt_bandstats stats = NULL; - uint32_t count; - - int j; - int n; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - - POSTGIS_RT_DEBUG(3, "RASTER_histogram: Starting"); - - /* 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); - - /* pgraster is null, return nothing */ - 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_histogram: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(2)) - exclude_nodata_value = PG_GETARG_BOOL(2); - - /* sample % */ - if (!PG_ARGISNULL(3)) { - sample = PG_GETARG_FLOAT8(3); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* bin_count */ - if (!PG_ARGISNULL(4)) { - bin_count = PG_GETARG_INT32(4); - if (bin_count < 1) bin_count = 0; - } - - /* bin_width */ - if (!PG_ARGISNULL(5)) { - array = PG_GETARG_ARRAYTYPE_P(5); - 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_histogram: Invalid data type for width"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - bin_width = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - width = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - width = (double) DatumGetFloat8(e[i]); - break; - } - - if (width < 0 || FLT_EQ(width, 0.0)) { - elog(NOTICE, "Invalid value for width (must be greater than 0). Returning NULL"); - pfree(bin_width); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - bin_width[j] = width; - POSTGIS_RT_DEBUGF(5, "bin_width[%d] = %f", j, bin_width[j]); - j++; - } - bin_width_count = j; - - if (j < 1) { - pfree(bin_width); - bin_width = NULL; - } - } - - /* right */ - if (!PG_ARGISNULL(6)) - right = PG_GETARG_BOOL(6); - - /* min */ - if (!PG_ARGISNULL(7)) min = PG_GETARG_FLOAT8(7); - - /* max */ - if (!PG_ARGISNULL(8)) max = PG_GETARG_FLOAT8(8); - - /* 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get stats */ - stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); - rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (NULL == stats || NULL == stats->values) { - elog(NOTICE, "Could not compute summary statistics for band at index %d", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (stats->count < 1) { - elog(NOTICE, "Could not compute histogram for band at index %d as the band has no values", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get histogram */ - hist = rt_band_get_histogram(stats, bin_count, bin_width, bin_width_count, right, min, max, &count); - if (bin_width_count) pfree(bin_width); - pfree(stats); - if (NULL == hist || !count) { - elog(NOTICE, "Could not compute histogram for band at index %d", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d bins returned", count); - - /* Store needed information */ - funcctx->user_fctx = hist; - - /* 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; - hist2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 4; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(hist2[call_cntr].min); - values[1] = Float8GetDatum(hist2[call_cntr].max); - values[2] = Int64GetDatum(hist2[call_cntr].count); - values[3] = Float8GetDatum(hist2[call_cntr].percent); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(hist2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Returns histogram of a coverage for a specified band - */ -PG_FUNCTION_INFO_V1(RASTER_histogramCoverage); -Datum RASTER_histogramCoverage(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - rt_histogram covhist = NULL; - rt_histogram covhist2; - int call_cntr; - int max_calls; - - POSTGIS_RT_DEBUG(3, "RASTER_histogramCoverage: Starting"); - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - text *tablenametext = NULL; - char *tablename = NULL; - text *colnametext = NULL; - char *colname = NULL; - int32_t bandindex = 1; - bool exclude_nodata_value = TRUE; - double sample = 0; - uint32_t bin_count = 0; - double *bin_width = NULL; - uint32_t bin_width_count = 0; - double width = 0; - bool right = FALSE; - uint32_t count; - - int len = 0; - char *sql = NULL; - char *tmp = NULL; - double min = 0; - double max = 0; - int spi_result; - Portal portal; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isNull = FALSE; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int num_bands = 0; - rt_bandstats stats = NULL; - rt_histogram hist; - uint64_t sum = 0; - - int j; - int n; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - - POSTGIS_RT_DEBUG(3, "RASTER_histogramCoverage: first call of function"); - - /* 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); - - /* tablename is null, return null */ - if (PG_ARGISNULL(0)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - tablenametext = PG_GETARG_TEXT_P(0); - tablename = text_to_cstring(tablenametext); - if (!strlen(tablename)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: tablename = %s", tablename); - - /* column name is null, return null */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - colnametext = PG_GETARG_TEXT_P(1); - colname = text_to_cstring(colnametext); - if (!strlen(colname)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: colname = %s", colname); - - /* band index is 1-based */ - if (!PG_ARGISNULL(2)) - bandindex = PG_GETARG_INT32(2); - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(3)) - exclude_nodata_value = PG_GETARG_BOOL(3); - - /* sample % */ - if (!PG_ARGISNULL(4)) { - sample = PG_GETARG_FLOAT8(4); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* bin_count */ - if (!PG_ARGISNULL(5)) { - bin_count = PG_GETARG_INT32(5); - if (bin_count < 1) bin_count = 0; - } - - /* bin_width */ - if (!PG_ARGISNULL(6)) { - array = PG_GETARG_ARRAYTYPE_P(6); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Invalid data type for width"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - bin_width = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - width = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - width = (double) DatumGetFloat8(e[i]); - break; - } - - if (width < 0 || FLT_EQ(width, 0.0)) { - elog(NOTICE, "Invalid value for width (must be greater than 0). Returning NULL"); - pfree(bin_width); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - bin_width[j] = width; - POSTGIS_RT_DEBUGF(5, "bin_width[%d] = %f", j, bin_width[j]); - j++; - } - bin_width_count = j; - - if (j < 1) { - pfree(bin_width); - bin_width = NULL; - } - } - - /* right */ - if (!PG_ARGISNULL(7)) - right = PG_GETARG_BOOL(7); - - /* connect to database */ - spi_result = SPI_connect(); - if (spi_result != SPI_OK_CONNECT) { - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not connect to database using SPI"); - SRF_RETURN_DONE(funcctx); - } - - /* coverage stats */ - len = sizeof(char) * (strlen("SELECT min, max FROM _st_summarystats('','',,::boolean,)") + strlen(tablename) + strlen(colname) + (MAX_INT_CHARLEN * 2) + MAX_DBL_CHARLEN + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for sql"); - SRF_RETURN_DONE(funcctx); - } - - /* get stats */ - snprintf(sql, len, "SELECT min, max FROM _st_summarystats('%s','%s',%d,%d::boolean,%f)", tablename, colname, bandindex, (exclude_nodata_value ? 1 : 0), sample); - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: %s", sql); - spi_result = SPI_execute(sql, TRUE, 0); - pfree(sql); - if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); - SRF_RETURN_DONE(funcctx); - } - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - tmp = SPI_getvalue(tuple, tupdesc, 1); - if (NULL == tmp || !strlen(tmp)) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); - SRF_RETURN_DONE(funcctx); - } - min = strtod(tmp, NULL); - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: min = %f", min); - pfree(tmp); - - tmp = SPI_getvalue(tuple, tupdesc, 2); - if (NULL == tmp || !strlen(tmp)) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); - SRF_RETURN_DONE(funcctx); - } - max = strtod(tmp, NULL); - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: max = %f", max); - pfree(tmp); - - /* iterate through rasters of coverage */ - /* create sql */ - len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for sql"); - SRF_RETURN_DONE(funcctx); - } - - /* get cursor */ - snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); - POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: %s", sql); - portal = SPI_cursor_open_with_args( - "coverage", - sql, - 0, NULL, - NULL, NULL, - TRUE, 0 - ); - pfree(sql); - - /* process resultset */ - SPI_cursor_fetch(portal, TRUE, 1); - while (SPI_processed == 1 && SPI_tuptable != NULL) { - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not get raster of coverage"); - SRF_RETURN_DONE(funcctx); - } - else if (isNull) { - SPI_cursor_fetch(portal, TRUE, 1); - continue; - } - - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); - - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* inspect number of bands*/ - 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* we need the raw values, hence the non-zero parameter */ - stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); - - rt_band_destroy(band); - rt_raster_destroy(raster); - - if (NULL == stats) { - elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get histogram */ - if (stats->count > 0) { - hist = rt_band_get_histogram(stats, bin_count, bin_width, bin_width_count, right, min, max, &count); - pfree(stats); - if (NULL == hist || !count) { - elog(NOTICE, "Could not compute histogram for band at index %d", bandindex); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covhist) pfree(covhist); - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d bins returned", count); - - /* coverage histogram */ - if (NULL == covhist) { - covhist = (rt_histogram) SPI_palloc(sizeof(struct rt_histogram_t) * count); - if (NULL == covhist) { - - pfree(hist); - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for histogram of coverage"); - SRF_RETURN_DONE(funcctx); - } - - for (i = 0; i < count; i++) { - sum += hist[i].count; - covhist[i].count = hist[i].count; - covhist[i].percent = 0; - covhist[i].min = hist[i].min; - covhist[i].max = hist[i].max; - } - } - else { - for (i = 0; i < count; i++) { - sum += hist[i].count; - covhist[i].count += hist[i].count; - } - } - - pfree(hist); - - /* assuming bin_count wasn't set, force consistency */ - if (bin_count <= 0) bin_count = count; - } - - /* next record */ - SPI_cursor_fetch(portal, TRUE, 1); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (bin_width_count) pfree(bin_width); - - /* finish percent of histogram */ - if (sum > 0) { - for (i = 0; i < count; i++) - covhist[i].percent = covhist[i].count / (double) sum; - } - - /* Store needed information */ - funcctx->user_fctx = covhist; - - /* 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; - covhist2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 4; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(covhist2[call_cntr].min); - values[1] = Float8GetDatum(covhist2[call_cntr].max); - values[2] = Int64GetDatum(covhist2[call_cntr].count); - values[3] = Float8GetDatum(covhist2[call_cntr].percent); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(covhist2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Returns quantiles for a band - */ -PG_FUNCTION_INFO_V1(RASTER_quantile); -Datum RASTER_quantile(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - rt_quantile quant; - rt_quantile quant2; - int call_cntr; - int max_calls; - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex = 0; - int num_bands = 0; - bool exclude_nodata_value = TRUE; - double sample = 0; - double *quantiles = NULL; - uint32_t quantiles_count = 0; - double quantile = 0; - rt_bandstats stats = NULL; - uint32_t count; - - int j; - int n; - - 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); - - /* pgraster is null, return nothing */ - 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_quantile: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* band index is 1-based */ - 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(2)) - exclude_nodata_value = PG_GETARG_BOOL(2); - - /* sample % */ - if (!PG_ARGISNULL(3)) { - sample = PG_GETARG_FLOAT8(3); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* quantiles */ - if (!PG_ARGISNULL(4)) { - 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); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantile: Invalid data type for quantiles"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - quantiles = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - quantile = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - quantile = (double) DatumGetFloat8(e[i]); - break; - } - - if (quantile < 0 || quantile > 1) { - elog(NOTICE, "Invalid value for quantile (must be between 0 and 1). Returning NULL"); - pfree(quantiles); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - quantiles[j] = quantile; - POSTGIS_RT_DEBUGF(5, "quantiles[%d] = %f", j, quantiles[j]); - j++; - } - quantiles_count = j; - - if (j < 1) { - pfree(quantiles); - quantiles = 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get stats */ - stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); - rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (NULL == stats || NULL == stats->values) { - elog(NOTICE, "Could not retrieve summary statistics for band at index %d", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (stats->count < 1) { - elog(NOTICE, "Could not compute quantiles for band at index %d as the band has no values", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get quantiles */ - quant = rt_band_get_quantiles(stats, quantiles, quantiles_count, &count); - if (quantiles_count) pfree(quantiles); - pfree(stats); - if (NULL == quant || !count) { - elog(NOTICE, "Could not compute quantiles for band at index %d", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d quantiles returned", count); - - /* Store needed information */ - funcctx->user_fctx = quant; - - /* 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; - quant2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(quant2[call_cntr].quantile); - values[1] = Float8GetDatum(quant2[call_cntr].value); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(quant2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Returns selected quantiles of a coverage for a specified band - */ -PG_FUNCTION_INFO_V1(RASTER_quantileCoverage); -Datum RASTER_quantileCoverage(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - rt_quantile covquant = NULL; - rt_quantile covquant2; - int call_cntr; - int max_calls; - - POSTGIS_RT_DEBUG(3, "RASTER_quantileCoverage: Starting"); - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - text *tablenametext = NULL; - char *tablename = NULL; - text *colnametext = NULL; - char *colname = NULL; - int32_t bandindex = 1; - bool exclude_nodata_value = TRUE; - double sample = 0; - double *quantiles = NULL; - uint32_t quantiles_count = 0; - double quantile = 0; - uint32_t count; - - int len = 0; - char *sql = NULL; - char *tmp = NULL; - uint64_t cov_count = 0; - int spi_result; - Portal portal; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isNull = FALSE; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int num_bands = 0; - struct quantile_llist *qlls = NULL; - uint32_t qlls_count; - - int j; - int n; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - - POSTGIS_RT_DEBUG(3, "RASTER_quantileCoverage: first call of function"); - - /* 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); - - /* tablename is null, return null */ - if (PG_ARGISNULL(0)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - tablenametext = PG_GETARG_TEXT_P(0); - tablename = text_to_cstring(tablenametext); - if (!strlen(tablename)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "RASTER_quantileCoverage: tablename = %s", tablename); - - /* column name is null, return null */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - colnametext = PG_GETARG_TEXT_P(1); - colname = text_to_cstring(colnametext); - if (!strlen(colname)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "RASTER_quantileCoverage: colname = %s", colname); - - /* band index is 1-based */ - if (!PG_ARGISNULL(2)) - bandindex = PG_GETARG_INT32(2); - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(3)) - exclude_nodata_value = PG_GETARG_BOOL(3); - - /* sample % */ - if (!PG_ARGISNULL(4)) { - sample = PG_GETARG_FLOAT8(4); - if (sample < 0 || sample > 1) { - elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - else if (FLT_EQ(sample, 0.0)) - sample = 1; - } - else - sample = 1; - - /* quantiles */ - if (!PG_ARGISNULL(5)) { - array = PG_GETARG_ARRAYTYPE_P(5); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Invalid data type for quantiles"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - quantiles = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - quantile = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - quantile = (double) DatumGetFloat8(e[i]); - break; - } - - if (quantile < 0 || quantile > 1) { - elog(NOTICE, "Invalid value for quantile (must be between 0 and 1). Returning NULL"); - pfree(quantiles); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - quantiles[j] = quantile; - POSTGIS_RT_DEBUGF(5, "quantiles[%d] = %f", j, quantiles[j]); - j++; - } - quantiles_count = j; - - if (j < 1) { - pfree(quantiles); - quantiles = NULL; - } - } - - /* coverage stats */ - /* connect to database */ - spi_result = SPI_connect(); - if (spi_result != SPI_OK_CONNECT) { - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not connect to database using SPI"); - SRF_RETURN_DONE(funcctx); - } - - len = sizeof(char) * (strlen("SELECT count FROM _st_summarystats('','',,::boolean,)") + strlen(tablename) + strlen(colname) + (MAX_INT_CHARLEN * 2) + MAX_DBL_CHARLEN + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not allocate memory for sql"); - SRF_RETURN_DONE(funcctx); - } - - /* get stats */ - snprintf(sql, len, "SELECT count FROM _st_summarystats('%s','%s',%d,%d::boolean,%f)", tablename, colname, bandindex, (exclude_nodata_value ? 1 : 0), sample); - POSTGIS_RT_DEBUGF(3, "stats sql: %s", sql); - spi_result = SPI_execute(sql, TRUE, 0); - pfree(sql); - if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not get summary stats of coverage"); - SRF_RETURN_DONE(funcctx); - } - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - tmp = SPI_getvalue(tuple, tupdesc, 1); - if (NULL == tmp || !strlen(tmp)) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not get summary stats of coverage"); - SRF_RETURN_DONE(funcctx); - } - cov_count = strtol(tmp, NULL, 10); - POSTGIS_RT_DEBUGF(3, "covcount = %d", (int) cov_count); - pfree(tmp); - - /* iterate through rasters of coverage */ - /* create sql */ - len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not allocate memory for sql"); - SRF_RETURN_DONE(funcctx); - } - - /* get cursor */ - snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); - POSTGIS_RT_DEBUGF(3, "coverage sql: %s", sql); - portal = SPI_cursor_open_with_args( - "coverage", - sql, - 0, NULL, - NULL, NULL, - TRUE, 0 - ); - pfree(sql); - - /* process resultset */ - SPI_cursor_fetch(portal, TRUE, 1); - while (SPI_processed == 1 && SPI_tuptable != NULL) { - if (NULL != covquant) pfree(covquant); - - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not get raster of coverage"); - SRF_RETURN_DONE(funcctx); - } - else if (isNull) { - SPI_cursor_fetch(portal, TRUE, 1); - continue; - } - - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); - - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_quantileCoverage: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* inspect number of bands*/ - 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get band */ - band = rt_raster_get_band(raster, bandindex - 1); - if (!band) { - elog(NOTICE, "Could not find raster band of index %d. Returning NULL", bandindex); - - rt_raster_destroy(raster); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - covquant = rt_band_get_quantiles_stream( - band, - exclude_nodata_value, sample, cov_count, - &qlls, &qlls_count, - quantiles, quantiles_count, - &count - ); - - rt_band_destroy(band); - rt_raster_destroy(raster); - - if (NULL == covquant || !count) { - elog(NOTICE, "Could not compute quantiles for band at index %d", bandindex); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* next record */ - SPI_cursor_fetch(portal, TRUE, 1); - } - - covquant2 = SPI_palloc(sizeof(struct rt_quantile_t) * count); - for (i = 0; i < count; i++) { - covquant2[i].quantile = covquant[i].quantile; - covquant2[i].has_value = covquant[i].has_value; - if (covquant2[i].has_value) - covquant2[i].value = covquant[i].value; - } - - if (NULL != covquant) pfree(covquant); - quantile_llist_destroy(&qlls, qlls_count); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (quantiles_count) pfree(quantiles); - - POSTGIS_RT_DEBUGF(3, "%d quantiles returned", count); - - /* Store needed information */ - funcctx->user_fctx = covquant2; - - /* 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; - covquant2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(covquant2[call_cntr].quantile); - if (covquant2[call_cntr].has_value) - values[1] = Float8GetDatum(covquant2[call_cntr].value); - else - nulls[1] = TRUE; - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - POSTGIS_RT_DEBUG(3, "done"); - pfree(covquant2); - SRF_RETURN_DONE(funcctx); - } -} - -/* get counts of values */ -PG_FUNCTION_INFO_V1(RASTER_valueCount); -Datum RASTER_valueCount(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - rt_valuecount vcnts; - rt_valuecount vcnts2; - int call_cntr; - int max_calls; - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int32_t bandindex = 0; - int num_bands = 0; - bool exclude_nodata_value = TRUE; - double *search_values = NULL; - uint32_t search_values_count = 0; - double roundto = 0; - uint32_t count; - - int j; - int n; - - 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); - - /* pgraster is null, return nothing */ - 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_valueCount: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* band index is 1-based */ - 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(2)) - exclude_nodata_value = PG_GETARG_BOOL(2); - - /* search values */ - if (!PG_ARGISNULL(3)) { - array = PG_GETARG_ARRAYTYPE_P(3); - 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_valueCount: Invalid data type for values"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - search_values = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - search_values[j] = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - search_values[j] = (double) DatumGetFloat8(e[i]); - break; - } - - POSTGIS_RT_DEBUGF(5, "search_values[%d] = %f", j, search_values[j]); - j++; - } - search_values_count = j; - - if (j < 1) { - pfree(search_values); - search_values = NULL; - } - } - - /* roundto */ - if (!PG_ARGISNULL(4)) { - roundto = PG_GETARG_FLOAT8(4); - if (roundto < 0.) roundto = 0; - } - - /* 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get counts of values */ - vcnts = rt_band_get_value_count(band, (int) exclude_nodata_value, search_values, search_values_count, roundto, NULL, &count); - rt_band_destroy(band); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (NULL == vcnts || !count) { - elog(NOTICE, "Could not count the values for band at index %d", bandindex); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d value counts returned", count); - - /* Store needed information */ - funcctx->user_fctx = vcnts; - - /* 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; - vcnts2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 3; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(vcnts2[call_cntr].value); - values[1] = UInt32GetDatum(vcnts2[call_cntr].count); - values[2] = Float8GetDatum(vcnts2[call_cntr].percent); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(vcnts2); - SRF_RETURN_DONE(funcctx); - } -} - -/* get counts of values for a coverage */ -PG_FUNCTION_INFO_V1(RASTER_valueCountCoverage); -Datum RASTER_valueCountCoverage(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - TupleDesc tupdesc; - - int i; - uint64_t covcount = 0; - uint64_t covtotal = 0; - rt_valuecount covvcnts = NULL; - rt_valuecount covvcnts2; - int call_cntr; - int max_calls; - - POSTGIS_RT_DEBUG(3, "RASTER_valueCountCoverage: Starting"); - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - text *tablenametext = NULL; - char *tablename = NULL; - text *colnametext = NULL; - char *colname = NULL; - int32_t bandindex = 1; - bool exclude_nodata_value = TRUE; - double *search_values = NULL; - uint32_t search_values_count = 0; - double roundto = 0; - - int len = 0; - char *sql = NULL; - int spi_result; - Portal portal; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isNull = FALSE; - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - int num_bands = 0; - uint32_t count; - uint32_t total; - rt_valuecount vcnts = NULL; - int exists = 0; - - int j; - int n; - - 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); - - /* tablename is null, return null */ - if (PG_ARGISNULL(0)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - tablenametext = PG_GETARG_TEXT_P(0); - tablename = text_to_cstring(tablenametext); - if (!strlen(tablename)) { - elog(NOTICE, "Table name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "tablename = %s", tablename); - - /* column name is null, return null */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - colnametext = PG_GETARG_TEXT_P(1); - colname = text_to_cstring(colnametext); - if (!strlen(colname)) { - elog(NOTICE, "Column name must be provided"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - POSTGIS_RT_DEBUGF(3, "colname = %s", colname); - - /* band index is 1-based */ - if (!PG_ARGISNULL(2)) - bandindex = PG_GETARG_INT32(2); - - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(3)) - exclude_nodata_value = PG_GETARG_BOOL(3); - - /* search values */ - if (!PG_ARGISNULL(4)) { - array = PG_GETARG_ARRAYTYPE_P(4); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Invalid data type for values"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - search_values = palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case FLOAT4OID: - search_values[j] = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - search_values[j] = (double) DatumGetFloat8(e[i]); - break; - } - - POSTGIS_RT_DEBUGF(5, "search_values[%d] = %f", j, search_values[j]); - j++; - } - search_values_count = j; - - if (j < 1) { - pfree(search_values); - search_values = NULL; - } - } - - /* roundto */ - if (!PG_ARGISNULL(5)) { - roundto = PG_GETARG_FLOAT8(5); - if (roundto < 0.) roundto = 0; - } - - /* iterate through rasters of coverage */ - /* connect to database */ - spi_result = SPI_connect(); - if (spi_result != SPI_OK_CONNECT) { - - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not connect to database using SPI"); - SRF_RETURN_DONE(funcctx); - } - - /* create sql */ - len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); - sql = (char *) palloc(len); - if (NULL == sql) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_finish(); - - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not allocate memory for sql"); - SRF_RETURN_DONE(funcctx); - } - - /* get cursor */ - snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); - POSTGIS_RT_DEBUGF(3, "RASTER_valueCountCoverage: %s", sql); - portal = SPI_cursor_open_with_args( - "coverage", - sql, - 0, NULL, - NULL, NULL, - TRUE, 0 - ); - pfree(sql); - - /* process resultset */ - SPI_cursor_fetch(portal, TRUE, 1); - while (SPI_processed == 1 && SPI_tuptable != NULL) { - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covvcnts) pfree(covvcnts); - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not get raster of coverage"); - SRF_RETURN_DONE(funcctx); - } - else if (isNull) { - SPI_cursor_fetch(portal, TRUE, 1); - continue; - } - - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); - - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covvcnts) pfree(covvcnts); - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* inspect number of bands*/ - 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covvcnts) pfree(covvcnts); - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* 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); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covvcnts) pfree(covvcnts); - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* get counts of values */ - vcnts = rt_band_get_value_count(band, (int) exclude_nodata_value, search_values, search_values_count, roundto, &total, &count); - rt_band_destroy(band); - rt_raster_destroy(raster); - if (NULL == vcnts || !count) { - elog(NOTICE, "Could not count the values for band at index %d", bandindex); - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (NULL != covvcnts) free(covvcnts); - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d value counts returned", count); - - if (NULL == covvcnts) { - covvcnts = (rt_valuecount) SPI_palloc(sizeof(struct rt_valuecount_t) * count); - if (NULL == covvcnts) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (search_values_count) pfree(search_values); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not allocate memory for value counts of coverage"); - SRF_RETURN_DONE(funcctx); - } - - for (i = 0; i < count; i++) { - covvcnts[i].value = vcnts[i].value; - covvcnts[i].count = vcnts[i].count; - covvcnts[i].percent = -1; - } - - covcount = count; - } - else { - for (i = 0; i < count; i++) { - exists = 0; - - for (j = 0; j < covcount; j++) { - if (FLT_EQ(vcnts[i].value, covvcnts[j].value)) { - exists = 1; - break; - } - } - - if (exists) { - covvcnts[j].count += vcnts[i].count; - } - else { - covcount++; - covvcnts = SPI_repalloc(covvcnts, sizeof(struct rt_valuecount_t) * covcount); - if (NULL == covvcnts) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (search_values_count) pfree(search_values); - if (NULL != covvcnts) free(covvcnts); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_valueCountCoverage: Could not change allocated memory for value counts of coverage"); - SRF_RETURN_DONE(funcctx); - } - - covvcnts[covcount - 1].value = vcnts[i].value; - covvcnts[covcount - 1].count = vcnts[i].count; - covvcnts[covcount - 1].percent = -1; - } - } - } - - covtotal += total; - - pfree(vcnts); - - /* next record */ - SPI_cursor_fetch(portal, TRUE, 1); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - SPI_cursor_close(portal); - SPI_finish(); - - if (search_values_count) pfree(search_values); - - /* compute percentages */ - for (i = 0; i < covcount; i++) { - covvcnts[i].percent = (double) covvcnts[i].count / covtotal; - } - - /* Store needed information */ - funcctx->user_fctx = covvcnts; - - /* total number of tuples to be returned */ - funcctx->max_calls = covcount; - - /* 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; - covvcnts2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 3; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Float8GetDatum(covvcnts2[call_cntr].value); - values[1] = UInt32GetDatum(covvcnts2[call_cntr].count); - values[2] = Float8GetDatum(covvcnts2[call_cntr].percent); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(covvcnts2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Reclassify the specified bands of the raster - */ -PG_FUNCTION_INFO_V1(RASTER_reclass); -Datum RASTER_reclass(PG_FUNCTION_ARGS) { - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - rt_band newband = NULL; - uint32_t numBands = 0; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - - int i = 0; - int j = 0; - int k = 0; - - int a = 0; - int b = 0; - int c = 0; - - rt_reclassexpr *exprset = NULL; - HeapTupleHeader tup; - bool isnull; - Datum tupv; - uint32_t nband = 0; - char *expr = NULL; - text *exprtext = NULL; - double val = 0; - char *junk = NULL; - int inc_val = 0; - int exc_val = 0; - char *pixeltype = NULL; - text *pixeltypetext = NULL; - rt_pixtype pixtype = PT_END; - double nodataval = 0; - bool hasnodata = FALSE; - - char **comma_set = NULL; - int comma_n = 0; - char **colon_set = NULL; - int colon_n = 0; - char **dash_set = NULL; - int dash_n = 0; - - POSTGIS_RT_DEBUG(3, "RASTER_reclass: Starting"); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_reclass: Could not deserialize raster"); - PG_RETURN_NULL(); - } - numBands = rt_raster_get_num_bands(raster); - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: %d possible bands to be reclassified", n); - - /* process set of reclassarg */ - POSTGIS_RT_DEBUG(3, "RASTER_reclass: Processing Arg 1 (reclassargset)"); - array = PG_GETARG_ARRAYTYPE_P(1); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (!n) { - elog(NOTICE, "Invalid argument for reclassargset. Returning original 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); - } - - /* - process each element of reclassarg - each element is the index of the band to process, the set - of reclass ranges and the output band's pixeltype - */ - for (i = 0; i < n; i++) { - if (nulls[i]) continue; - - /* each element is a tuple */ - tup = (HeapTupleHeader) DatumGetPointer(e[i]); - if (NULL == tup) { - elog(NOTICE, "Invalid argument for reclassargset. Returning original 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); - } - - /* band index (1-based) */ - tupv = GetAttributeByName(tup, "nband", &isnull); - if (isnull) { - elog(NOTICE, "Invalid argument for reclassargset. Missing value of nband for reclassarg of index %d . Returning original raster", i); - - 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); - } - nband = DatumGetInt32(tupv); - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: expression for band %d", nband); - - /* valid band index? */ - if (nband < 1 || nband > numBands) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid band index (must use 1-based) for reclassarg of index %d . Returning original raster", i); - - 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); - } - - /* reclass expr */ - tupv = GetAttributeByName(tup, "reclassexpr", &isnull); - if (isnull) { - elog(NOTICE, "Invalid argument for reclassargset. Missing value of reclassexpr for reclassarg of index %d . Returning original raster", i); - - 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); - } - exprtext = (text *) DatumGetPointer(tupv); - if (NULL == exprtext) { - elog(NOTICE, "Invalid argument for reclassargset. Missing value of reclassexpr for reclassarg of index %d . Returning original raster", i); - - 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); - } - expr = text_to_cstring(exprtext); - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: expr (raw) %s", expr); - expr = rtpg_removespaces(expr); - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: expr (clean) %s", expr); - - /* split string to its components */ - /* comma (,) separating rangesets */ - comma_set = rtpg_strsplit(expr, ",", &comma_n); - if (comma_n < 1) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); - - 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 of reclass expressions */ - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: %d possible expressions", comma_n); - exprset = palloc(comma_n * sizeof(rt_reclassexpr)); - - for (a = 0, j = 0; a < comma_n; a++) { - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: map %s", comma_set[a]); - - /* colon (:) separating range "src" and "dst" */ - colon_set = rtpg_strsplit(comma_set[a], ":", &colon_n); - if (colon_n != 2) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - 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); - } - - /* allocate mem for reclass expression */ - exprset[j] = palloc(sizeof(struct rt_reclassexpr_t)); - - for (b = 0; b < colon_n; b++) { - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: range %s", colon_set[b]); - - /* dash (-) separating "min" and "max" */ - dash_set = rtpg_strsplit(colon_set[b], "-", &dash_n); - if (dash_n < 1 || dash_n > 3) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - 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); - } - - for (c = 0; c < dash_n; c++) { - /* need to handle: (-9999-100 -> "(", "9999", "100" */ - if ( - c < 1 && - strlen(dash_set[c]) == 1 && ( - strchr(dash_set[c], '(') != NULL || - strchr(dash_set[c], '[') != NULL || - strchr(dash_set[c], ')') != NULL || - strchr(dash_set[c], ']') != NULL - ) - ) { - junk = palloc(sizeof(char) * (strlen(dash_set[c + 1]) + 2)); - if (NULL == junk) { - for (k = 0; k <= j; k++) pfree(exprset[k]); - pfree(exprset); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - elog(ERROR, "RASTER_reclass: Could not allocate memory"); - PG_RETURN_NULL(); - } - - sprintf(junk, "%s%s", dash_set[c], dash_set[c + 1]); - c++; - dash_set[c] = repalloc(dash_set[c], sizeof(char) * (strlen(junk) + 1)); - strcpy(dash_set[c], junk); - pfree(junk); - - /* rebuild dash_set */ - for (k = 1; k < dash_n; k++) { - dash_set[k - 1] = repalloc(dash_set[k - 1], (strlen(dash_set[k]) + 1) * sizeof(char)); - strcpy(dash_set[k - 1], dash_set[k]); - } - dash_n--; - c--; - pfree(dash_set[dash_n]); - dash_set = repalloc(dash_set, sizeof(char *) * dash_n); - } - - /* there shouldn't be more than two in dash_n */ - if (c < 1 && dash_n > 2) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - 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); - } - - /* check interval flags */ - exc_val = 0; - inc_val = 1; - /* range */ - if (dash_n != 1) { - /* min */ - if (c < 1) { - if ( - strchr(dash_set[c], ')') != NULL || - strchr(dash_set[c], ']') != NULL - ) { - exc_val = 1; - inc_val = 1; - } - else if (strchr(dash_set[c], '(') != NULL){ - inc_val = 0; - } - else { - inc_val = 1; - } - } - /* max */ - else { - if ( - strrchr(dash_set[c], '(') != NULL || - strrchr(dash_set[c], '[') != NULL - ) { - exc_val = 1; - inc_val = 0; - } - else if (strrchr(dash_set[c], ']') != NULL) { - inc_val = 1; - } - else { - inc_val = 0; - } - } - } - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: exc_val %d inc_val %d", exc_val, inc_val); - - /* remove interval flags */ - dash_set[c] = rtpg_chartrim(dash_set[c], "()[]"); - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (char) %s", dash_set[c]); - - /* value from string to double */ - errno = 0; - val = strtod(dash_set[c], &junk); - if (errno != 0 || dash_set[c] == junk) { - elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - 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); - } - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (double) %f", val); - - /* strsplit removes dash (a.k.a. negative sign), compare now to restore */ - junk = strstr(colon_set[b], dash_set[c]); - /* not beginning of string */ - if (junk != colon_set[b]) { - /* prior is a dash */ - if (*(junk - 1) == '-') { - /* prior is beginning of string or prior - 1 char is dash, negative number */ - if ( - ((junk - 1) == colon_set[b]) || - (*(junk - 2) == '-') || - (*(junk - 2) == '[') || - (*(junk - 2) == '(') - ) { - val *= -1.; - } - } - } - POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (double) %f", val); - - /* src */ - if (b < 1) { - /* singular value */ - if (dash_n == 1) { - exprset[j]->src.exc_min = exprset[j]->src.exc_max = exc_val; - exprset[j]->src.inc_min = exprset[j]->src.inc_max = inc_val; - exprset[j]->src.min = exprset[j]->src.max = val; - } - /* min */ - else if (c < 1) { - exprset[j]->src.exc_min = exc_val; - exprset[j]->src.inc_min = inc_val; - exprset[j]->src.min = val; - } - /* max */ - else { - exprset[j]->src.exc_max = exc_val; - exprset[j]->src.inc_max = inc_val; - exprset[j]->src.max = val; - } - } - /* dst */ - else { - /* singular value */ - if (dash_n == 1) - exprset[j]->dst.min = exprset[j]->dst.max = val; - /* min */ - else if (c < 1) - exprset[j]->dst.min = val; - /* max */ - else - exprset[j]->dst.max = val; - } - } - pfree(dash_set); - } - pfree(colon_set); - - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: or: %f - %f nr: %f - %f" - , exprset[j]->src.min - , exprset[j]->src.max - , exprset[j]->dst.min - , exprset[j]->dst.max - ); - j++; - } - pfree(comma_set); - - /* pixel type */ - tupv = GetAttributeByName(tup, "pixeltype", &isnull); - if (isnull) { - elog(NOTICE, "Invalid argument for reclassargset. Missing value of pixeltype for reclassarg of index %d . Returning original raster", i); - - 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); - } - pixeltypetext = (text *) DatumGetPointer(tupv); - if (NULL == pixeltypetext) { - elog(NOTICE, "Invalid argument for reclassargset. Missing value of pixeltype for reclassarg of index %d . Returning original raster", i); - - 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); - } - pixeltype = text_to_cstring(pixeltypetext); - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: pixeltype %s", pixeltype); - pixtype = rt_pixtype_index_from_name(pixeltype); - - /* nodata */ - tupv = GetAttributeByName(tup, "nodataval", &isnull); - if (isnull) { - nodataval = 0; - hasnodata = FALSE; - } - else { - nodataval = DatumGetFloat8(tupv); - hasnodata = TRUE; - } - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: nodataval %f", nodataval); - POSTGIS_RT_DEBUGF(3, "RASTER_reclass: hasnodata %d", hasnodata); - - /* do reclass */ - band = rt_raster_get_band(raster, nband - 1); - if (!band) { - elog(NOTICE, "Could not find raster band of index %d. Returning original raster", nband); - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - 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); - } - newband = rt_band_reclass(band, pixtype, hasnodata, nodataval, exprset, j); - if (!newband) { - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - elog(ERROR, "RASTER_reclass: Could not reclassify raster band of index %d", nband); - PG_RETURN_NULL(); - } - - /* replace old band with new band */ - if (rt_raster_replace_band(raster, newband, nband - 1) == NULL) { - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - - rt_band_destroy(newband); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - elog(ERROR, "RASTER_reclass: Could not replace raster band of index %d with reclassified band", nband); - PG_RETURN_NULL(); - } - - /* old band is in the variable band */ - rt_band_destroy(band); - - /* free exprset */ - for (k = 0; k < j; k++) pfree(exprset[k]); - pfree(exprset); - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (!pgrtn) - PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "RASTER_reclass: Finished"); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/* ---------------------------------------------------------------- */ -/* apply colormap to specified band of a raster */ -/* ---------------------------------------------------------------- */ - -typedef struct rtpg_colormap_arg_t *rtpg_colormap_arg; -struct rtpg_colormap_arg_t { - rt_raster raster; - int nband; /* 1-based */ - rt_band band; - rt_bandstats bandstats; - - rt_colormap colormap; - int nodataentry; - - char **entry; - int nentry; - char **element; - int nelement; -}; - -static rtpg_colormap_arg -rtpg_colormap_arg_init() { - rtpg_colormap_arg arg = NULL; - - arg = palloc(sizeof(struct rtpg_colormap_arg_t)); - if (arg == NULL) { - elog(ERROR, "rtpg_colormap_arg: Could not allocate memory for function arguments"); - return NULL; - } - - arg->raster = NULL; - arg->nband = 1; - arg->band = NULL; - arg->bandstats = NULL; - - arg->colormap = palloc(sizeof(struct rt_colormap_t)); - if (arg->colormap == NULL) { - elog(ERROR, "rtpg_colormap_arg: Could not allocate memory for function arguments"); - return NULL; - } - arg->colormap->nentry = 0; - arg->colormap->entry = NULL; - arg->colormap->ncolor = 4; /* assume RGBA */ - arg->colormap->method = CM_INTERPOLATE; - arg->nodataentry = -1; - - arg->entry = NULL; - arg->nentry = 0; - arg->element = NULL; - arg->nelement = 0; - - return arg; -} - -static void -rtpg_colormap_arg_destroy(rtpg_colormap_arg arg) { - int i = 0; - if (arg->raster != NULL) - rt_raster_destroy(arg->raster); - - if (arg->bandstats != NULL) - pfree(arg->bandstats); - - if (arg->colormap != NULL) { - if (arg->colormap->entry != NULL) - pfree(arg->colormap->entry); - pfree(arg->colormap); - } - - if (arg->nentry) { - for (i = 0; i < arg->nentry; i++) { - if (arg->entry[i] != NULL) - pfree(arg->entry[i]); - } - pfree(arg->entry); - } - - if (arg->nelement) { - for (i = 0; i < arg->nelement; i++) - pfree(arg->element[i]); - pfree(arg->element); - } - - pfree(arg); - arg = NULL; -} - -PG_FUNCTION_INFO_V1(RASTER_colorMap); -Datum RASTER_colorMap(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rtpg_colormap_arg arg = NULL; - char *junk = NULL; - rt_raster raster = NULL; - - POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Starting"); - - /* pgraster is NULL, return NULL */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - /* init arg */ - arg = rtpg_colormap_arg_init(); - if (arg == NULL) { - elog(ERROR, "RASTER_colorMap: Could not initialize argument structure"); - PG_RETURN_NULL(); - } - - /* raster (0) */ - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster object */ - arg->raster = rt_raster_deserialize(pgraster, FALSE); - if (!arg->raster) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* nband (1) */ - if (!PG_ARGISNULL(1)) - arg->nband = PG_GETARG_INT32(1); - POSTGIS_RT_DEBUGF(4, "nband = %d", arg->nband); - - /* check that band works */ - if (!rt_raster_has_band(arg->raster, arg->nband - 1)) { - elog(NOTICE, "Raster does not have band at index %d. Returning empty raster", arg->nband); - - raster = rt_raster_clone(arg->raster, 0); - if (raster == NULL) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not create empty raster"); - PG_RETURN_NULL(); - } - - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (pgraster == NULL) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size); - PG_RETURN_POINTER(pgraster); - } - - /* get band */ - arg->band = rt_raster_get_band(arg->raster, arg->nband - 1); - if (arg->band == NULL) { - int nband = arg->nband; - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not get band at index %d", nband); - PG_RETURN_NULL(); - } - - /* method (3) */ - if (!PG_ARGISNULL(3)) { - char *method = NULL; - char *tmp = text_to_cstring(PG_GETARG_TEXT_P(3)); - POSTGIS_RT_DEBUGF(4, "raw method = %s", tmp); - - method = rtpg_trim(tmp); - pfree(tmp); - method = rtpg_strtoupper(method); - - if (strcmp(method, "INTERPOLATE") == 0) - arg->colormap->method = CM_INTERPOLATE; - else if (strcmp(method, "EXACT") == 0) - arg->colormap->method = CM_EXACT; - else if (strcmp(method, "NEAREST") == 0) - arg->colormap->method = CM_NEAREST; - else { - elog(NOTICE, "Unknown value provided for method. Defaulting to INTERPOLATE"); - arg->colormap->method = CM_INTERPOLATE; - } - } - /* default to INTERPOLATE */ - else - arg->colormap->method = CM_INTERPOLATE; - POSTGIS_RT_DEBUGF(4, "method = %d", arg->colormap->method); - - /* colormap (2) */ - if (PG_ARGISNULL(2)) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Value must be provided for colormap"); - PG_RETURN_NULL(); - } - else { - char *tmp = NULL; - char *colormap = text_to_cstring(PG_GETARG_TEXT_P(2)); - char *_entry; - char *_element; - int i = 0; - int j = 0; - - POSTGIS_RT_DEBUGF(4, "colormap = %s", colormap); - - /* empty string */ - if (!strlen(colormap)) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Value must be provided for colormap"); - PG_RETURN_NULL(); - } - - arg->entry = rtpg_strsplit(colormap, "\n", &(arg->nentry)); - pfree(colormap); - if (arg->nentry < 1) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not process the value provided for colormap"); - PG_RETURN_NULL(); - } - - /* allocate the max # of colormap entries */ - arg->colormap->entry = palloc(sizeof(struct rt_colormap_entry_t) * arg->nentry); - if (arg->colormap->entry == NULL) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not allocate memory for colormap entries"); - PG_RETURN_NULL(); - } - memset(arg->colormap->entry, 0, sizeof(struct rt_colormap_entry_t) * arg->nentry); - - /* each entry */ - for (i = 0; i < arg->nentry; i++) { - /* substitute space for other delimiters */ - tmp = rtpg_strreplace(arg->entry[i], ":", " ", NULL); - _entry = rtpg_strreplace(tmp, ",", " ", NULL); - pfree(tmp); - tmp = rtpg_strreplace(_entry, "\t", " ", NULL); - pfree(_entry); - _entry = rtpg_trim(tmp); - pfree(tmp); - - POSTGIS_RT_DEBUGF(4, "Processing entry[%d] = %s", i, arg->entry[i]); - POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d] = %s", i, _entry); - - /* empty entry, continue */ - if (!strlen(_entry)) { - POSTGIS_RT_DEBUGF(3, "Skipping empty entry[%d]", i); - pfree(_entry); - continue; - } - - arg->element = rtpg_strsplit(_entry, " ", &(arg->nelement)); - pfree(_entry); - if (arg->nelement < 2) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not process colormap entry %d", i + 1); - PG_RETURN_NULL(); - } - else if (arg->nelement > 5) { - elog(NOTICE, "More than five elements in colormap entry %d. Using at most five elements", i + 1); - arg->nelement = 5; - } - - /* smallest # of colors */ - if ((arg->nelement - 1) < arg->colormap->ncolor) - arg->colormap->ncolor = arg->nelement - 1; - - /* each element of entry */ - for (j = 0; j < arg->nelement; j++) { - - _element = rtpg_trim(arg->element[j]); - _element = rtpg_strtoupper(_element); - POSTGIS_RT_DEBUGF(4, "Processing entry[%d][%d] = %s", i, j, arg->element[j]); - POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d][%d] = %s", i, j, _element); - - /* first element is ALWAYS a band value, percentage OR "nv" string */ - if (j == 0) { - char *percent = NULL; - - /* NODATA */ - if ( - strcmp(_element, "NV") == 0 || - strcmp(_element, "NULL") == 0 || - strcmp(_element, "NODATA") == 0 - ) { - POSTGIS_RT_DEBUG(4, "Processing NODATA string"); - - if (arg->nodataentry > -1) { - elog(NOTICE, "More than one NODATA entry found. Using only the first one"); - } - else { - arg->colormap->entry[arg->colormap->nentry].isnodata = 1; - /* no need to set value as value comes from band's NODATA */ - arg->colormap->entry[arg->colormap->nentry].value = 0; - } - } - /* percent value */ - else if ((percent = strchr(_element, '%')) != NULL) { - double value; - POSTGIS_RT_DEBUG(4, "Processing percent string"); - - /* get the band stats */ - if (arg->bandstats == NULL) { - POSTGIS_RT_DEBUG(4, "Getting band stats"); - - arg->bandstats = rt_band_get_summary_stats(arg->band, 1, 1, 0, NULL, NULL, NULL); - if (arg->bandstats == NULL) { - pfree(_element); - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not get band's summary stats to process percentages"); - PG_RETURN_NULL(); - } - } - - /* get the string before the percent char */ - tmp = palloc(sizeof(char) * (percent - _element + 1)); - if (tmp == NULL) { - pfree(_element); - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not allocate memory for value of percentage"); - PG_RETURN_NULL(); - } - - memcpy(tmp, _element, percent - _element); - tmp[percent - _element] = '\0'; - POSTGIS_RT_DEBUGF(4, "Percent value = %s", tmp); - - /* get percentage value */ - errno = 0; - value = strtod(tmp, NULL); - pfree(tmp); - if (errno != 0 || _element == junk) { - pfree(_element); - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not process percent string to value"); - PG_RETURN_NULL(); - } - - /* check percentage */ - if (value < 0.) { - elog(NOTICE, "Percentage values cannot be less than zero. Defaulting to zero"); - value = 0.; - } - else if (value > 100.) { - elog(NOTICE, "Percentage values cannot be greater than 100. Defaulting to 100"); - value = 100.; - } - - /* get the true pixel value */ - /* TODO: should the percentage be quantile based? */ - arg->colormap->entry[arg->colormap->nentry].value = ((value / 100.) * (arg->bandstats->max - arg->bandstats->min)) + arg->bandstats->min; - } - /* straight value */ - else { - errno = 0; - arg->colormap->entry[arg->colormap->nentry].value = strtod(_element, &junk); - if (errno != 0 || _element == junk) { - pfree(_element); - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not process string to value"); - PG_RETURN_NULL(); - } - } - - } - /* RGB values (0 - 255) */ - else { - int value = 0; - - errno = 0; - value = (int) strtod(_element, &junk); - if (errno != 0 || _element == junk) { - pfree(_element); - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not process string to value"); - PG_RETURN_NULL(); - } - - if (value > 255) { - elog(NOTICE, "RGBA value cannot be greater than 255. Defaulting to 255"); - value = 255; - } - else if (value < 0) { - elog(NOTICE, "RGBA value cannot be less than zero. Defaulting to zero"); - value = 0; - } - arg->colormap->entry[arg->colormap->nentry].color[j - 1] = value; - } - - pfree(_element); - } - - POSTGIS_RT_DEBUGF(4, "colormap->entry[%d] (isnodata, value, R, G, B, A) = (%d, %f, %d, %d, %d, %d)", - arg->colormap->nentry, - arg->colormap->entry[arg->colormap->nentry].isnodata, - arg->colormap->entry[arg->colormap->nentry].value, - arg->colormap->entry[arg->colormap->nentry].color[0], - arg->colormap->entry[arg->colormap->nentry].color[1], - arg->colormap->entry[arg->colormap->nentry].color[2], - arg->colormap->entry[arg->colormap->nentry].color[3] - ); - - arg->colormap->nentry++; - } - - POSTGIS_RT_DEBUGF(4, "colormap->nentry = %d", arg->colormap->nentry); - POSTGIS_RT_DEBUGF(4, "colormap->ncolor = %d", arg->colormap->ncolor); - } - - /* call colormap */ - raster = rt_raster_colormap(arg->raster, arg->nband - 1, arg->colormap); - if (raster == NULL) { - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_colorMap: Could not create new raster with applied colormap"); - PG_RETURN_NULL(); - } - - rtpg_colormap_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - - POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Done"); - - if (pgraster == NULL) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size); - PG_RETURN_POINTER(pgraster); -} - -/* ---------------------------------------------------------------- */ -/* Returns raster from GDAL raster */ -/* ---------------------------------------------------------------- */ -PG_FUNCTION_INFO_V1(RASTER_fromGDALRaster); -Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS) -{ - bytea *bytea_data; - uint8_t *data; - int data_len = 0; - VSILFILE *vsifp = NULL; - GDALDatasetH hdsSrc; - int srid = -1; /* -1 for NULL */ - - rt_pgraster *pgraster = NULL; - rt_raster raster; - - /* NULL if NULL */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - /* get data */ - bytea_data = (bytea *) PG_GETARG_BYTEA_P(0); - data = (uint8_t *) VARDATA(bytea_data); - data_len = VARSIZE(bytea_data) - VARHDRSZ; - - /* process srid */ - /* NULL srid means try to determine SRID from bytea */ - if (!PG_ARGISNULL(1)) - srid = clamp_srid(PG_GETARG_INT32(1)); - - /* create memory "file" */ - vsifp = VSIFileFromMemBuffer("/vsimem/in.dat", data, data_len, FALSE); - if (vsifp == NULL) { - PG_FREE_IF_COPY(bytea_data, 0); - elog(ERROR, "RASTER_fromGDALRaster: Could not load bytea into memory file for use by GDAL"); - PG_RETURN_NULL(); - } - - /* register all GDAL drivers */ - rt_util_gdal_register_all(); - - /* open GDAL raster */ - hdsSrc = GDALOpenShared("/vsimem/in.dat", GA_ReadOnly); - if (hdsSrc == NULL) { - VSIFCloseL(vsifp); - PG_FREE_IF_COPY(bytea_data, 0); - elog(ERROR, "RASTER_fromGDALRaster: Could not open bytea with GDAL. Check that the bytea is of a GDAL supported format"); - PG_RETURN_NULL(); - } - -#if POSTGIS_DEBUG_LEVEL > 3 - { - GDALDriverH hdrv = GDALGetDatasetDriver(hdsSrc); - - POSTGIS_RT_DEBUGF(4, "Input GDAL Raster info: %s, (%d x %d)", - GDALGetDriverShortName(hdrv), - GDALGetRasterXSize(hdsSrc), - GDALGetRasterYSize(hdsSrc) - ); - } -#endif - - /* convert GDAL raster to raster */ - raster = rt_raster_from_gdal_dataset(hdsSrc); - - GDALClose(hdsSrc); - VSIFCloseL(vsifp); - PG_FREE_IF_COPY(bytea_data, 0); - - if (raster == NULL) { - elog(ERROR, "RASTER_fromGDALRaster: Could not convert GDAL raster to raster"); - PG_RETURN_NULL(); - } - - /* apply SRID if set */ - if (srid != -1) - rt_raster_set_srid(raster, srid); - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgraster) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); -} - -/** - * Returns formatted GDAL raster as bytea object of raster - */ -PG_FUNCTION_INFO_V1(RASTER_asGDALRaster); -Datum RASTER_asGDALRaster(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster; - - text *formattext = NULL; - char *format = NULL; - char **options = NULL; - text *optiontext = NULL; - char *option = NULL; - int srid = SRID_UNKNOWN; - char *srs = NULL; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - int i = 0; - int j = 0; - - uint8_t *gdal = NULL; - uint64_t gdal_size = 0; - bytea *result = NULL; - uint64_t result_size = 0; - - POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Starting"); - - /* pgraster is null, return null */ - 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_asGDALRaster: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* format is required */ - if (PG_ARGISNULL(1)) { - elog(NOTICE, "Format must be provided"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - else { - formattext = PG_GETARG_TEXT_P(1); - format = text_to_cstring(formattext); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: Arg 1 (format) is %s", format); - - /* process options */ - if (!PG_ARGISNULL(2)) { - POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Processing Arg 2 (options)"); - array = PG_GETARG_ARRAYTYPE_P(2); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case TEXTOID: - break; - default: - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_asGDALRaster: Invalid data type for options"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (n) { - options = (char **) palloc(sizeof(char *) * (n + 1)); - if (options == NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_asGDALRaster: Could not allocate memory for options"); - PG_RETURN_NULL(); - } - - /* clean each option */ - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - option = NULL; - switch (etype) { - case TEXTOID: - optiontext = (text *) DatumGetPointer(e[i]); - if (NULL == optiontext) break; - option = text_to_cstring(optiontext); - - /* trim string */ - option = rtpg_trim(option); - POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: option is '%s'", option); - break; - } - - if (strlen(option)) { - options[j] = (char *) palloc(sizeof(char) * (strlen(option) + 1)); - options[j] = option; - j++; - } - } - - if (j > 0) { - /* trim allocation */ - options = repalloc(options, (j + 1) * sizeof(char *)); - - /* add NULL to end */ - options[j] = NULL; - - } - else { - pfree(options); - options = NULL; - } - } - } - - /* process srid */ - /* NULL srid means use raster's srid */ - if (PG_ARGISNULL(3)) - srid = rt_raster_get_srid(raster); - else - srid = PG_GETARG_INT32(3); - - /* get srs from srid */ - if (clamp_srid(srid) != SRID_UNKNOWN) { - srs = rtpg_getSR(srid); - if (NULL == srs) { - if (NULL != options) { - for (i = j - 1; i >= 0; i--) pfree(options[i]); - pfree(options); - } - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_asGDALRaster: Could not find srtext for SRID (%d)", srid); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: Arg 3 (srs) is %s", srs); - } - else - srs = NULL; - - POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Generating GDAL raster"); - gdal = rt_raster_to_gdal(raster, srs, format, options, &gdal_size); - - /* free memory */ - if (NULL != options) { - for (i = j - 1; i >= 0; i--) pfree(options[i]); - pfree(options); - } - if (NULL != srs) pfree(srs); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - if (!gdal) { - elog(ERROR, "RASTER_asGDALRaster: Could not allocate and generate GDAL raster"); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: GDAL raster generated with %d bytes", (int) gdal_size); - - /* result is a varlena */ - result_size = gdal_size + VARHDRSZ; - result = (bytea *) palloc(result_size); - if (NULL == result) { - elog(ERROR, "RASTER_asGDALRaster: Insufficient virtual memory for GDAL raster"); - PG_RETURN_NULL(); - } - SET_VARSIZE(result, result_size); - memcpy(VARDATA(result), gdal, VARSIZE(result) - VARHDRSZ); - - /* for test output - FILE *fh = NULL; - fh = fopen("/tmp/out.dat", "w"); - fwrite(gdal, sizeof(uint8_t), gdal_size, fh); - fclose(fh); - */ - - /* free gdal mem buffer */ - if (gdal) CPLFree(gdal); - - POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Returning pointer to GDAL raster"); - PG_RETURN_POINTER(result); -} - -/** - * Returns available GDAL drivers - */ -PG_FUNCTION_INFO_V1(RASTER_getGDALDrivers); -Datum RASTER_getGDALDrivers(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - - uint32_t drv_count; - rt_gdaldriver drv_set; - rt_gdaldriver drv_set2; - int call_cntr; - int max_calls; - - /* first call of function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - /* 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); - - drv_set = rt_raster_gdal_drivers(&drv_count, 1); - if (NULL == drv_set || !drv_count) { - elog(NOTICE, "No GDAL drivers found"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - POSTGIS_RT_DEBUGF(3, "%d drivers returned", (int) drv_count); - - /* Store needed information */ - funcctx->user_fctx = drv_set; - - /* total number of tuples to be returned */ - funcctx->max_calls = drv_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; - drv_set2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 4; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = Int32GetDatum(drv_set2[call_cntr].idx); - values[1] = CStringGetTextDatum(drv_set2[call_cntr].short_name); - values[2] = CStringGetTextDatum(drv_set2[call_cntr].long_name); - values[3] = CStringGetTextDatum(drv_set2[call_cntr].create_options); - - POSTGIS_RT_DEBUGF(4, "Result %d, Index %d", call_cntr, drv_set2[call_cntr].idx); - POSTGIS_RT_DEBUGF(4, "Result %d, Short Name %s", call_cntr, drv_set2[call_cntr].short_name); - POSTGIS_RT_DEBUGF(4, "Result %d, Full Name %s", call_cntr, drv_set2[call_cntr].long_name); - POSTGIS_RT_DEBUGF(5, "Result %d, Create Options %s", call_cntr, drv_set2[call_cntr].create_options); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - /* clean up */ - pfree(drv_set2[call_cntr].short_name); - pfree(drv_set2[call_cntr].long_name); - pfree(drv_set2[call_cntr].create_options); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(drv_set2); - SRF_RETURN_DONE(funcctx); - } -} - -/** - * Rasterize a geometry - */ -PG_FUNCTION_INFO_V1(RASTER_asRaster); -Datum RASTER_asRaster(PG_FUNCTION_ARGS) -{ - GSERIALIZED *gser = NULL; - - LWGEOM *geom = NULL; - rt_raster rast = NULL; - rt_pgraster *pgrast = NULL; - - unsigned char *wkb; - size_t wkb_len = 0; - unsigned char variant = WKB_SFSQL; - - double scale[2] = {0}; - double *scale_x = NULL; - double *scale_y = NULL; - - int dim[2] = {0}; - int *dim_x = NULL; - int *dim_y = NULL; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - int i = 0; - int j = 0; - int haserr = 0; - - text *pixeltypetext = NULL; - char *pixeltype = NULL; - rt_pixtype pixtype = PT_END; - rt_pixtype *pixtypes = NULL; - int pixtypes_len = 0; - - double *values = NULL; - int values_len = 0; - - uint8_t *hasnodatas = NULL; - double *nodatavals = NULL; - int nodatavals_len = 0; - - double ulw[2] = {0}; - double *ul_xw = NULL; - double *ul_yw = NULL; - - double gridw[2] = {0}; - double *grid_xw = NULL; - double *grid_yw = NULL; - - double skew[2] = {0}; - double *skew_x = NULL; - double *skew_y = NULL; - - char **options = NULL; - int options_len = 0; - - uint32_t num_bands = 0; - - int srid = SRID_UNKNOWN; - char *srs = NULL; - - POSTGIS_RT_DEBUG(3, "RASTER_asRaster: Starting"); - - /* based upon LWGEOM_asBinary function in postgis/lwgeom_ogc.c */ - - /* Get the geometry */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - gser = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - geom = lwgeom_from_gserialized(gser); - - /* Get a 2D version of the geometry if necessary */ - if (lwgeom_ndims(geom) > 2) { - LWGEOM *geom2d = lwgeom_force_2d(geom); - lwgeom_free(geom); - geom = geom2d; - } - - /* empty geometry, return empty raster */ - if (lwgeom_is_empty(geom)) { - POSTGIS_RT_DEBUG(3, "Input geometry is empty. Returning empty raster"); - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - rast = rt_raster_new(0, 0); - if (rast == NULL) - PG_RETURN_NULL(); - - pgrast = rt_raster_serialize(rast); - rt_raster_destroy(rast); - - if (NULL == pgrast) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrast, pgrast->size); - PG_RETURN_POINTER(pgrast); - } - - /* scale x */ - if (!PG_ARGISNULL(1)) { - scale[0] = PG_GETARG_FLOAT8(1); - if (FLT_NEQ(scale[0], 0)) scale_x = &scale[0]; - } - - /* scale y */ - if (!PG_ARGISNULL(2)) { - scale[1] = PG_GETARG_FLOAT8(2); - if (FLT_NEQ(scale[1], 0)) scale_y = &scale[1]; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: scale (x, y) = %f, %f", scale[0], scale[1]); - - /* width */ - if (!PG_ARGISNULL(3)) { - dim[0] = PG_GETARG_INT32(3); - if (dim[0] < 0) dim[0] = 0; - if (dim[0] != 0) dim_x = &dim[0]; - } - - /* height */ - if (!PG_ARGISNULL(4)) { - dim[1] = PG_GETARG_INT32(4); - if (dim[1] < 0) dim[1] = 0; - if (dim[1] != 0) dim_y = &dim[1]; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: dim (x, y) = %d, %d", dim[0], dim[1]); - - /* pixeltype */ - if (!PG_ARGISNULL(5)) { - array = PG_GETARG_ARRAYTYPE_P(5); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case TEXTOID: - break; - default: - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - elog(ERROR, "RASTER_asRaster: Invalid data type for pixeltype"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (n) { - pixtypes = (rt_pixtype *) palloc(sizeof(rt_pixtype) * n); - /* clean each pixeltype */ - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) { - pixtypes[j++] = PT_64BF; - continue; - } - - pixeltype = NULL; - switch (etype) { - case TEXTOID: - pixeltypetext = (text *) DatumGetPointer(e[i]); - if (NULL == pixeltypetext) break; - pixeltype = text_to_cstring(pixeltypetext); - - /* trim string */ - pixeltype = rtpg_trim(pixeltype); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixeltype is '%s'", pixeltype); - break; - } - - if (strlen(pixeltype)) { - pixtype = rt_pixtype_index_from_name(pixeltype); - if (pixtype == PT_END) { - - pfree(pixtypes); - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - elog(ERROR, "RASTER_asRaster: Invalid pixel type provided: %s", pixeltype); - PG_RETURN_NULL(); - } - - pixtypes[j] = pixtype; - j++; - } - } - - if (j > 0) { - /* trim allocation */ - pixtypes = repalloc(pixtypes, j * sizeof(rt_pixtype)); - pixtypes_len = j; - } - else { - pfree(pixtypes); - pixtypes = NULL; - pixtypes_len = 0; - } - } - } -#if POSTGIS_DEBUG_LEVEL > 0 - for (i = 0; i < pixtypes_len; i++) - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixtypes[%d] = %d", i, (int) pixtypes[i]); -#endif - - /* value */ - if (!PG_ARGISNULL(6)) { - array = PG_GETARG_ARRAYTYPE_P(6); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - - if (pixtypes_len) pfree(pixtypes); - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - elog(ERROR, "RASTER_asRaster: Invalid data type for value"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (n) { - values = (double *) palloc(sizeof(double) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) { - values[j++] = 1; - continue; - } - - switch (etype) { - case FLOAT4OID: - values[j] = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - values[j] = (double) DatumGetFloat8(e[i]); - break; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values[%d] = %f", j, values[j]); - - j++; - } - - if (j > 0) { - /* trim allocation */ - values = repalloc(values, j * sizeof(double)); - values_len = j; - } - else { - pfree(values); - values = NULL; - values_len = 0; - } - } - } -#if POSTGIS_DEBUG_LEVEL > 0 - for (i = 0; i < values_len; i++) - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values[%d] = %f", i, values[i]); -#endif - - /* nodataval */ - if (!PG_ARGISNULL(7)) { - array = PG_GETARG_ARRAYTYPE_P(7); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - - if (pixtypes_len) pfree(pixtypes); - if (values_len) pfree(values); - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - elog(ERROR, "RASTER_asRaster: Invalid data type for nodataval"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - if (n) { - nodatavals = (double *) palloc(sizeof(double) * n); - hasnodatas = (uint8_t *) palloc(sizeof(uint8_t) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) { - hasnodatas[j] = 0; - nodatavals[j] = 0; - j++; - continue; - } - - hasnodatas[j] = 1; - switch (etype) { - case FLOAT4OID: - nodatavals[j] = (double) DatumGetFloat4(e[i]); - break; - case FLOAT8OID: - nodatavals[j] = (double) DatumGetFloat8(e[i]); - break; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: hasnodatas[%d] = %d", j, hasnodatas[j]); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals[%d] = %f", j, nodatavals[j]); - - j++; - } - - if (j > 0) { - /* trim allocation */ - nodatavals = repalloc(nodatavals, j * sizeof(double)); - hasnodatas = repalloc(hasnodatas, j * sizeof(uint8_t)); - nodatavals_len = j; - } - else { - pfree(nodatavals); - pfree(hasnodatas); - nodatavals = NULL; - hasnodatas = NULL; - nodatavals_len = 0; - } - } - } -#if POSTGIS_DEBUG_LEVEL > 0 - for (i = 0; i < nodatavals_len; i++) { - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: hasnodatas[%d] = %d", i, hasnodatas[i]); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals[%d] = %f", i, nodatavals[i]); - } -#endif - - /* upperleftx */ - if (!PG_ARGISNULL(8)) { - ulw[0] = PG_GETARG_FLOAT8(8); - ul_xw = &ulw[0]; - } - - /* upperlefty */ - if (!PG_ARGISNULL(9)) { - ulw[1] = PG_GETARG_FLOAT8(9); - ul_yw = &ulw[1]; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: upperleft (x, y) = %f, %f", ulw[0], ulw[1]); - - /* gridx */ - if (!PG_ARGISNULL(10)) { - gridw[0] = PG_GETARG_FLOAT8(10); - grid_xw = &gridw[0]; - } - - /* gridy */ - if (!PG_ARGISNULL(11)) { - gridw[1] = PG_GETARG_FLOAT8(11); - grid_yw = &gridw[1]; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: grid (x, y) = %f, %f", gridw[0], gridw[1]); - - /* check dependent variables */ - haserr = 0; - do { - /* only part of scale provided */ - if ( - (scale_x == NULL && scale_y != NULL) || - (scale_x != NULL && scale_y == NULL) - ) { - elog(NOTICE, "Values must be provided for both X and Y of scale if one is specified"); - haserr = 1; - break; - } - - /* only part of dimension provided */ - if ( - (dim_x == NULL && dim_y != NULL) || - (dim_x != NULL && dim_y == NULL) - ) { - elog(NOTICE, "Values must be provided for both width and height if one is specified"); - haserr = 1; - break; - } - - /* scale and dimension provided */ - if ( - (scale_x != NULL && scale_y != NULL) && - (dim_x != NULL && dim_y != NULL) - ) { - elog(NOTICE, "Values provided for X and Y of scale and width and height. Using the width and height"); - scale_x = NULL; - scale_y = NULL; - break; - } - - /* neither scale or dimension provided */ - if ( - (scale_x == NULL && scale_y == NULL) && - (dim_x == NULL && dim_y == NULL) - ) { - elog(NOTICE, "Values must be provided for X and Y of scale or width and height"); - haserr = 1; - break; - } - - /* only part of upper-left provided */ - if ( - (ul_xw == NULL && ul_yw != NULL) || - (ul_xw != NULL && ul_yw == NULL) - ) { - elog(NOTICE, "Values must be provided for both X and Y when specifying the upper-left corner"); - haserr = 1; - break; - } - - /* only part of alignment provided */ - if ( - (grid_xw == NULL && grid_yw != NULL) || - (grid_xw != NULL && grid_yw == NULL) - ) { - elog(NOTICE, "Values must be provided for both X and Y when specifying the alignment"); - haserr = 1; - break; - } - - /* upper-left and alignment provided */ - if ( - (ul_xw != NULL && ul_yw != NULL) && - (grid_xw != NULL && grid_yw != NULL) - ) { - elog(NOTICE, "Values provided for both X and Y of upper-left corner and alignment. Using the values of upper-left corner"); - grid_xw = NULL; - grid_yw = NULL; - break; - } - } - while (0); - - if (haserr) { - if (pixtypes_len) pfree(pixtypes); - if (values_len) pfree(values); - if (nodatavals_len) { - pfree(nodatavals); - pfree(hasnodatas); - } - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - PG_RETURN_NULL(); - } - - /* skewx */ - if (!PG_ARGISNULL(12)) { - skew[0] = PG_GETARG_FLOAT8(12); - if (FLT_NEQ(skew[0], 0)) skew_x = &skew[0]; - } - - /* skewy */ - if (!PG_ARGISNULL(13)) { - skew[1] = PG_GETARG_FLOAT8(13); - if (FLT_NEQ(skew[1], 0)) skew_y = &skew[1]; - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: skew (x, y) = %f, %f", skew[0], skew[1]); - - /* all touched */ - if (!PG_ARGISNULL(14) && PG_GETARG_BOOL(14) == TRUE) { - if (options_len < 1) { - options_len = 1; - options = (char **) palloc(sizeof(char *) * options_len); - } - else { - options_len++; - options = (char **) repalloc(options, sizeof(char *) * options_len); - } - - options[options_len - 1] = palloc(sizeof(char*) * (strlen("ALL_TOUCHED=TRUE") + 1)); - options[options_len - 1] = "ALL_TOUCHED=TRUE"; - } - - if (options_len) { - options_len++; - options = (char **) repalloc(options, sizeof(char *) * options_len); - options[options_len - 1] = NULL; - } - - /* get geometry's srid */ - srid = gserialized_get_srid(gser); - - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: srid = %d", srid); - if (clamp_srid(srid) != SRID_UNKNOWN) { - srs = rtpg_getSR(srid); - if (NULL == srs) { - - if (pixtypes_len) pfree(pixtypes); - if (values_len) pfree(values); - if (nodatavals_len) { - pfree(hasnodatas); - pfree(nodatavals); - } - if (options_len) pfree(options); - - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - elog(ERROR, "RASTER_asRaster: Could not find srtext for SRID (%d)", srid); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: srs is %s", srs); - } - else - srs = NULL; - - /* determine number of bands */ - /* MIN macro is from GDAL's cpl_port.h */ - num_bands = MIN(pixtypes_len, values_len); - num_bands = MIN(num_bands, nodatavals_len); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixtypes_len = %d", pixtypes_len); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values_len = %d", values_len); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals_len = %d", nodatavals_len); - POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: num_bands = %d", num_bands); - - /* warn of imbalanced number of band elements */ - if (!( - (pixtypes_len == values_len) && - (values_len == nodatavals_len) - )) { - elog( - NOTICE, - "Imbalanced number of values provided for pixeltype (%d), value (%d) and nodataval (%d). Using the first %d values of each parameter", - pixtypes_len, - values_len, - nodatavals_len, - num_bands - ); - } - - /* get wkb of geometry */ - POSTGIS_RT_DEBUG(3, "RASTER_asRaster: getting wkb of geometry"); - wkb = lwgeom_to_wkb(geom, variant, &wkb_len); - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 0); - - /* rasterize geometry */ - POSTGIS_RT_DEBUG(3, "RASTER_asRaster: rasterizing geometry"); - /* use nodatavals for the init parameter */ - rast = rt_raster_gdal_rasterize(wkb, - (uint32_t) wkb_len, srs, - num_bands, pixtypes, - nodatavals, values, - nodatavals, hasnodatas, - dim_x, dim_y, - scale_x, scale_y, - ul_xw, ul_yw, - grid_xw, grid_yw, - skew_x, skew_y, - options - ); - - if (pixtypes_len) pfree(pixtypes); - if (values_len) pfree(values); - if (nodatavals_len) { - pfree(hasnodatas); - pfree(nodatavals); - } - if (options_len) pfree(options); - - if (!rast) { - elog(ERROR, "RASTER_asRaster: Could not rasterize geometry"); - PG_RETURN_NULL(); - } - - /* add target srid */ - rt_raster_set_srid(rast, srid); - - pgrast = rt_raster_serialize(rast); - rt_raster_destroy(rast); - - if (NULL == pgrast) PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "RASTER_asRaster: done"); - - SET_VARSIZE(pgrast, pgrast->size); - PG_RETURN_POINTER(pgrast); -} - -/** - * warp a raster using GDAL Warp API - */ -PG_FUNCTION_INFO_V1(RASTER_GDALWarp); -Datum RASTER_GDALWarp(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrast = NULL; - rt_raster raster = NULL; - rt_raster rast = NULL; - - text *algtext = NULL; - char *algchar = NULL; - GDALResampleAlg alg = GRA_NearestNeighbour; - double max_err = 0.125; - - int src_srid = SRID_UNKNOWN; - char *src_srs = NULL; - int dst_srid = SRID_UNKNOWN; - char *dst_srs = NULL; - int no_srid = 0; - - double scale[2] = {0}; - double *scale_x = NULL; - double *scale_y = NULL; - - double gridw[2] = {0}; - double *grid_xw = NULL; - double *grid_yw = NULL; - - double skew[2] = {0}; - double *skew_x = NULL; - double *skew_y = NULL; - - int dim[2] = {0}; - int *dim_x = NULL; - int *dim_y = NULL; - - POSTGIS_RT_DEBUG(3, "RASTER_GDALWarp: Starting"); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_GDALWarp: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* resampling algorithm */ - if (!PG_ARGISNULL(1)) { - algtext = PG_GETARG_TEXT_P(1); - algchar = rtpg_trim(rtpg_strtoupper(text_to_cstring(algtext))); - alg = rt_util_gdal_resample_alg(algchar); - } - POSTGIS_RT_DEBUGF(4, "Resampling algorithm: %d", alg); - - /* max error */ - if (!PG_ARGISNULL(2)) { - max_err = PG_GETARG_FLOAT8(2); - if (max_err < 0.) max_err = 0.; - } - POSTGIS_RT_DEBUGF(4, "max_err: %f", max_err); - - /* source SRID */ - src_srid = clamp_srid(rt_raster_get_srid(raster)); - POSTGIS_RT_DEBUGF(4, "source SRID: %d", src_srid); - - /* target SRID */ - if (!PG_ARGISNULL(3)) { - dst_srid = clamp_srid(PG_GETARG_INT32(3)); - if (dst_srid == SRID_UNKNOWN) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_GDALWarp: %d is an invalid target SRID", dst_srid); - PG_RETURN_NULL(); - } - } - else - dst_srid = src_srid; - POSTGIS_RT_DEBUGF(4, "destination SRID: %d", dst_srid); - - /* target SRID != src SRID, error */ - if (src_srid == SRID_UNKNOWN && dst_srid != src_srid) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_GDALWarp: Input raster has unknown (%d) SRID", src_srid); - PG_RETURN_NULL(); - } - /* target SRID == src SRID, no reprojection */ - else if (dst_srid == src_srid) { - no_srid = 1; - } - - /* scale x */ - if (!PG_ARGISNULL(4)) { - scale[0] = PG_GETARG_FLOAT8(4); - if (FLT_NEQ(scale[0], 0)) scale_x = &scale[0]; - } - - /* scale y */ - if (!PG_ARGISNULL(5)) { - scale[1] = PG_GETARG_FLOAT8(5); - if (FLT_NEQ(scale[1], 0)) scale_y = &scale[1]; - } - - /* grid alignment x */ - if (!PG_ARGISNULL(6)) { - gridw[0] = PG_GETARG_FLOAT8(6); - grid_xw = &gridw[0]; - } - - /* grid alignment y */ - if (!PG_ARGISNULL(7)) { - gridw[1] = PG_GETARG_FLOAT8(7); - grid_yw = &gridw[1]; - } - - /* skew x */ - if (!PG_ARGISNULL(8)) { - skew[0] = PG_GETARG_FLOAT8(8); - if (FLT_NEQ(skew[0], 0)) skew_x = &skew[0]; - } - - /* skew y */ - if (!PG_ARGISNULL(9)) { - skew[1] = PG_GETARG_FLOAT8(9); - if (FLT_NEQ(skew[1], 0)) skew_y = &skew[1]; - } - - /* width */ - if (!PG_ARGISNULL(10)) { - dim[0] = PG_GETARG_INT32(10); - if (dim[0] < 0) dim[0] = 0; - if (dim[0] > 0) dim_x = &dim[0]; - } - - /* height */ - if (!PG_ARGISNULL(11)) { - dim[1] = PG_GETARG_INT32(11); - if (dim[1] < 0) dim[1] = 0; - if (dim[1] > 0) dim_y = &dim[1]; - } - - /* check that at least something is to be done */ - if ( - (dst_srid == SRID_UNKNOWN) && - (scale_x == NULL) && - (scale_y == NULL) && - (grid_xw == NULL) && - (grid_yw == NULL) && - (skew_x == NULL) && - (skew_y == NULL) && - (dim_x == NULL) && - (dim_y == NULL) - ) { - elog(NOTICE, "No resampling parameters provided. Returning original raster"); - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - /* both values of alignment must be provided if any one is provided */ - else if ( - (grid_xw != NULL && grid_yw == NULL) || - (grid_xw == NULL && grid_yw != NULL) - ) { - elog(NOTICE, "Values must be provided for both X and Y when specifying the alignment. Returning original raster"); - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - /* both values of scale must be provided if any one is provided */ - else if ( - (scale_x != NULL && scale_y == NULL) || - (scale_x == NULL && scale_y != NULL) - ) { - elog(NOTICE, "Values must be provided for both X and Y when specifying the scale. Returning original raster"); - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - /* scale and width/height provided */ - else if ( - (scale_x != NULL || scale_y != NULL) && - (dim_x != NULL || dim_y != NULL) - ) { - elog(NOTICE, "Scale X/Y and width/height are mutually exclusive. Only provide one. Returning original raster"); - rt_raster_destroy(raster); - PG_RETURN_POINTER(pgraster); - } - - /* get srses from srids */ - if (!no_srid) { - /* source srs */ - src_srs = rtpg_getSR(src_srid); - if (NULL == src_srs) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_GDALWarp: Input raster has unknown SRID (%d)", src_srid); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(4, "src srs: %s", src_srs); - - dst_srs = rtpg_getSR(dst_srid); - if (NULL == dst_srs) { - if (!no_srid) pfree(src_srs); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_GDALWarp: Target SRID (%d) is unknown", dst_srid); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(4, "dst srs: %s", dst_srs); - } - - rast = rt_raster_gdal_warp( - raster, - src_srs, dst_srs, - scale_x, scale_y, - dim_x, dim_y, - NULL, NULL, - grid_xw, grid_yw, - skew_x, skew_y, - alg, max_err); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - if (!no_srid) { - pfree(src_srs); - pfree(dst_srs); - } - if (!rast) { - elog(ERROR, "RASTER_band: Could not create transformed raster"); - PG_RETURN_NULL(); - } - - /* add target SRID */ - rt_raster_set_srid(rast, dst_srid); - - pgrast = rt_raster_serialize(rast); - rt_raster_destroy(rast); - - if (NULL == pgrast) PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "RASTER_GDALWarp: done"); - - SET_VARSIZE(pgrast, pgrast->size); - PG_RETURN_POINTER(pgrast); -} - -/** - * Get raster's meta data - */ -PG_FUNCTION_INFO_V1(RASTER_metadata); -Datum RASTER_metadata(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - - uint32_t numBands; - double scaleX; - double scaleY; - double ipX; - double ipY; - double skewX; - double skewY; - int32_t srid; - uint32_t width; - uint32_t height; - - TupleDesc tupdesc; - int values_length = 10; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUG(3, "RASTER_metadata: Starting"); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_metadata; Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* upper left x, y */ - ipX = rt_raster_get_x_offset(raster); - ipY = rt_raster_get_y_offset(raster); - - /* width, height */ - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - - /* scale x, y */ - scaleX = rt_raster_get_x_scale(raster); - scaleY = rt_raster_get_y_scale(raster); - - /* skew x, y */ - skewX = rt_raster_get_x_skew(raster); - skewY = rt_raster_get_y_skew(raster); - - /* srid */ - srid = rt_raster_get_srid(raster); - - /* numbands */ - numBands = rt_raster_get_num_bands(raster); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* 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); - - values[0] = Float8GetDatum(ipX); - values[1] = Float8GetDatum(ipY); - values[2] = UInt32GetDatum(width); - values[3] = UInt32GetDatum(height); - values[4] = Float8GetDatum(scaleX); - values[5] = Float8GetDatum(scaleY); - values[6] = Float8GetDatum(skewX); - values[7] = Float8GetDatum(skewY); - values[8] = Int32GetDatum(srid); - values[9] = UInt32GetDatum(numBands); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - PG_RETURN_DATUM(result); -} - -/** - * Get raster bands' meta data - */ -PG_FUNCTION_INFO_V1(RASTER_bandmetadata); -Datum RASTER_bandmetadata(PG_FUNCTION_ARGS) -{ - FuncCallContext *funcctx; - TupleDesc tupdesc; - int call_cntr; - int max_calls; - - struct bandmetadata { - uint32_t bandnum; - char *pixeltype; - bool hasnodata; - double nodataval; - bool isoutdb; - char *bandpath; - }; - struct bandmetadata *bmd = NULL; - struct bandmetadata *bmd2 = NULL; - - HeapTuple tuple; - Datum result; - - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_band band = NULL; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int i = 0; - int j = 0; - int n = 0; - - uint32_t numBands; - uint32_t idx = 1; - uint32_t *bandNums = NULL; - const char *tmp = NULL; - - POSTGIS_RT_DEBUG(3, "RASTER_bandmetadata: Starting"); - - /* 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); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) { - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_bandmetadata: Could not deserialize raster"); - SRF_RETURN_DONE(funcctx); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(raster); - if (numBands < 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 */ - array = PG_GETARG_ARRAYTYPE_P(1); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case INT2OID: - case INT4OID: - break; - default: - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_bandmetadata: Invalid data type for band number(s)"); - SRF_RETURN_DONE(funcctx); - break; - } - - deconstruct_array(array, etype, typlen, typbyval, typalign, &e, - &nulls, &n); - - bandNums = palloc(sizeof(uint32_t) * n); - for (i = 0, j = 0; i < n; i++) { - if (nulls[i]) continue; - - switch (etype) { - case INT2OID: - idx = (uint32_t) DatumGetInt16(e[i]); - break; - case INT4OID: - idx = (uint32_t) DatumGetInt32(e[i]); - break; - } - - POSTGIS_RT_DEBUGF(3, "band idx (before): %d", idx); - if (idx > numBands || idx < 1) { - elog(NOTICE, "Invalid band index: %d. Indices must be 1-based. Returning NULL", idx); - pfree(bandNums); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - bandNums[j] = idx; - POSTGIS_RT_DEBUGF(3, "bandNums[%d] = %d", j, bandNums[j]); - j++; - } - - if (j < 1) { - j = numBands; - bandNums = repalloc(bandNums, sizeof(uint32_t) * j); - for (i = 0; i < j; i++) - bandNums[i] = i + 1; - } - else if (j < n) - bandNums = repalloc(bandNums, sizeof(uint32_t) * j); - - bmd = (struct bandmetadata *) palloc(sizeof(struct bandmetadata) * j); - - for (i = 0; i < j; i++) { - band = rt_raster_get_band(raster, bandNums[i] - 1); - if (NULL == band) { - elog(NOTICE, "Could not get raster band at index %d", bandNums[i]); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - - /* bandnum */ - bmd[i].bandnum = bandNums[i]; - - /* pixeltype */ - tmp = rt_pixtype_name(rt_band_get_pixtype(band)); - bmd[i].pixeltype = palloc(sizeof(char) * (strlen(tmp) + 1)); - strncpy(bmd[i].pixeltype, tmp, strlen(tmp) + 1); - - /* hasnodatavalue */ - if (rt_band_get_hasnodata_flag(band)) - bmd[i].hasnodata = TRUE; - else - bmd[i].hasnodata = FALSE; - - /* nodatavalue */ - if (bmd[i].hasnodata) - rt_band_get_nodata(band, &(bmd[i].nodataval)); - else - bmd[i].nodataval = 0; - - /* path */ - tmp = rt_band_get_ext_path(band); - if (tmp) { - bmd[i].bandpath = palloc(sizeof(char) * (strlen(tmp) + 1)); - strncpy(bmd[i].bandpath, tmp, strlen(tmp) + 1); - } - else - bmd[i].bandpath = NULL; - - /* isoutdb */ - bmd[i].isoutdb = bmd[i].bandpath ? TRUE : FALSE; - - rt_band_destroy(band); - } - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Store needed information */ - funcctx->user_fctx = bmd; - - /* total number of tuples to be returned */ - funcctx->max_calls = j; - - /* 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; - bmd2 = funcctx->user_fctx; - - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 5; - Datum values[values_length]; - bool nulls[values_length]; - - memset(nulls, FALSE, sizeof(bool) * values_length); - - values[0] = UInt32GetDatum(bmd2[call_cntr].bandnum); - values[1] = CStringGetTextDatum(bmd2[call_cntr].pixeltype); - - if (bmd2[call_cntr].hasnodata) - values[2] = Float8GetDatum(bmd2[call_cntr].nodataval); - else - nulls[2] = TRUE; - - values[3] = BoolGetDatum(bmd2[call_cntr].isoutdb); - if (bmd2[call_cntr].bandpath && strlen(bmd2[call_cntr].bandpath)) - values[4] = CStringGetTextDatum(bmd2[call_cntr].bandpath); - else - nulls[4] = TRUE; - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - /* clean up */ - pfree(bmd2[call_cntr].pixeltype); - if (bmd2[call_cntr].bandpath) pfree(bmd2[call_cntr].bandpath); - - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(bmd2); - SRF_RETURN_DONE(funcctx); - } -} - -PG_FUNCTION_INFO_V1(RASTER_rasterToWorldCoord); -Datum RASTER_rasterToWorldCoord(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - int i = 0; - int cr[2] = {0}; - bool skewed[2] = {false, false}; - double cw[2] = {0}; - - TupleDesc tupdesc; - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUG(3, "RASTER_rasterToWorldCoord: Starting"); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_rasterToWorldCoord: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* raster skewed? */ - skewed[0] = FLT_NEQ(rt_raster_get_x_skew(raster), 0) ? true : false; - skewed[1] = FLT_NEQ(rt_raster_get_y_skew(raster), 0) ? true : false; - - /* column and row */ - for (i = 1; i <= 2; i++) { - if (PG_ARGISNULL(i)) { - /* if skewed on same axis, parameter is required */ - if (skewed[i - 1]) { - elog(NOTICE, "Pixel row and column required for computing longitude and latitude of a rotated raster"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - continue; - } - - cr[i - 1] = PG_GETARG_INT32(i); - } - - /* user-provided value is 1-based but needs to be 0-based */ - if (rt_raster_cell_to_geopoint( - raster, - (double) cr[0] - 1, (double) cr[1] - 1, - &(cw[0]), &(cw[1]), - NULL - ) != ES_NONE) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_rasterToWorldCoord: Could not compute longitude and latitude from pixel row and column"); - PG_RETURN_NULL(); - } - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* 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); - - values[0] = Float8GetDatum(cw[0]); - values[1] = Float8GetDatum(cw[1]); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - PG_RETURN_DATUM(result); -} - -PG_FUNCTION_INFO_V1(RASTER_worldToRasterCoord); -Datum RASTER_worldToRasterCoord(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - int i = 0; - double cw[2] = {0}; - double _cr[2] = {0}; - int cr[2] = {0}; - bool skewed = false; - - TupleDesc tupdesc; - int values_length = 2; - Datum values[values_length]; - bool nulls[values_length]; - HeapTuple tuple; - Datum result; - - POSTGIS_RT_DEBUG(3, "RASTER_worldToRasterCoord: Starting"); - - /* pgraster is null, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); - - /* raster */ - raster = rt_raster_deserialize(pgraster, TRUE); - if (!raster) { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_worldToRasterCoord: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* raster skewed? */ - skewed = FLT_NEQ(rt_raster_get_x_skew(raster), 0) ? true : false; - if (!skewed) - skewed = FLT_NEQ(rt_raster_get_y_skew(raster), 0) ? true : false; - - /* longitude and latitude */ - for (i = 1; i <= 2; i++) { - if (PG_ARGISNULL(i)) { - /* if skewed, parameter is required */ - if (skewed) { - elog(NOTICE, "Latitude and longitude required for computing pixel row and column of a rotated raster"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - PG_RETURN_NULL(); - } - - continue; - } - - cw[i - 1] = PG_GETARG_FLOAT8(i); - } - - /* return pixel row and column values are 0-based */ - if (rt_raster_geopoint_to_cell( - raster, - cw[0], cw[1], - &(_cr[0]), &(_cr[1]), - NULL - ) != ES_NONE) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_worldToRasterCoord: Could not compute pixel row and column from longitude and latitude"); - PG_RETURN_NULL(); - } - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* force to integer and add one to make 1-based */ - cr[0] = ((int) _cr[0]) + 1; - cr[1] = ((int) _cr[1]) + 1; - - /* 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); - - values[0] = Int32GetDatum(cr[0]); - values[1] = Int32GetDatum(cr[1]); - - memset(nulls, FALSE, sizeof(bool) * values_length); - - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); - - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - PG_RETURN_DATUM(result); -} - -/** - * See if two rasters intersect - */ -PG_FUNCTION_INFO_V1(RASTER_intersects); -Datum RASTER_intersects(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_intersects: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_intersects( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_intersects: Could not test for intersection on the two rasters"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if two rasters overlap - */ -PG_FUNCTION_INFO_V1(RASTER_overlaps); -Datum RASTER_overlaps(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_overlaps: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_overlaps( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_overlaps: Could not test for overlap on the two rasters"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if two rasters touch - */ -PG_FUNCTION_INFO_V1(RASTER_touches); -Datum RASTER_touches(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_touches: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_touches( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_touches: Could not test for touch on the two rasters"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the first raster contains the second raster - */ -PG_FUNCTION_INFO_V1(RASTER_contains); -Datum RASTER_contains(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_contains: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_contains( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_contains: Could not test that the first raster contains the second raster"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the first raster contains properly the second raster - */ -PG_FUNCTION_INFO_V1(RASTER_containsProperly); -Datum RASTER_containsProperly(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_containsProperly: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_contains_properly( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_containsProperly: Could not test that the first raster contains properly the second raster"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the first raster covers the second raster - */ -PG_FUNCTION_INFO_V1(RASTER_covers); -Datum RASTER_covers(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_covers: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_covers( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_covers: Could not test that the first raster covers the second raster"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the first raster is covered by the second raster - */ -PG_FUNCTION_INFO_V1(RASTER_coveredby); -Datum RASTER_coveredby(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_coveredby: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_coveredby( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_coveredby: Could not test that the first raster is covered by the second raster"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the two rasters are within the specified distance of each other - */ -PG_FUNCTION_INFO_V1(RASTER_dwithin); -Datum RASTER_dwithin(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - double distance = 0; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_dwithin: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* distance */ - if (PG_ARGISNULL(4)) { - elog(NOTICE, "Distance cannot be NULL. Returning NULL"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - distance = PG_GETARG_FLOAT8(4); - if (distance < 0) { - elog(NOTICE, "Distance cannot be less than zero. Returning NULL"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_within_distance( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - distance, - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_dwithin: Could not test that the two rasters are within the specified distance of each other"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if the two rasters are fully within the specified distance of each other - */ -PG_FUNCTION_INFO_V1(RASTER_dfullywithin); -Datum RASTER_dfullywithin(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - uint32_t bandindex[2] = {0}; - uint32_t hasbandindex[2] = {0}; - double distance = 0; - - uint32_t i; - uint32_t j; - uint32_t k; - uint32_t numBands; - int rtn; - int result; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_dfullywithin: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* numbands */ - numBands = rt_raster_get_num_bands(rast[i]); - if (numBands < 1) { - elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - if (bandindex[i] < 1 || bandindex[i] > numBands) { - elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); - if (i > 0) i++; - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - hasbandindex[i] = 1; - } - else - hasbandindex[i] = 0; - POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); - POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); - j++; - } - - /* distance */ - if (PG_ARGISNULL(4)) { - elog(NOTICE, "Distance cannot be NULL. Returning NULL"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - distance = PG_GETARG_FLOAT8(4); - if (distance < 0) { - elog(NOTICE, "Distance cannot be less than zero. Returning NULL"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* hasbandindex must be balanced */ - if ( - (hasbandindex[0] && !hasbandindex[1]) || - (!hasbandindex[0] && hasbandindex[1]) - ) { - elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* SRID must match */ - if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "The two rasters provided have different SRIDs"); - PG_RETURN_NULL(); - } - - rtn = rt_raster_fully_within_distance( - rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), - rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), - distance, - &result - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_dfullywithin: Could not test that the two rasters are fully within the specified distance of each other"); - PG_RETURN_NULL(); - } - - PG_RETURN_BOOL(result); -} - -/** - * See if two rasters are aligned - */ -PG_FUNCTION_INFO_V1(RASTER_sameAlignment); -Datum RASTER_sameAlignment(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - - uint32_t i; - uint32_t j; - uint32_t k; - int rtn; - int aligned = 0; - char *reason = NULL; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(j), 0, sizeof(struct rt_raster_serialized_t)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], TRUE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_sameAlignment: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - } - - rtn = rt_raster_same_alignment( - rast[0], - rast[1], - &aligned, - &reason - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_sameAlignment: Could not test for alignment on the two rasters"); - PG_RETURN_NULL(); - } - - /* only output reason if not aligned */ - if (reason != NULL && !aligned) - elog(NOTICE, "%s", reason); - - PG_RETURN_BOOL(aligned); -} - -/** - * Return a reason why two rasters are not aligned - */ -PG_FUNCTION_INFO_V1(RASTER_notSameAlignmentReason); -Datum RASTER_notSameAlignmentReason(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_raster rast[2] = {NULL}; - - uint32_t i; - uint32_t j; - uint32_t k; - int rtn; - int aligned = 0; - char *reason = NULL; - text *result = NULL; - - for (i = 0, j = 0; i < set_count; i++) { - /* pgrast is null, return null */ - if (PG_ARGISNULL(j)) { - for (k = 0; k < i; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(j), 0, sizeof(struct rt_raster_serialized_t)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], TRUE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i) - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_notSameAlignmentReason: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - } - - rtn = rt_raster_same_alignment( - rast[0], - rast[1], - &aligned, - &reason - ); - for (k = 0; k < set_count; k++) { - rt_raster_destroy(rast[k]); - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - if (rtn != ES_NONE) { - elog(ERROR, "RASTER_notSameAlignmentReason: Could not test for alignment on the two rasters"); - PG_RETURN_NULL(); - } - - result = cstring2text(reason); - PG_RETURN_TEXT_P(result); -} - -/** - * Two raster MapAlgebra - */ -PG_FUNCTION_INFO_V1(RASTER_mapAlgebra2); -Datum RASTER_mapAlgebra2(PG_FUNCTION_ARGS) -{ - const int set_count = 2; - rt_pgraster *pgrast[2]; - int pgrastpos[2] = {-1, -1}; - rt_pgraster *pgrtn; - rt_raster rast[2] = {NULL}; - int _isempty[2] = {0}; - uint32_t bandindex[2] = {0}; - rt_raster _rast[2] = {NULL}; - rt_band _band[2] = {NULL}; - int _hasnodata[2] = {0}; - double _nodataval[2] = {0}; - double _offset[4] = {0.}; - double _rastoffset[2][4] = {{0.}}; - int _haspixel[2] = {0}; - double _pixel[2] = {0}; - int _pos[2][2] = {{0}}; - uint16_t _dim[2][2] = {{0}}; - - char *pixtypename = NULL; - rt_pixtype pixtype = PT_END; - char *extenttypename = NULL; - rt_extenttype extenttype = ET_INTERSECTION; - - rt_raster raster = NULL; - rt_band band = NULL; - uint16_t dim[2] = {0}; - int haspixel = 0; - double pixel = 0.; - double nodataval = 0; - double gt[6] = {0.}; - - Oid calltype = InvalidOid; - - const int spi_count = 3; - uint16_t spi_exprpos[3] = {4, 7, 8}; - uint32_t spi_argcount[3] = {0}; - char *expr = NULL; - char *sql = NULL; - SPIPlanPtr spi_plan[3] = {NULL}; - uint16_t spi_empty = 0; - Oid *argtype = NULL; - const int argkwcount = 8; - uint8_t argpos[3][8] = {{0}}; - char *argkw[] = {"[rast1.x]", "[rast1.y]", "[rast1.val]", "[rast1]", "[rast2.x]", "[rast2.y]", "[rast2.val]", "[rast2]"}; - Datum values[argkwcount]; - bool nulls[argkwcount]; - TupleDesc tupdesc; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isnull = FALSE; - int hasargval[3] = {0}; - double argval[3] = {0.}; - int hasnodatanodataval = 0; - double nodatanodataval = 0; - int isnodata = 0; - - Oid ufc_noid = InvalidOid; - FmgrInfo ufl_info; - FunctionCallInfoData ufc_info; - int ufc_nullcount = 0; - - int idx = 0; - uint32_t i = 0; - uint32_t j = 0; - uint32_t k = 0; - uint32_t x = 0; - uint32_t y = 0; - int _x = 0; - int _y = 0; - int err; - int aligned = 0; - int len = 0; - - POSTGIS_RT_DEBUG(3, "Starting RASTER_mapAlgebra2"); - - for (i = 0, j = 0; i < set_count; i++) { - if (!PG_ARGISNULL(j)) { - pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); - pgrastpos[i] = j; - j++; - - /* raster */ - rast[i] = rt_raster_deserialize(pgrast[i], FALSE); - if (!rast[i]) { - for (k = 0; k <= i; k++) { - if (k < i && rast[k] != NULL) - rt_raster_destroy(rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not deserialize the %s raster", i < 1 ? "first" : "second"); - PG_RETURN_NULL(); - } - - /* empty */ - _isempty[i] = rt_raster_is_empty(rast[i]); - - /* band index */ - if (!PG_ARGISNULL(j)) { - bandindex[i] = PG_GETARG_INT32(j); - } - j++; - } - else { - _isempty[i] = 1; - j += 2; - } - - POSTGIS_RT_DEBUGF(3, "_isempty[%d] = %d", i, _isempty[i]); - } - - /* both rasters are NULL */ - if (rast[0] == NULL && rast[1] == NULL) { - elog(NOTICE, "The two rasters provided are NULL. Returning NULL"); - for (k = 0; k < set_count; k++) { - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* both rasters are empty */ - if (_isempty[0] && _isempty[1]) { - elog(NOTICE, "The two rasters provided are empty. Returning empty raster"); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - for (k = 0; k < set_count; k++) { - if (rast[k] != NULL) - rt_raster_destroy(rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not create empty raster"); - PG_RETURN_NULL(); - } - rt_raster_set_scale(raster, 0, 0); - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* replace the empty or NULL raster with one matching the other */ - if ( - (rast[0] == NULL || _isempty[0]) || - (rast[1] == NULL || _isempty[1]) - ) { - /* first raster is empty */ - if (rast[0] == NULL || _isempty[0]) { - i = 0; - j = 1; - } - /* second raster is empty */ - else { - i = 1; - j = 0; - } - - _rast[j] = rast[j]; - - /* raster is empty, destroy it */ - if (_rast[i] != NULL) - rt_raster_destroy(_rast[i]); - - _dim[i][0] = rt_raster_get_width(_rast[j]); - _dim[i][1] = rt_raster_get_height(_rast[j]); - _dim[j][0] = rt_raster_get_width(_rast[j]); - _dim[j][1] = rt_raster_get_height(_rast[j]); - - _rast[i] = rt_raster_new( - _dim[j][0], - _dim[j][1] - ); - if (_rast[i] == NULL) { - rt_raster_destroy(_rast[j]); - for (k = 0; k < set_count; k++) { - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not create NODATA raster"); - PG_RETURN_NULL(); - } - rt_raster_set_srid(_rast[i], rt_raster_get_srid(_rast[j])); - - rt_raster_get_geotransform_matrix(_rast[j], gt); - rt_raster_set_geotransform_matrix(_rast[i], gt); - } - else { - _rast[0] = rast[0]; - _dim[0][0] = rt_raster_get_width(_rast[0]); - _dim[0][1] = rt_raster_get_height(_rast[0]); - - _rast[1] = rast[1]; - _dim[1][0] = rt_raster_get_width(_rast[1]); - _dim[1][1] = rt_raster_get_height(_rast[1]); - } - - /* SRID must match */ - /* - if (rt_raster_get_srid(_rast[0]) != rt_raster_get_srid(_rast[1])) { - elog(NOTICE, "The two rasters provided have different SRIDs. Returning NULL"); - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - */ - - /* same alignment */ - if (rt_raster_same_alignment(_rast[0], _rast[1], &aligned, NULL) != ES_NONE) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not test for alignment on the two rasters"); - PG_RETURN_NULL(); - } - if (!aligned) { - elog(NOTICE, "The two rasters provided do not have the same alignment. Returning NULL"); - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - PG_RETURN_NULL(); - } - - /* pixel type */ - if (!PG_ARGISNULL(5)) { - pixtypename = text_to_cstring(PG_GETARG_TEXT_P(5)); - /* Get the pixel type index */ - pixtype = rt_pixtype_index_from_name(pixtypename); - if (pixtype == PT_END ) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Invalid pixel type: %s", pixtypename); - PG_RETURN_NULL(); - } - } - - /* extent type */ - if (!PG_ARGISNULL(6)) { - extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(6)))); - extenttype = rt_util_extent_type(extenttypename); - } - POSTGIS_RT_DEBUGF(3, "extenttype: %d %s", extenttype, extenttypename); - - /* computed raster from extent type */ - err = rt_raster_from_two_rasters( - _rast[0], _rast[1], - extenttype, - &raster, _offset - ); - if (err != ES_NONE) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not get output raster of correct extent"); - PG_RETURN_NULL(); - } - - /* copy offsets */ - _rastoffset[0][0] = _offset[0]; - _rastoffset[0][1] = _offset[1]; - _rastoffset[1][0] = _offset[2]; - _rastoffset[1][1] = _offset[3]; - - /* get output raster dimensions */ - dim[0] = rt_raster_get_width(raster); - dim[1] = rt_raster_get_height(raster); - - i = 2; - /* handle special cases for extent */ - switch (extenttype) { - case ET_FIRST: - i = 0; - case ET_SECOND: - if (i > 1) - i = 1; - - if ( - _isempty[i] && ( - (extenttype == ET_FIRST && i == 0) || - (extenttype == ET_SECOND && i == 1) - ) - ) { - elog(NOTICE, "The %s raster is NULL. Returning NULL", (i != 1 ? "FIRST" : "SECOND")); - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - PG_RETURN_NULL(); - } - - /* specified band not found */ - if (!rt_raster_has_band(_rast[i], bandindex[i] - 1)) { - elog(NOTICE, "The %s raster does not have the band at index %d. Returning no band raster of correct extent", - (i != 1 ? "FIRST" : "SECOND"), bandindex[i] - ); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgrtn) PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - break; - case ET_UNION: - break; - case ET_INTERSECTION: - /* no intersection */ - if ( - _isempty[0] || _isempty[1] || - !dim[0] || !dim[1] - ) { - elog(NOTICE, "The two rasters provided have no intersection. Returning no band raster"); - - /* raster has dimension, replace with no band raster */ - if (dim[0] || dim[1]) { - rt_raster_destroy(raster); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: Could not create no band raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(raster, 0, 0); - rt_raster_set_srid(raster, rt_raster_get_srid(_rast[0])); - } - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgrtn) PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - break; - case ET_LAST: - case ET_CUSTOM: - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - elog(ERROR, "RASTER_mapAlgebra2: ET_LAST and ET_CUSTOM are not implemented"); - PG_RETURN_NULL(); - break; - } - - /* both rasters do not have specified bands */ - if ( - (!_isempty[0] && !rt_raster_has_band(_rast[0], bandindex[0] - 1)) && - (!_isempty[1] && !rt_raster_has_band(_rast[1], bandindex[1] - 1)) - ) { - elog(NOTICE, "The two rasters provided do not have the respectively specified band indices. Returning no band raster of correct extent"); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgrtn) PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* get bands */ - for (i = 0; i < set_count; i++) { - if (_isempty[i] || !rt_raster_has_band(_rast[i], bandindex[i] - 1)) { - _hasnodata[i] = 1; - _nodataval[i] = 0; - - continue; - } - - _band[i] = rt_raster_get_band(_rast[i], bandindex[i] - 1); - if (_band[i] == NULL) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - elog(ERROR, "RASTER_mapAlgebra2: Could not get band %d of the %s raster", - bandindex[i], - (i < 1 ? "FIRST" : "SECOND") - ); - PG_RETURN_NULL(); - } - - _hasnodata[i] = rt_band_get_hasnodata_flag(_band[i]); - if (_hasnodata[i]) - rt_band_get_nodata(_band[i], &(_nodataval[i])); - } - - /* pixtype is PT_END, get pixtype based upon extent */ - if (pixtype == PT_END) { - if ((extenttype == ET_SECOND && !_isempty[1]) || _isempty[0]) - pixtype = rt_band_get_pixtype(_band[1]); - else - pixtype = rt_band_get_pixtype(_band[0]); - } - - /* nodata value for new band */ - if (extenttype == ET_SECOND && !_isempty[1] && _hasnodata[1]) { - nodataval = _nodataval[1]; - } - else if (!_isempty[0] && _hasnodata[0]) { - nodataval = _nodataval[0]; - } - else if (!_isempty[1] && _hasnodata[1]) { - nodataval = _nodataval[1]; - } - else { - elog(NOTICE, "Neither raster provided has a NODATA value for the specified band indices. NODATA value set to minimum possible for %s", rt_pixtype_name(pixtype)); - nodataval = rt_pixtype_get_min_value(pixtype); - } - - /* add band to output raster */ - if (rt_raster_generate_new_band( - raster, - pixtype, - nodataval, - 1, nodataval, - 0 - ) < 0) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - elog(ERROR, "RASTER_mapAlgebra2: Could not add new band to output raster"); - PG_RETURN_NULL(); - } - - /* get output band */ - band = rt_raster_get_band(raster, 0); - if (band == NULL) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - elog(ERROR, "RASTER_mapAlgebra2: Could not get newly added band of output raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(4, "offsets = (%d, %d, %d, %d)", - (int) _rastoffset[0][0], - (int) _rastoffset[0][1], - (int) _rastoffset[1][0], - (int) _rastoffset[1][1] - ); - - POSTGIS_RT_DEBUGF(4, "metadata = (%f, %f, %d, %d, %f, %f, %f, %f, %d)", - rt_raster_get_x_offset(raster), - rt_raster_get_y_offset(raster), - rt_raster_get_width(raster), - rt_raster_get_height(raster), - rt_raster_get_x_scale(raster), - rt_raster_get_y_scale(raster), - rt_raster_get_x_skew(raster), - rt_raster_get_y_skew(raster), - rt_raster_get_srid(raster) - ); - - /* - determine who called this function - Arg 4 will either be text or regprocedure - */ - POSTGIS_RT_DEBUG(3, "checking parameter type for arg 4"); - calltype = get_fn_expr_argtype(fcinfo->flinfo, 4); - - switch(calltype) { - case TEXTOID: { - POSTGIS_RT_DEBUG(3, "arg 4 is \"expression\"!"); - - /* connect SPI */ - if (SPI_connect() != SPI_OK_CONNECT) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - elog(ERROR, "RASTER_mapAlgebra2: Could not connect to the SPI manager"); - PG_RETURN_NULL(); - } - - /* reset hasargval */ - memset(hasargval, 0, sizeof(int) * spi_count); - - /* - process expressions - - spi_exprpos elements are: - 4 - expression => spi_plan[0] - 7 - nodata1expr => spi_plan[1] - 8 - nodata2expr => spi_plan[2] - */ - for (i = 0; i < spi_count; i++) { - if (!PG_ARGISNULL(spi_exprpos[i])) { - char *tmp = NULL; - char place[5] = "$1"; - expr = text_to_cstring(PG_GETARG_TEXT_P(spi_exprpos[i])); - POSTGIS_RT_DEBUGF(3, "raw expr #%d: %s", i, expr); - - for (j = 0, k = 1; j < argkwcount; j++) { - /* attempt to replace keyword with placeholder */ - len = 0; - tmp = rtpg_strreplace(expr, argkw[j], place, &len); - pfree(expr); - expr = tmp; - - if (len) { - spi_argcount[i]++; - argpos[i][j] = k++; - - sprintf(place, "$%d", k); - } - else - argpos[i][j] = 0; - } - - len = strlen("SELECT (") + strlen(expr) + strlen(")::double precision"); - sql = (char *) palloc(len + 1); - if (sql == NULL) { - - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not allocate memory for expression parameter %d", spi_exprpos[i]); - PG_RETURN_NULL(); - } - - strncpy(sql, "SELECT (", strlen("SELECT (")); - strncpy(sql + strlen("SELECT ("), expr, strlen(expr)); - strncpy(sql + strlen("SELECT (") + strlen(expr), ")::double precision", strlen(")::double precision")); - sql[len] = '\0'; - - POSTGIS_RT_DEBUGF(3, "sql #%d: %s", i, sql); - - /* create prepared plan */ - if (spi_argcount[i]) { - argtype = (Oid *) palloc(spi_argcount[i] * sizeof(Oid)); - if (argtype == NULL) { - - pfree(sql); - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not allocate memory for prepared plan argtypes of expression parameter %d", spi_exprpos[i]); - PG_RETURN_NULL(); - } - - /* specify datatypes of parameters */ - for (j = 0, k = 0; j < argkwcount; j++) { - if (argpos[i][j] < 1) continue; - - /* positions are INT4 */ - if ( - (strstr(argkw[j], "[rast1.x]") != NULL) || - (strstr(argkw[j], "[rast1.y]") != NULL) || - (strstr(argkw[j], "[rast2.x]") != NULL) || - (strstr(argkw[j], "[rast2.y]") != NULL) - ) { - argtype[k] = INT4OID; - } - /* everything else is FLOAT8 */ - else { - argtype[k] = FLOAT8OID; - } - - k++; - } - - spi_plan[i] = SPI_prepare(sql, spi_argcount[i], argtype); - pfree(argtype); - - if (spi_plan[i] == NULL) { - - pfree(sql); - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not create prepared plan of expression parameter %d", spi_exprpos[i]); - PG_RETURN_NULL(); - } - } - /* no args, just execute query */ - else { - err = SPI_execute(sql, TRUE, 0); - if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - - pfree(sql); - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not evaluate expression parameter %d", spi_exprpos[i]); - PG_RETURN_NULL(); - } - - /* get output of prepared plan */ - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - pfree(sql); - if (SPI_tuptable) SPI_freetuptable(tuptable); - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not get result of expression parameter %d", spi_exprpos[i]); - PG_RETURN_NULL(); - } - - if (!isnull) { - hasargval[i] = 1; - argval[i] = DatumGetFloat8(datum); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - } - - pfree(sql); - } - else - spi_empty++; - } - - /* nodatanodataval */ - if (!PG_ARGISNULL(9)) { - hasnodatanodataval = 1; - nodatanodataval = PG_GETARG_FLOAT8(9); - } - else - hasnodatanodataval = 0; - break; - } - case REGPROCEDUREOID: { - POSTGIS_RT_DEBUG(3, "arg 4 is \"userfunction\"!"); - if (!PG_ARGISNULL(4)) { - - ufc_nullcount = 0; - ufc_noid = PG_GETARG_OID(4); - - /* get function info */ - fmgr_info(ufc_noid, &ufl_info); - - /* function cannot return set */ - err = 0; - if (ufl_info.fn_retset) { - err = 1; - } - /* function should have correct # of args */ - else if (ufl_info.fn_nargs < 3 || ufl_info.fn_nargs > 4) { - err = 2; - } - - /* - TODO: consider adding checks of the userfunction parameters - should be able to use get_fn_expr_argtype() of fmgr.c - */ - - if (err > 0) { - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - if (err > 1) - elog(ERROR, "RASTER_mapAlgebra2: Function provided must have three or four input parameters"); - else - elog(ERROR, "RASTER_mapAlgebra2: Function provided must return double precision not resultset"); - PG_RETURN_NULL(); - } - - if (func_volatile(ufc_noid) == 'v') { - elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); - } - - /* prep function call data */ -#if POSTGIS_PGSQL_VERSION <= 90 - InitFunctionCallInfoData(ufc_info, &ufl_info, ufl_info.fn_nargs, InvalidOid, NULL); -#else - InitFunctionCallInfoData(ufc_info, &ufl_info, ufl_info.fn_nargs, InvalidOid, NULL, NULL); -#endif - memset(ufc_info.argnull, FALSE, sizeof(bool) * ufl_info.fn_nargs); - - if (ufl_info.fn_nargs != 4) - k = 2; - else - k = 3; - if (!PG_ARGISNULL(7)) { - ufc_info.arg[k] = PG_GETARG_DATUM(7); - } - else { - ufc_info.arg[k] = (Datum) NULL; - ufc_info.argnull[k] = TRUE; - ufc_nullcount++; - } - } - break; - } - default: - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - elog(ERROR, "RASTER_mapAlgebra2: Invalid data type for expression or userfunction"); - PG_RETURN_NULL(); - break; - } - - /* loop over pixels */ - /* if any expression present, run */ - if (( - (calltype == TEXTOID) && ( - (spi_empty != spi_count) || hasnodatanodataval - ) - ) || ( - (calltype == REGPROCEDUREOID) && (ufc_noid != InvalidOid) - )) { - for (x = 0; x < dim[0]; x++) { - for (y = 0; y < dim[1]; y++) { - - /* get pixel from each raster */ - for (i = 0; i < set_count; i++) { - _haspixel[i] = 0; - _pixel[i] = 0; - - /* row/column */ - _x = x - (int) _rastoffset[i][0]; - _y = y - (int) _rastoffset[i][1]; - - /* store _x and _y in 1-based */ - _pos[i][0] = _x + 1; - _pos[i][1] = _y + 1; - - /* get pixel value */ - if (_band[i] == NULL) { - if (!_hasnodata[i]) { - _haspixel[i] = 1; - _pixel[i] = _nodataval[i]; - } - } - else if ( - !_isempty[i] && - (_x >= 0 && _x < _dim[i][0]) && - (_y >= 0 && _y < _dim[i][1]) - ) { - err = rt_band_get_pixel(_band[i], _x, _y, &(_pixel[i]), &isnodata); - if (err != ES_NONE) { - - if (calltype == TEXTOID) { - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - } - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not get pixel of %s raster", (i < 1 ? "FIRST" : "SECOND")); - PG_RETURN_NULL(); - } - - if (!_hasnodata[i] || !isnodata) - _haspixel[i] = 1; - } - - POSTGIS_RT_DEBUGF(5, "pixel r%d(%d, %d) = %d, %f", - i, - _x, _y, - _haspixel[i], - _pixel[i] - ); - } - - haspixel = 0; - - switch (calltype) { - case TEXTOID: { - /* which prepared plan to use? */ - /* !pixel0 && !pixel1 */ - /* use nodatanodataval */ - if (!_haspixel[0] && !_haspixel[1]) - i = 3; - /* pixel0 && !pixel1 */ - /* run spi_plan[2] (nodata2expr) */ - else if (_haspixel[0] && !_haspixel[1]) - i = 2; - /* !pixel0 && pixel1 */ - /* run spi_plan[1] (nodata1expr) */ - else if (!_haspixel[0] && _haspixel[1]) - i = 1; - /* pixel0 && pixel1 */ - /* run spi_plan[0] (expression) */ - else - i = 0; - - /* process values */ - if (i == 3) { - if (hasnodatanodataval) { - haspixel = 1; - pixel = nodatanodataval; - } - } - /* has an evaluated value */ - else if (hasargval[i]) { - haspixel = 1; - pixel = argval[i]; - } - /* prepared plan exists */ - else if (spi_plan[i] != NULL) { - POSTGIS_RT_DEBUGF(4, "Using prepared plan: %d", i); - - /* expression has argument(s) */ - if (spi_argcount[i]) { - /* reset values to (Datum) NULL */ - memset(values, (Datum) NULL, sizeof(Datum) * argkwcount); - /* reset nulls to FALSE */ - memset(nulls, FALSE, sizeof(bool) * argkwcount); - - /* set values and nulls */ - for (j = 0; j < argkwcount; j++) { - idx = argpos[i][j]; - if (idx < 1) continue; - idx--; /* 1-based becomes 0-based */ - - if (strstr(argkw[j], "[rast1.x]") != NULL) { - values[idx] = _pos[0][0]; - } - else if (strstr(argkw[j], "[rast1.y]") != NULL) { - values[idx] = _pos[0][1]; - } - else if ( - (strstr(argkw[j], "[rast1.val]") != NULL) || - (strstr(argkw[j], "[rast1]") != NULL) - ) { - if (_isempty[0] || !_haspixel[0]) - nulls[idx] = TRUE; - else - values[idx] = Float8GetDatum(_pixel[0]); - } - else if (strstr(argkw[j], "[rast2.x]") != NULL) { - values[idx] = _pos[1][0]; - } - else if (strstr(argkw[j], "[rast2.y]") != NULL) { - values[idx] = _pos[1][1]; - } - else if ( - (strstr(argkw[j], "[rast2.val]") != NULL) || - (strstr(argkw[j], "[rast2]") != NULL) - ) { - if (_isempty[1] || !_haspixel[1]) - nulls[idx] = TRUE; - else - values[idx] = Float8GetDatum(_pixel[1]); - } - } - } - - /* run prepared plan */ - err = SPI_execute_plan(spi_plan[i], values, nulls, TRUE, 1); - if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Unexpected error when running prepared statement %d", i); - PG_RETURN_NULL(); - } - - /* get output of prepared plan */ - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - - if (SPI_tuptable) SPI_freetuptable(tuptable); - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not get result of prepared statement %d", i); - PG_RETURN_NULL(); - } - - if (!isnull) { - haspixel = 1; - pixel = DatumGetFloat8(datum); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - } - } break; - case REGPROCEDUREOID: { - Datum d[4]; - ArrayType *a; - - /* build fcnarg */ - for (i = 0; i < set_count; i++) { - ufc_info.arg[i] = Float8GetDatum(_pixel[i]); - - if (_haspixel[i]) { - ufc_info.argnull[i] = FALSE; - ufc_nullcount--; - } - else { - ufc_info.argnull[i] = TRUE; - ufc_nullcount++; - } - } - - /* function is strict and null parameter is passed */ - /* http://archives.postgresql.org/pgsql-general/2011-11/msg00424.php */ - if (ufl_info.fn_strict && ufc_nullcount) - break; - - /* 4 parameters, add position */ - if (ufl_info.fn_nargs == 4) { - /* Datum of 4 element array */ - /* array is (x1, y1, x2, y2) */ - for (i = 0; i < set_count; i++) { - if (i < 1) { - d[0] = Int32GetDatum(_pos[i][0]); - d[1] = Int32GetDatum(_pos[i][1]); - } - else { - d[2] = Int32GetDatum(_pos[i][0]); - d[3] = Int32GetDatum(_pos[i][1]); - } - } - - a = construct_array(d, 4, INT4OID, sizeof(int32), true, 'i'); - ufc_info.arg[2] = PointerGetDatum(a); - ufc_info.argnull[2] = FALSE; - } - - datum = FunctionCallInvoke(&ufc_info); - - /* result is not null*/ - if (!ufc_info.isnull) { - haspixel = 1; - pixel = DatumGetFloat8(datum); - } - } break; - } - - /* burn pixel if haspixel != 0 */ - if (haspixel) { - if (rt_band_set_pixel(band, x, y, pixel, NULL) != ES_NONE) { - - if (calltype == TEXTOID) { - for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); - SPI_finish(); - } - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - rt_raster_destroy(raster); - - elog(ERROR, "RASTER_mapAlgebra2: Could not set pixel value of output raster"); - PG_RETURN_NULL(); - } - } - - POSTGIS_RT_DEBUGF(5, "(x, y, val) = (%d, %d, %f)", x, y, haspixel ? pixel : nodataval); - - } /* y: height */ - } /* x: width */ - } - - /* CLEANUP */ - if (calltype == TEXTOID) { - for (i = 0; i < spi_count; i++) { - if (spi_plan[i] != NULL) SPI_freeplan(spi_plan[i]); - } - SPI_finish(); - } - - for (k = 0; k < set_count; k++) { - if (_rast[k] != NULL) - rt_raster_destroy(_rast[k]); - if (pgrastpos[k] != -1) - PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); - } - - pgrtn = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgrtn) PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "Finished RASTER_mapAlgebra2"); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/** - * One raster neighborhood MapAlgebra - */ -PG_FUNCTION_INFO_V1(RASTER_mapAlgebraFctNgb); -Datum RASTER_mapAlgebraFctNgb(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - rt_pgraster *pgrtn = NULL; - rt_raster raster = NULL; - rt_raster newrast = NULL; - rt_band band = NULL; - rt_band newband = NULL; - int x, y, nband, width, height, ngbwidth, ngbheight, winwidth, winheight, u, v, nIndex, nNullItems; - double r, rpix; - double newnodatavalue = 0.0; - double newinitialvalue = 0.0; - double newval = 0.0; - rt_pixtype newpixeltype; - int ret = -1; - Oid oid; - FmgrInfo cbinfo; - FunctionCallInfoData cbdata; - Datum tmpnewval; - ArrayType * neighborDatum; - char * strFromText = NULL; - text * txtNodataMode = NULL; - text * txtCallbackParam = NULL; - int intReplace = 0; - float fltReplace = 0; - bool valuereplace = false, pixelreplace, nNodataOnly = true, nNullSkip = false; - Datum * neighborData = NULL; - bool * neighborNulls = NULL; - int neighborDims[2]; - int neighborLbs[2]; - int16 typlen; - bool typbyval; - char typalign; - - POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraFctNgb: STARTING..."); - - /* Check raster */ - if (PG_ARGISNULL(0)) { - elog(WARNING, "Raster is NULL. Returning NULL"); - PG_RETURN_NULL(); - } - - - /* Deserialize raster */ - pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - raster = rt_raster_deserialize(pgraster, FALSE); - if (NULL == raster) - { - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Getting arguments..."); - - /* Get the rest of the arguments */ - - if (PG_ARGISNULL(1)) - nband = 1; - else - nband = PG_GETARG_INT32(1); - - if (nband < 1) - nband = 1; - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Creating new empty raster..."); - - /** - * Create a new empty raster with having the same georeference as the - * provided raster - **/ - width = rt_raster_get_width(raster); - height = rt_raster_get_height(raster); - - newrast = rt_raster_new(width, height); - - if ( NULL == newrast ) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not create a new raster"); - PG_RETURN_NULL(); - } - - rt_raster_set_scale(newrast, - rt_raster_get_x_scale(raster), - rt_raster_get_y_scale(raster)); - - rt_raster_set_offsets(newrast, - rt_raster_get_x_offset(raster), - rt_raster_get_y_offset(raster)); - - rt_raster_set_skews(newrast, - rt_raster_get_x_skew(raster), - rt_raster_get_y_skew(raster)); - - rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); - - - /** - * If this new raster is empty (width = 0 OR height = 0) then there is - * nothing to compute and we return it right now - **/ - if (rt_raster_is_empty(newrast)) - { - elog(NOTICE, "Raster is empty. Returning an empty raster"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Getting raster band %d...", nband); - - /** - * Check if the raster has the required band. Otherwise, return a raster - * without band - **/ - if (!rt_raster_has_band(raster, nband - 1)) { - elog(NOTICE, "Raster does not have the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* Get the raster band */ - band = rt_raster_get_band(raster, nband - 1); - if ( NULL == band ) { - elog(NOTICE, "Could not get the required band. Returning a raster " - "without a band"); - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* - * Get NODATA value - */ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Getting NODATA value for band..."); - - if (rt_band_get_hasnodata_flag(band)) { - rt_band_get_nodata(band, &newnodatavalue); - } - - else { - newnodatavalue = rt_band_get_min_value(band); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: NODATA value for band: %f", - newnodatavalue); - /** - * We set the initial value of the future band to nodata value. If nodata - * value is null, then the raster will be initialized to - * rt_band_get_min_value but all the values should be recomputed anyway - **/ - newinitialvalue = newnodatavalue; - - /** - * Set the new pixeltype - **/ - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Setting pixeltype..."); - - if (PG_ARGISNULL(2)) { - newpixeltype = rt_band_get_pixtype(band); - } - - else { - strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Pixeltype parameter: %s", strFromText); - newpixeltype = rt_pixtype_index_from_name(strFromText); - pfree(strFromText); - if (newpixeltype == PT_END) - newpixeltype = rt_band_get_pixtype(band); - } - - if (newpixeltype == PT_END) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Invalid pixeltype"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Pixeltype set to %s (%d)", - rt_pixtype_name(newpixeltype), newpixeltype); - - /* Get the name of the callback userfunction */ - if (PG_ARGISNULL(5)) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Required function is missing"); - PG_RETURN_NULL(); - } - - oid = PG_GETARG_OID(5); - if (oid == InvalidOid) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Got invalid function object id"); - PG_RETURN_NULL(); - } - - fmgr_info(oid, &cbinfo); - - /* function cannot return set */ - if (cbinfo.fn_retset) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Function provided must return double precision not resultset"); - PG_RETURN_NULL(); - } - /* function should have correct # of args */ - else if (cbinfo.fn_nargs != 3) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Function does not have three input parameters"); - PG_RETURN_NULL(); - } - - if (func_volatile(oid) == 'v') { - elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); - } - - /* prep function call data */ -#if POSTGIS_PGSQL_VERSION <= 90 - InitFunctionCallInfoData(cbdata, &cbinfo, 3, InvalidOid, NULL); -#else - InitFunctionCallInfoData(cbdata, &cbinfo, 3, InvalidOid, NULL, NULL); -#endif - memset(cbdata.argnull, FALSE, sizeof(bool) * 3); - - /* check that the function isn't strict if the args are null. */ - if (PG_ARGISNULL(7)) { - if (cbinfo.fn_strict) { - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - rt_raster_destroy(newrast); - - elog(ERROR, "RASTER_mapAlgebraFctNgb: Strict callback functions cannot have NULL parameters"); - PG_RETURN_NULL(); - } - - cbdata.arg[2] = (Datum)NULL; - cbdata.argnull[2] = TRUE; - } - else { - cbdata.arg[2] = PG_GETARG_DATUM(7); - } - - /** - * Optimization: If the raster is only filled with nodata values return - * right now a raster filled with the nodatavalueexpr - * TODO: Call rt_band_check_isnodata instead? - **/ - if (rt_band_get_isnodata_flag(band)) { - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Band is a nodata band, returning " - "a raster filled with nodata"); - - rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - - /** - * Create the raster receiving all the computed values. Initialize it to the - * new initial value - **/ - rt_raster_generate_new_band(newrast, newpixeltype, - newinitialvalue, TRUE, newnodatavalue, 0); - - /* Get the new raster band */ - newband = rt_raster_get_band(newrast, 0); - if ( NULL == newband ) { - elog(NOTICE, "Could not modify band for new raster. Returning new " - "raster with the original band"); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* Get the width of the neighborhood */ - if (PG_ARGISNULL(3) || PG_GETARG_INT32(3) <= 0) { - elog(NOTICE, "Neighborhood width is NULL or <= 0. Returning new " - "raster with the original band"); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - ngbwidth = PG_GETARG_INT32(3); - winwidth = ngbwidth * 2 + 1; - - /* Get the height of the neighborhood */ - if (PG_ARGISNULL(4) || PG_GETARG_INT32(4) <= 0) { - elog(NOTICE, "Neighborhood height is NULL or <= 0. Returning new " - "raster with the original band"); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - ngbheight = PG_GETARG_INT32(4); - winheight = ngbheight * 2 + 1; - - /* Get the type of NODATA behavior for the neighborhoods. */ - if (PG_ARGISNULL(6)) { - elog(NOTICE, "Neighborhood NODATA behavior defaulting to 'ignore'"); - txtNodataMode = cstring_to_text("ignore"); - } - else { - txtNodataMode = PG_GETARG_TEXT_P(6); - } - - txtCallbackParam = (text*)palloc(VARSIZE(txtNodataMode)); - SET_VARSIZE(txtCallbackParam, VARSIZE(txtNodataMode)); - memcpy((void *)VARDATA(txtCallbackParam), (void *)VARDATA(txtNodataMode), VARSIZE(txtNodataMode) - VARHDRSZ); - - /* pass the nodata mode into the user function */ - cbdata.arg[1] = CStringGetDatum(txtCallbackParam); - - strFromText = text_to_cstring(txtNodataMode); - strFromText = rtpg_strtoupper(strFromText); - - if (strcmp(strFromText, "VALUE") == 0) - valuereplace = true; - else if (strcmp(strFromText, "IGNORE") != 0 && strcmp(strFromText, "NULL") != 0) { - /* if the text is not "IGNORE" or "NULL", it may be a numerical value */ - if (sscanf(strFromText, "%d", &intReplace) <= 0 && sscanf(strFromText, "%f", &fltReplace) <= 0) { - /* the value is NOT an integer NOR a floating point */ - elog(NOTICE, "Neighborhood NODATA mode is not recognized. Must be one of 'value', 'ignore', " - "'NULL', or a numeric value. Returning new raster with the original band"); - - /* clean up the nodatamode string */ - pfree(txtCallbackParam); - pfree(strFromText); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* Serialize created raster */ - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) { - elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); - PG_RETURN_NULL(); - } - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - } - else if (strcmp(strFromText, "NULL") == 0) { - /* this setting means that the neighborhood should be skipped if any of the values are null */ - nNullSkip = true; - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Main computing loop (%d x %d)", - width, height); - - /* Allocate room for the neighborhood. */ - neighborData = (Datum *)palloc(winwidth * winheight * sizeof(Datum)); - neighborNulls = (bool *)palloc(winwidth * winheight * sizeof(bool)); - - /* The dimensions of the neighborhood array, for creating a multi-dimensional array. */ - neighborDims[0] = winwidth; - neighborDims[1] = winheight; - - /* The lower bounds for the new multi-dimensional array. */ - neighborLbs[0] = 1; - neighborLbs[1] = 1; - - /* Get information about the type of item in the multi-dimensional array (float8). */ - get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign); - - for (x = 0 + ngbwidth; x < width - ngbwidth; x++) { - for(y = 0 + ngbheight; y < height - ngbheight; y++) { - /* populate an array with the pixel values in the neighborhood */ - nIndex = 0; - nNullItems = 0; - nNodataOnly = true; - pixelreplace = false; - if (valuereplace) { - ret = rt_band_get_pixel(band, x, y, &rpix, NULL); - if (ret == ES_NONE && FLT_NEQ(rpix, newnodatavalue)) { - pixelreplace = true; - } - } - for (u = x - ngbwidth; u <= x + ngbwidth; u++) { - for (v = y - ngbheight; v <= y + ngbheight; v++) { - ret = rt_band_get_pixel(band, u, v, &r, NULL); - if (ret == ES_NONE) { - if (FLT_NEQ(r, newnodatavalue)) { - /* If the pixel value for this neighbor cell is not NODATA */ - neighborData[nIndex] = Float8GetDatum((double)r); - neighborNulls[nIndex] = false; - nNodataOnly = false; - } - else { - /* If the pixel value for this neighbor cell is NODATA */ - if (valuereplace && pixelreplace) { - /* Replace the NODATA value with the currently processing pixel. */ - neighborData[nIndex] = Float8GetDatum((double)rpix); - neighborNulls[nIndex] = false; - /* do not increment nNullItems, since the user requested that the */ - /* neighborhood replace NODATA values with the central pixel value */ - } - else { - neighborData[nIndex] = PointerGetDatum(NULL); - neighborNulls[nIndex] = true; - nNullItems++; - } - } - } - else { - /* Fill this will NULL if we can't read the raster pixel. */ - neighborData[nIndex] = PointerGetDatum(NULL); - neighborNulls[nIndex] = true; - nNullItems++; - } - /* Next neighbor position */ - nIndex++; - } - } - - /** - * We compute a value only for the withdata value neighborhood since the - * nodata value has already been set by the first optimization - **/ - if (!(nNodataOnly || /* neighborhood only contains NODATA -- OR -- */ - (nNullSkip && nNullItems > 0) || /* neighborhood should skip any NODATA cells, and a NODATA cell was detected -- OR -- */ - (valuereplace && nNullItems > 0))) { /* neighborhood should replace NODATA cells with the central pixel value, and a NODATA cell was detected */ - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: (%dx%d), %dx%d neighborhood", - x, y, winwidth, winheight); - - neighborDatum = construct_md_array((void *)neighborData, neighborNulls, 2, neighborDims, neighborLbs, - FLOAT8OID, typlen, typbyval, typalign); - - /* Assign the neighbor matrix as the first argument to the user function */ - cbdata.arg[0] = PointerGetDatum(neighborDatum); - - /* Invoke the user function */ - tmpnewval = FunctionCallInvoke(&cbdata); - - /* Get the return value of the user function */ - if (cbdata.isnull) { - newval = newnodatavalue; - } - else { - newval = DatumGetFloat8(tmpnewval); - } - - POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: new value = %f", - newval); - - rt_band_set_pixel(newband, x, y, newval, NULL); - } - - /* reset the number of null items in the neighborhood */ - nNullItems = 0; - } - } - - - /* clean up */ - pfree(neighborNulls); - pfree(neighborData); - pfree(strFromText); - pfree(txtCallbackParam); - - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 0); - - /* The newrast band has been modified */ - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: raster modified, serializing it."); - /* Serialize created raster */ - - pgrtn = rt_raster_serialize(newrast); - rt_raster_destroy(newrast); - if (NULL == pgrtn) - PG_RETURN_NULL(); - - POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: raster serialized"); - POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraFctNgb: returning raster"); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/* ---------------------------------------------------------------- */ -/* n-raster MapAlgebra */ -/* ---------------------------------------------------------------- */ - -typedef struct { - Oid ufc_noid; - FmgrInfo ufl_info; - FunctionCallInfoData ufc_info; - int ufc_nullcount; -} rtpg_nmapalgebra_callback_arg; - -typedef struct rtpg_nmapalgebra_arg_t *rtpg_nmapalgebra_arg; -struct rtpg_nmapalgebra_arg_t { - int numraster; - rt_pgraster **pgraster; - rt_raster *raster; - uint8_t *isempty; /* flag indicating if raster is empty */ - uint8_t *ownsdata; /* is the raster self owned or just a pointer to another raster */ - int *nband; /* source raster's band index, 0-based */ - uint8_t *hasband; /* does source raster have band at index nband? */ - - rt_pixtype pixtype; /* output raster band's pixel type */ - int hasnodata; /* NODATA flag */ - double nodataval; /* NODATA value */ - - int distance[2]; /* distance in X and Y axis */ - - rt_extenttype extenttype; /* ouput raster's extent type */ - rt_pgraster *pgcextent; /* custom extent of type rt_pgraster */ - rt_raster cextent; /* custom extent of type rt_raster */ - - rtpg_nmapalgebra_callback_arg callback; -}; - -static rtpg_nmapalgebra_arg rtpg_nmapalgebra_arg_init() { - rtpg_nmapalgebra_arg arg = NULL; - - arg = palloc(sizeof(struct rtpg_nmapalgebra_arg_t)); - if (arg == NULL) { - elog(ERROR, "rtpg_nmapalgebra_arg_init: Could not allocate memory for arguments"); - return NULL; - } - - arg->numraster = 0; - arg->pgraster = NULL; - arg->raster = NULL; - arg->isempty = NULL; - arg->ownsdata = NULL; - arg->nband = NULL; - arg->hasband = NULL; - - arg->pixtype = PT_END; - arg->hasnodata = 1; - arg->nodataval = 0; - - arg->distance[0] = 0; - arg->distance[1] = 0; - - arg->extenttype = ET_INTERSECTION; - arg->pgcextent = NULL; - arg->cextent = NULL; - - arg->callback.ufc_noid = InvalidOid; - arg->callback.ufc_nullcount = 0; - - return arg; -} - -static void rtpg_nmapalgebra_arg_destroy(rtpg_nmapalgebra_arg arg) { - int i = 0; - - if (arg->raster != NULL) { - for (i = 0; i < arg->numraster; i++) { - if (arg->raster[i] == NULL || !arg->ownsdata[i]) - continue; - - rt_raster_destroy(arg->raster[i]); - } - - pfree(arg->raster); - pfree(arg->pgraster); - pfree(arg->isempty); - pfree(arg->ownsdata); - pfree(arg->nband); - } - - if (arg->cextent != NULL) - rt_raster_destroy(arg->cextent); - - pfree(arg); -} - -static int rtpg_nmapalgebra_rastbandarg_process(rtpg_nmapalgebra_arg arg, ArrayType *array, int *allnull, int *allempty, int *noband) { - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - - HeapTupleHeader tup; - bool isnull; - Datum tupv; - - int i; - int j; - int nband; - - if (arg == NULL || array == NULL) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: NULL values not permitted for parameters"); - return 0; - } - - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - deconstruct_array( - array, - etype, - typlen, typbyval, typalign, - &e, &nulls, &n - ); - - if (!n) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Invalid argument for rastbandarg"); - return 0; - } - - /* prep arg */ - arg->numraster = n; - arg->pgraster = palloc(sizeof(rt_pgraster *) * arg->numraster); - arg->raster = palloc(sizeof(rt_raster) * arg->numraster); - arg->isempty = palloc(sizeof(uint8_t) * arg->numraster); - arg->ownsdata = palloc(sizeof(uint8_t) * arg->numraster); - arg->nband = palloc(sizeof(int) * arg->numraster); - arg->hasband = palloc(sizeof(uint8_t) * arg->numraster); - if ( - arg->pgraster == NULL || - arg->raster == NULL || - arg->isempty == NULL || - arg->ownsdata == NULL || - arg->nband == NULL || - arg->hasband == NULL - ) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not allocate memory for processing rastbandarg"); - return 0; - } - - *allnull = 0; - *allempty = 0; - *noband = 0; - - /* process each element */ - for (i = 0; i < n; i++) { - if (nulls[i]) { - arg->numraster--; - continue; - } - - POSTGIS_RT_DEBUGF(4, "Processing rastbandarg at index %d", i); - - arg->raster[i] = NULL; - arg->isempty[i] = 0; - arg->ownsdata[i] = 1; - arg->nband[i] = 0; - arg->hasband[i] = 0; - - /* each element is a tuple */ - tup = (HeapTupleHeader) DatumGetPointer(e[i]); - if (NULL == tup) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Invalid argument for rastbandarg at index %d", i); - return 0; - } - - /* first element, raster */ - POSTGIS_RT_DEBUG(4, "Processing first element (raster)"); - tupv = GetAttributeByName(tup, "rast", &isnull); - if (isnull) { - elog(NOTICE, "First argument (nband) of rastbandarg at index %d is NULL. Assuming NULL raster", i); - arg->isempty[i] = 1; - arg->ownsdata[i] = 0; - - (*allnull)++; - (*allempty)++; - (*noband)++; - - continue; - } - - arg->pgraster[i] = (rt_pgraster *) PG_DETOAST_DATUM(tupv); - - /* see if this is a copy of an existing pgraster */ - for (j = 0; j < i; j++) { - if (!arg->isempty[j] && (arg->pgraster[i] == arg->pgraster[j])) { - POSTGIS_RT_DEBUG(4, "raster matching existing same raster found"); - arg->raster[i] = arg->raster[j]; - arg->ownsdata[i] = 0; - break; - } - } - - if (arg->ownsdata[i]) { - POSTGIS_RT_DEBUG(4, "deserializing raster"); - arg->raster[i] = rt_raster_deserialize(arg->pgraster[i], FALSE); - if (arg->raster[i] == NULL) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not deserialize raster at index %d", i); - return 0; - } - } - - /* is raster empty? */ - arg->isempty[i] = rt_raster_is_empty(arg->raster[i]); - if (arg->isempty[i]) { - (*allempty)++; - (*noband)++; - - continue; - } - - /* second element, nband */ - POSTGIS_RT_DEBUG(4, "Processing second element (nband)"); - tupv = GetAttributeByName(tup, "nband", &isnull); - if (isnull) { - nband = 1; - elog(NOTICE, "First argument (nband) of rastbandarg at index %d is NULL. Assuming nband = %d", i, nband); - } - else - nband = DatumGetInt32(tupv); - - if (nband < 1) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Band number provided for rastbandarg at index %d must be greater than zero (1-based)", i); - return 0; - } - - arg->nband[i] = nband - 1; - arg->hasband[i] = rt_raster_has_band(arg->raster[i], arg->nband[i]); - if (!arg->hasband[i]) { - (*noband)++; - POSTGIS_RT_DEBUGF(4, "Band at index %d not found in raster", nband); - } - } - - if (arg->numraster < n) { - arg->pgraster = repalloc(arg->pgraster, sizeof(rt_pgraster *) * arg->numraster); - arg->raster = repalloc(arg->raster, sizeof(rt_raster) * arg->numraster); - arg->isempty = repalloc(arg->isempty, sizeof(uint8_t) * arg->numraster); - arg->ownsdata = repalloc(arg->ownsdata, sizeof(uint8_t) * arg->numraster); - arg->nband = repalloc(arg->nband, sizeof(int) * arg->numraster); - arg->hasband = repalloc(arg->hasband, sizeof(uint8_t) * arg->numraster); - if ( - arg->pgraster == NULL || - arg->raster == NULL || - arg->isempty == NULL || - arg->ownsdata == NULL || - arg->nband == NULL || - arg->hasband == NULL - ) { - elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not reallocate memory for processed rastbandarg"); - return 0; - } - } - - POSTGIS_RT_DEBUGF(4, "arg->numraster = %d", arg->numraster); - - return 1; -} - -/* - Callback for RASTER_nMapAlgebra -*/ -static int rtpg_nmapalgebra_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - rtpg_nmapalgebra_callback_arg *callback = (rtpg_nmapalgebra_callback_arg *) userarg; - - int16 typlen; - bool typbyval; - char typalign; - - ArrayType *mdValues = NULL; - Datum *_values = NULL; - bool *_nodata = NULL; - - ArrayType *mdPos = NULL; - Datum *_pos = NULL; - bool *_null = NULL; - - int i = 0; - int x = 0; - int y = 0; - int z = 0; - int dim[3] = {0}; - int lbound[3] = {1, 1, 1}; - Datum datum = (Datum) NULL; - - if (arg == NULL) - return 0; - - *value = 0; - *nodata = 0; - - dim[0] = arg->rasters; - dim[1] = arg->rows; - dim[2] = arg->columns; - - _values = palloc(sizeof(Datum) * arg->rasters * arg->rows * arg->columns); - _nodata = palloc(sizeof(bool) * arg->rasters * arg->rows * arg->columns); - if (_values == NULL || _nodata == NULL) { - elog(ERROR, "rtpg_nmapalgebra_callback: Could not allocate memory for values array"); - return 0; - } - - /* build mdValues */ - i = 0; - /* raster */ - for (z = 0; z < arg->rasters; z++) { - /* Y axis */ - for (y = 0; y < arg->rows; y++) { - /* X axis */ - for (x = 0; x < arg->columns; x++) { - POSTGIS_RT_DEBUGF(4, "(z, y ,x) = (%d, %d, %d)", z, y, x); - POSTGIS_RT_DEBUGF(4, "(value, nodata) = (%f, %d)", arg->values[z][y][x], arg->nodata[z][y][x]); - - _nodata[i] = (bool) arg->nodata[z][y][x]; - if (!_nodata[i]) - _values[i] = Float8GetDatum(arg->values[z][y][x]); - else - _values[i] = (Datum) NULL; - - i++; - } - } - } - - /* info about the type of item in the multi-dimensional array (float8). */ - get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign); - - /* construct mdValues */ - mdValues = construct_md_array( - _values, _nodata, - 3, dim, lbound, - FLOAT8OID, - typlen, typbyval, typalign - ); - pfree(_nodata); - pfree(_values); - - _pos = palloc(sizeof(Datum) * (arg->rasters + 1) * 2); - _null = palloc(sizeof(bool) * (arg->rasters + 1) * 2); - if (_pos == NULL || _null == NULL) { - pfree(mdValues); - elog(ERROR, "rtpg_nmapalgebra_callback: Could not allocate memory for position array"); - return 0; - } - memset(_null, 0, sizeof(bool) * (arg->rasters + 1) * 2); - - /* build mdPos */ - i = 0; - _pos[i] = arg->dst_pixel[0] + 1; - i++; - _pos[i] = arg->dst_pixel[1] + 1; - i++; - - for (z = 0; z < arg->rasters; z++) { - _pos[i] = arg->src_pixel[z][0] + 1; - i++; - - _pos[i] = arg->src_pixel[z][1] + 1; - i++; - } - - /* info about the type of item in the multi-dimensional array (int4). */ - get_typlenbyvalalign(INT4OID, &typlen, &typbyval, &typalign); - - /* reuse dim and lbound, just tweak to what we need */ - dim[0] = arg->rasters + 1; - dim[1] = 2; - lbound[0] = 0; - - /* construct mdPos */ - mdPos = construct_md_array( - _pos, _null, - 2, dim, lbound, - INT4OID, - typlen, typbyval, typalign - ); - pfree(_pos); - pfree(_null); - - callback->ufc_info.arg[0] = PointerGetDatum(mdValues); - callback->ufc_info.arg[1] = PointerGetDatum(mdPos); - - /* function is strict and null parameter is passed */ - /* http://archives.postgresql.org/pgsql-general/2011-11/msg00424.php */ - if (callback->ufl_info.fn_strict && callback->ufc_nullcount) { - *nodata = 1; - - pfree(mdValues); - pfree(mdPos); - - return 1; - } - - /* call user callback function */ - datum = FunctionCallInvoke(&(callback->ufc_info)); - pfree(mdValues); - pfree(mdPos); - - /* result is not null*/ - if (!callback->ufc_info.isnull) - *value = DatumGetFloat8(datum); - else - *nodata = 1; - - return 1; -} - -/* - ST_MapAlgebra for n rasters -*/ -PG_FUNCTION_INFO_V1(RASTER_nMapAlgebra); -Datum RASTER_nMapAlgebra(PG_FUNCTION_ARGS) -{ - rtpg_nmapalgebra_arg arg = NULL; - rt_iterator itrset; - int i = 0; - int noerr = 0; - int allnull = 0; - int allempty = 0; - int noband = 0; - - rt_raster raster = NULL; - rt_band band = NULL; - rt_pgraster *pgraster = NULL; - - POSTGIS_RT_DEBUG(3, "Starting..."); - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - /* init argument struct */ - arg = rtpg_nmapalgebra_arg_init(); - if (arg == NULL) { - elog(ERROR, "RASTER_nMapAlgebra: Could not initialize argument structure"); - PG_RETURN_NULL(); - } - - /* let helper function process rastbandarg (0) */ - if (!rtpg_nmapalgebra_rastbandarg_process(arg, PG_GETARG_ARRAYTYPE_P(0), &allnull, &allempty, &noband)) { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Could not process rastbandarg"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(4, "allnull, allempty, noband = %d, %d, %d", allnull, allempty, noband); - - /* all rasters are NULL, return NULL */ - if (allnull == arg->numraster) { - elog(NOTICE, "All input rasters are NULL. Returning NULL"); - rtpg_nmapalgebra_arg_destroy(arg); - PG_RETURN_NULL(); - } - - /* pixel type (2) */ - if (!PG_ARGISNULL(2)) { - char *pixtypename = text_to_cstring(PG_GETARG_TEXT_P(2)); - - /* Get the pixel type index */ - arg->pixtype = rt_pixtype_index_from_name(pixtypename); - if (arg->pixtype == PT_END) { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Invalid pixel type: %s", pixtypename); - PG_RETURN_NULL(); - } - } - - /* distancex (3) */ - if (!PG_ARGISNULL(3)) - arg->distance[0] = PG_GETARG_INT32(3); - /* distancey (4) */ - if (!PG_ARGISNULL(4)) - arg->distance[1] = PG_GETARG_INT32(4); - - if (arg->distance[0] < 0 || arg->distance[1] < 0) { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Distance for X and Y axis must be greater than or equal to zero"); - PG_RETURN_NULL(); - } - - /* extent type (5) */ - if (!PG_ARGISNULL(5)) { - char *extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(5)))); - arg->extenttype = rt_util_extent_type(extenttypename); - } - POSTGIS_RT_DEBUGF(4, "extenttype: %d", arg->extenttype); - - /* custom extent (6) */ - if (arg->extenttype == ET_CUSTOM) { - if (PG_ARGISNULL(6)) { - elog(NOTICE, "Custom extent is NULL. Returning NULL"); - rtpg_nmapalgebra_arg_destroy(arg); - PG_RETURN_NULL(); - } - - arg->pgcextent = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(6)); - - /* only need the raster header */ - arg->cextent = rt_raster_deserialize(arg->pgcextent, TRUE); - if (arg->cextent == NULL) { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Could not deserialize custom extent"); - PG_RETURN_NULL(); - } - else if (rt_raster_is_empty(arg->cextent)) { - elog(NOTICE, "Custom extent is an empty raster. Returning empty raster"); - rtpg_nmapalgebra_arg_destroy(arg); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - elog(ERROR, "RASTER_nMapAlgebra: Could not create empty raster"); - PG_RETURN_NULL(); - } - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgraster) PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); - } - } - - noerr = 1; - /* all rasters are empty, return empty raster */ - if (allempty == arg->numraster) { - elog(NOTICE, "All input rasters are empty. Returning empty raster"); - noerr = 0; - } - /* all rasters don't have indicated band, return empty raster */ - else if (noband == arg->numraster) { - elog(NOTICE, "All input rasters do not have bands at indicated indexes. Returning empty raster"); - noerr = 0; - } - if (!noerr) { - rtpg_nmapalgebra_arg_destroy(arg); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - elog(ERROR, "RASTER_nMapAlgebra: Could not create empty raster"); - PG_RETURN_NULL(); - } - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgraster) PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); - } - - /* do regprocedure last (1) */ - if (!PG_ARGISNULL(1) || get_fn_expr_argtype(fcinfo->flinfo, 1) == REGPROCEDUREOID) { - POSTGIS_RT_DEBUG(4, "processing callbackfunc"); - arg->callback.ufc_noid = PG_GETARG_OID(1); - - /* get function info */ - fmgr_info(arg->callback.ufc_noid, &(arg->callback.ufl_info)); - - /* function cannot return set */ - noerr = 0; - if (arg->callback.ufl_info.fn_retset) { - noerr = 1; - } - /* function should have correct # of args */ - else if (arg->callback.ufl_info.fn_nargs != 3) { - noerr = 2; - } - - /* - TODO: consider adding checks of the userfunction parameters - should be able to use get_fn_expr_argtype() of fmgr.c - */ - - if (noerr > 0) { - rtpg_nmapalgebra_arg_destroy(arg); - if (noerr > 1) - elog(ERROR, "RASTER_nMapAlgebra: Function provided must have three input parameters"); - else - elog(ERROR, "RASTER_nMapAlgebra: Function provided must return double precision, not resultset"); - PG_RETURN_NULL(); - } - - if (func_volatile(arg->callback.ufc_noid) == 'v') - elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); - - /* prep function call data */ -#if POSTGIS_PGSQL_VERSION > 90 - InitFunctionCallInfoData(arg->callback.ufc_info, &(arg->callback.ufl_info), arg->callback.ufl_info.fn_nargs, InvalidOid, NULL, NULL); -#else - InitFunctionCallInfoData(arg->callback.ufc_info, &(arg->callback.ufl_info), arg->callback.ufl_info.fn_nargs, NULL, NULL); -#endif - memset(arg->callback.ufc_info.argnull, FALSE, sizeof(bool) * arg->callback.ufl_info.fn_nargs); - - /* userargs (7) */ - if (!PG_ARGISNULL(7)) - arg->callback.ufc_info.arg[2] = PG_GETARG_DATUM(7); - else { - arg->callback.ufc_info.arg[2] = (Datum) NULL; - arg->callback.ufc_info.argnull[2] = TRUE; - arg->callback.ufc_nullcount++; - } - } - else { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: callbackfunc must be provided"); - PG_RETURN_NULL(); - } - - /* determine nodataval and possibly pixtype */ - /* band to check */ - switch (arg->extenttype) { - case ET_LAST: - i = arg->numraster - 1; - break; - case ET_SECOND: - if (arg->numraster > 1) { - i = 1; - break; - } - default: - i = 0; - break; - } - /* find first viable band */ - if (!arg->hasband[i]) { - for (i = 0; i < arg->numraster; i++) { - if (arg->hasband[i]) - break; - } - if (i >= arg->numraster) - i = arg->numraster - 1; - } - band = rt_raster_get_band(arg->raster[i], arg->nband[i]); - - /* set pixel type if PT_END */ - if (arg->pixtype == PT_END) - arg->pixtype = rt_band_get_pixtype(band); - - /* set hasnodata and nodataval */ - arg->hasnodata = 1; - if (rt_band_get_hasnodata_flag(band)) - rt_band_get_nodata(band, &(arg->nodataval)); - else - arg->nodataval = rt_band_get_min_value(band); - - POSTGIS_RT_DEBUGF(4, "pixtype, hasnodata, nodataval: %s, %d, %f", rt_pixtype_name(arg->pixtype), arg->hasnodata, arg->nodataval); - - /* init itrset */ - itrset = palloc(sizeof(struct rt_iterator_t) * arg->numraster); - if (itrset == NULL) { - rtpg_nmapalgebra_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Could not allocate memory for iterator arguments"); - PG_RETURN_NULL(); - } - - /* set itrset */ - for (i = 0; i < arg->numraster; i++) { - itrset[i].raster = arg->raster[i]; - itrset[i].nband = arg->nband[i]; - itrset[i].nbnodata = 1; - } - - /* pass everything to iterator */ - noerr = rt_raster_iterator( - itrset, arg->numraster, - arg->extenttype, arg->cextent, - arg->pixtype, - arg->hasnodata, arg->nodataval, - arg->distance[0], arg->distance[1], - &(arg->callback), - rtpg_nmapalgebra_callback, - &raster - ); - - /* cleanup */ - pfree(itrset); - rtpg_nmapalgebra_arg_destroy(arg); - - if (noerr != ES_NONE) { - elog(ERROR, "RASTER_nMapAlgebra: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - else if (raster == NULL) - PG_RETURN_NULL(); - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - - POSTGIS_RT_DEBUG(3, "Finished"); - - if (!pgraster) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); -} - -/* ---------------------------------------------------------------- */ -/* expression ST_MapAlgebra for n rasters */ -/* ---------------------------------------------------------------- */ - -typedef struct { - int exprcount; - - struct { - SPIPlanPtr spi_plan; - uint32_t spi_argcount; - uint8_t *spi_argpos; - - int hasval; - double val; - } expr[3]; - - struct { - int hasval; - double val; - } nodatanodata; - - struct { - int count; - char **val; - } kw; - -} rtpg_nmapalgebraexpr_callback_arg; - -typedef struct rtpg_nmapalgebraexpr_arg_t *rtpg_nmapalgebraexpr_arg; -struct rtpg_nmapalgebraexpr_arg_t { - rtpg_nmapalgebra_arg bandarg; - - rtpg_nmapalgebraexpr_callback_arg callback; -}; - -static rtpg_nmapalgebraexpr_arg rtpg_nmapalgebraexpr_arg_init(int cnt, char **kw) { - rtpg_nmapalgebraexpr_arg arg = NULL; - int i = 0; - - arg = palloc(sizeof(struct rtpg_nmapalgebraexpr_arg_t)); - if (arg == NULL) { - elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for arguments"); - return NULL; - } - - arg->bandarg = rtpg_nmapalgebra_arg_init(); - if (arg->bandarg == NULL) { - elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for arg->bandarg"); - return NULL; - } - - arg->callback.kw.count = cnt; - arg->callback.kw.val = kw; - - arg->callback.exprcount = 3; - for (i = 0; i < arg->callback.exprcount; i++) { - arg->callback.expr[i].spi_plan = NULL; - arg->callback.expr[i].spi_argcount = 0; - arg->callback.expr[i].spi_argpos = palloc(cnt * sizeof(uint8_t)); - if (arg->callback.expr[i].spi_argpos == NULL) { - elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for spi_argpos"); - return NULL; - } - memset(arg->callback.expr[i].spi_argpos, 0, sizeof(uint8_t) * cnt); - arg->callback.expr[i].hasval = 0; - arg->callback.expr[i].val = 0; - } - - arg->callback.nodatanodata.hasval = 0; - arg->callback.nodatanodata.val = 0; - - return arg; -} - -static void rtpg_nmapalgebraexpr_arg_destroy(rtpg_nmapalgebraexpr_arg arg) { - int i = 0; - - rtpg_nmapalgebra_arg_destroy(arg->bandarg); - - for (i = 0; i < arg->callback.exprcount; i++) { - if (arg->callback.expr[i].spi_plan) - SPI_freeplan(arg->callback.expr[i].spi_plan); - if (arg->callback.kw.count) - pfree(arg->callback.expr[i].spi_argpos); - } - - pfree(arg); -} - -static int rtpg_nmapalgebraexpr_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - rtpg_nmapalgebraexpr_callback_arg *callback = (rtpg_nmapalgebraexpr_callback_arg *) userarg; - SPIPlanPtr plan = NULL; - int i = 0; - int id = -1; - - if (arg == NULL) - return 0; - - *value = 0; - *nodata = 0; - - /* 2 raster */ - if (arg->rasters > 1) { - /* nodata1 = 1 AND nodata2 = 1, nodatanodataval */ - if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) { - if (callback->nodatanodata.hasval) - *value = callback->nodatanodata.val; - else - *nodata = 1; - } - /* nodata1 = 1 AND nodata2 != 1, nodata1expr */ - else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) { - id = 1; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else - *nodata = 1; - } - /* nodata1 != 1 AND nodata2 = 1, nodata2expr */ - else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) { - id = 2; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else - *nodata = 1; - } - /* expression */ - else { - id = 0; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else { - if (callback->nodatanodata.hasval) - *value = callback->nodatanodata.val; - else - *nodata = 1; - } - } - } - /* 1 raster */ - else { - /* nodata = 1, nodata1expr */ - if (arg->nodata[0][0][0]) { - id = 1; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else - *nodata = 1; - } - /* expression */ - else { - id = 0; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else { - /* see if nodata1expr is available */ - id = 1; - if (callback->expr[id].hasval) - *value = callback->expr[id].val; - else if (callback->expr[id].spi_plan) - plan = callback->expr[id].spi_plan; - else - *nodata = 1; - } - } - } - - /* run prepared plan */ - if (plan != NULL) { - Datum values[12]; - bool nulls[12]; - int err = 0; - - TupleDesc tupdesc; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isnull = FALSE; - - POSTGIS_RT_DEBUGF(4, "Running plan %d", id); - - /* init values and nulls */ - memset(values, (Datum) NULL, sizeof(Datum) * callback->kw.count); - memset(nulls, FALSE, sizeof(bool) * callback->kw.count); - - if (callback->expr[id].spi_argcount) { - int idx = 0; - - for (i = 0; i < callback->kw.count; i++) { - idx = callback->expr[id].spi_argpos[i]; - if (idx < 1) continue; - idx--; /* 1-based now 0-based */ - - switch (i) { - /* [rast.x] */ - case 0: - values[idx] = Int32GetDatum(arg->src_pixel[0][0] + 1); - break; - /* [rast.y] */ - case 1: - values[idx] = Int32GetDatum(arg->src_pixel[0][1] + 1); - break; - /* [rast.val] */ - case 2: - /* [rast] */ - case 3: - if (!arg->nodata[0][0][0]) - values[idx] = Float8GetDatum(arg->values[0][0][0]); - else - nulls[idx] = TRUE; - break; - - /* [rast1.x] */ - case 4: - values[idx] = Int32GetDatum(arg->src_pixel[0][0] + 1); - break; - /* [rast1.y] */ - case 5: - values[idx] = Int32GetDatum(arg->src_pixel[0][1] + 1); - break; - /* [rast1.val] */ - case 6: - /* [rast1] */ - case 7: - if (!arg->nodata[0][0][0]) - values[idx] = Float8GetDatum(arg->values[0][0][0]); - else - nulls[idx] = TRUE; - break; - - /* [rast2.x] */ - case 8: - values[idx] = Int32GetDatum(arg->src_pixel[1][0] + 1); - break; - /* [rast2.y] */ - case 9: - values[idx] = Int32GetDatum(arg->src_pixel[1][1] + 1); - break; - /* [rast2.val] */ - case 10: - /* [rast2] */ - case 11: - if (!arg->nodata[1][0][0]) - values[idx] = Float8GetDatum(arg->values[1][0][0]); - else - nulls[idx] = TRUE; - break; - } - - } - } - - /* run prepared plan */ - err = SPI_execute_plan(plan, values, nulls, TRUE, 1); - if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - elog(ERROR, "rtpg_nmapalgebraexpr_callback: Unexpected error when running prepared statement %d", id); - return 0; - } - - /* get output of prepared plan */ - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - if (SPI_tuptable) SPI_freetuptable(tuptable); - elog(ERROR, "rtpg_nmapalgebraexpr_callback: Could not get result of prepared statement %d", id); - return 0; - } - - if (!isnull) { - *value = DatumGetFloat8(datum); - POSTGIS_RT_DEBUG(4, "Getting value from Datum"); - } - else { - /* 2 raster, check nodatanodataval */ - if (arg->rasters > 1) { - if (callback->nodatanodata.hasval) - *value = callback->nodatanodata.val; - else - *nodata = 1; - } - /* 1 raster, check nodataval */ - else { - if (callback->expr[1].hasval) - *value = callback->expr[1].val; - else - *nodata = 1; - } - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - } - - POSTGIS_RT_DEBUGF(4, "(value, nodata) = (%f, %d)", *value, *nodata); - return 1; -} - -PG_FUNCTION_INFO_V1(RASTER_nMapAlgebraExpr); -Datum RASTER_nMapAlgebraExpr(PG_FUNCTION_ARGS) -{ - MemoryContext mainMemCtx = CurrentMemoryContext; - rtpg_nmapalgebraexpr_arg arg = NULL; - rt_iterator itrset; - uint16_t exprpos[3] = {1, 4, 5}; - - int i = 0; - int j = 0; - int k = 0; - - int numraster = 0; - int err = 0; - int allnull = 0; - int allempty = 0; - int noband = 0; - int len = 0; - - TupleDesc tupdesc; - SPITupleTable *tuptable = NULL; - HeapTuple tuple; - Datum datum; - bool isnull = FALSE; - - rt_raster raster = NULL; - rt_band band = NULL; - rt_pgraster *pgraster = NULL; - - const int argkwcount = 12; - char *argkw[] = { - "[rast.x]", - "[rast.y]", - "[rast.val]", - "[rast]", - "[rast1.x]", - "[rast1.y]", - "[rast1.val]", - "[rast1]", - "[rast2.x]", - "[rast2.y]", - "[rast2.val]", - "[rast2]" - }; - - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - /* init argument struct */ - arg = rtpg_nmapalgebraexpr_arg_init(argkwcount, argkw); - if (arg == NULL) { - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not initialize argument structure"); - PG_RETURN_NULL(); - } - - /* let helper function process rastbandarg (0) */ - if (!rtpg_nmapalgebra_rastbandarg_process(arg->bandarg, PG_GETARG_ARRAYTYPE_P(0), &allnull, &allempty, &noband)) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebra: Could not process rastbandarg"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(4, "allnull, allempty, noband = %d, %d, %d", allnull, allempty, noband); - - /* all rasters are NULL, return NULL */ - if (allnull == arg->bandarg->numraster) { - elog(NOTICE, "All input rasters are NULL. Returning NULL"); - rtpg_nmapalgebraexpr_arg_destroy(arg); - PG_RETURN_NULL(); - } - - /* only work on one or two rasters */ - if (arg->bandarg->numraster > 1) - numraster = 2; - else - numraster = 1; - - /* pixel type (2) */ - if (!PG_ARGISNULL(2)) { - char *pixtypename = text_to_cstring(PG_GETARG_TEXT_P(2)); - - /* Get the pixel type index */ - arg->bandarg->pixtype = rt_pixtype_index_from_name(pixtypename); - if (arg->bandarg->pixtype == PT_END) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebraExpr: Invalid pixel type: %s", pixtypename); - PG_RETURN_NULL(); - } - } - POSTGIS_RT_DEBUGF(4, "pixeltype: %d", arg->bandarg->pixtype); - - /* extent type (3) */ - if (!PG_ARGISNULL(3)) { - char *extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(3)))); - arg->bandarg->extenttype = rt_util_extent_type(extenttypename); - } - - if (arg->bandarg->extenttype == ET_CUSTOM) { - if (numraster < 2) { - elog(NOTICE, "CUSTOM extent type not supported. Defaulting to FIRST"); - arg->bandarg->extenttype = ET_FIRST; - } - else { - elog(NOTICE, "CUSTOM extent type not supported. Defaulting to INTERSECTION"); - arg->bandarg->extenttype = ET_INTERSECTION; - } - } - else if (numraster < 2) - arg->bandarg->extenttype = ET_FIRST; - - POSTGIS_RT_DEBUGF(4, "extenttype: %d", arg->bandarg->extenttype); - - /* nodatanodataval (6) */ - if (!PG_ARGISNULL(6)) { - arg->callback.nodatanodata.hasval = 1; - arg->callback.nodatanodata.val = PG_GETARG_FLOAT8(6); - } - - err = 0; - /* all rasters are empty, return empty raster */ - if (allempty == arg->bandarg->numraster) { - elog(NOTICE, "All input rasters are empty. Returning empty raster"); - err = 1; - } - /* all rasters don't have indicated band, return empty raster */ - else if (noband == arg->bandarg->numraster) { - elog(NOTICE, "All input rasters do not have bands at indicated indexes. Returning empty raster"); - err = 1; - } - if (err) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - - raster = rt_raster_new(0, 0); - if (raster == NULL) { - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not create empty raster"); - PG_RETURN_NULL(); - } - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - if (!pgraster) PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); - } - - /* connect SPI */ - if (SPI_connect() != SPI_OK_CONNECT) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not connect to the SPI manager"); - PG_RETURN_NULL(); - } - - /* - process expressions - - exprpos elements are: - 1 - expression => spi_plan[0] - 4 - nodata1expr => spi_plan[1] - 5 - nodata2expr => spi_plan[2] - */ - for (i = 0; i < arg->callback.exprcount; i++) { - char *expr = NULL; - char *tmp = NULL; - char *sql = NULL; - char place[5] = "$1"; - - if (PG_ARGISNULL(exprpos[i])) - continue; - - expr = text_to_cstring(PG_GETARG_TEXT_P(exprpos[i])); - POSTGIS_RT_DEBUGF(3, "raw expr of argument #%d: %s", exprpos[i], expr); - - for (j = 0, k = 1; j < argkwcount; j++) { - /* attempt to replace keyword with placeholder */ - len = 0; - tmp = rtpg_strreplace(expr, argkw[j], place, &len); - pfree(expr); - expr = tmp; - - if (len) { - POSTGIS_RT_DEBUGF(4, "kw #%d (%s) at pos $%d", j, argkw[j], k); - arg->callback.expr[i].spi_argcount++; - arg->callback.expr[i].spi_argpos[j] = k++; - - sprintf(place, "$%d", k); - } - else - arg->callback.expr[i].spi_argpos[j] = 0; - } - - len = strlen("SELECT (") + strlen(expr) + strlen(")::double precision"); - sql = (char *) palloc(len + 1); - if (sql == NULL) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not allocate memory for expression parameter %d", exprpos[i]); - PG_RETURN_NULL(); - } - - strncpy(sql, "SELECT (", strlen("SELECT (")); - strncpy(sql + strlen("SELECT ("), expr, strlen(expr)); - strncpy(sql + strlen("SELECT (") + strlen(expr), ")::double precision", strlen(")::double precision")); - sql[len] = '\0'; - - POSTGIS_RT_DEBUGF(3, "sql #%d: %s", exprpos[i], sql); - - /* prepared plan */ - if (arg->callback.expr[i].spi_argcount) { - Oid *argtype = (Oid *) palloc(arg->callback.expr[i].spi_argcount * sizeof(Oid)); - POSTGIS_RT_DEBUGF(3, "expression parameter %d is a prepared plan", exprpos[i]); - if (argtype == NULL) { - pfree(sql); - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not allocate memory for prepared plan argtypes of expression parameter %d", exprpos[i]); - PG_RETURN_NULL(); - } - - /* specify datatypes of parameters */ - for (j = 0, k = 0; j < argkwcount; j++) { - if (arg->callback.expr[i].spi_argpos[j] < 1) continue; - - /* positions are INT4 */ - if ( - (strstr(argkw[j], "[rast.x]") != NULL) || - (strstr(argkw[j], "[rast.y]") != NULL) || - (strstr(argkw[j], "[rast1.x]") != NULL) || - (strstr(argkw[j], "[rast1.y]") != NULL) || - (strstr(argkw[j], "[rast2.x]") != NULL) || - (strstr(argkw[j], "[rast2.y]") != NULL) - ) - argtype[k] = INT4OID; - /* everything else is FLOAT8 */ - else - argtype[k] = FLOAT8OID; - - k++; - } - - arg->callback.expr[i].spi_plan = SPI_prepare(sql, arg->callback.expr[i].spi_argcount, argtype); - pfree(argtype); - pfree(sql); - - if (arg->callback.expr[i].spi_plan == NULL) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not create prepared plan of expression parameter %d", exprpos[i]); - PG_RETURN_NULL(); - } - } - /* no args, just execute query */ - else { - POSTGIS_RT_DEBUGF(3, "expression parameter %d has no args, simply executing", exprpos[i]); - err = SPI_execute(sql, TRUE, 0); - pfree(sql); - - if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not evaluate expression parameter %d", exprpos[i]); - PG_RETURN_NULL(); - } - - /* get output of prepared plan */ - tupdesc = SPI_tuptable->tupdesc; - tuptable = SPI_tuptable; - tuple = tuptable->vals[0]; - - datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); - if (SPI_result == SPI_ERROR_NOATTRIBUTE) { - if (SPI_tuptable) SPI_freetuptable(tuptable); - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not get result of expression parameter %d", exprpos[i]); - PG_RETURN_NULL(); - } - - if (!isnull) { - arg->callback.expr[i].hasval = 1; - arg->callback.expr[i].val = DatumGetFloat8(datum); - } - - if (SPI_tuptable) SPI_freetuptable(tuptable); - } - } - - /* determine nodataval and possibly pixtype */ - /* band to check */ - switch (arg->bandarg->extenttype) { - case ET_LAST: - case ET_SECOND: - if (numraster > 1) - i = 1; - else - i = 0; - break; - default: - i = 0; - break; - } - /* find first viable band */ - if (!arg->bandarg->hasband[i]) { - for (i = 0; i < numraster; i++) { - if (arg->bandarg->hasband[i]) - break; - } - if (i >= numraster) - i = numraster - 1; - } - band = rt_raster_get_band(arg->bandarg->raster[i], arg->bandarg->nband[i]); - - /* set pixel type if PT_END */ - if (arg->bandarg->pixtype == PT_END) - arg->bandarg->pixtype = rt_band_get_pixtype(band); - - /* set hasnodata and nodataval */ - arg->bandarg->hasnodata = 1; - if (rt_band_get_hasnodata_flag(band)) - rt_band_get_nodata(band, &(arg->bandarg->nodataval)); - else - arg->bandarg->nodataval = rt_band_get_min_value(band); - - POSTGIS_RT_DEBUGF(4, "pixtype, hasnodata, nodataval: %s, %d, %f", rt_pixtype_name(arg->bandarg->pixtype), arg->bandarg->hasnodata, arg->bandarg->nodataval); - - /* init itrset */ - itrset = palloc(sizeof(struct rt_iterator_t) * numraster); - if (itrset == NULL) { - rtpg_nmapalgebraexpr_arg_destroy(arg); - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebra: Could not allocate memory for iterator arguments"); - PG_RETURN_NULL(); - } - - /* set itrset */ - for (i = 0; i < numraster; i++) { - itrset[i].raster = arg->bandarg->raster[i]; - itrset[i].nband = arg->bandarg->nband[i]; - itrset[i].nbnodata = 1; - } - - /* pass everything to iterator */ - err = rt_raster_iterator( - itrset, numraster, - arg->bandarg->extenttype, arg->bandarg->cextent, - arg->bandarg->pixtype, - arg->bandarg->hasnodata, arg->bandarg->nodataval, - 0, 0, - &(arg->callback), - rtpg_nmapalgebraexpr_callback, - &raster - ); - - pfree(itrset); - rtpg_nmapalgebraexpr_arg_destroy(arg); - - if (err != ES_NONE) { - SPI_finish(); - elog(ERROR, "RASTER_nMapAlgebraExpr: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - else if (raster == NULL) { - SPI_finish(); - PG_RETURN_NULL(); - } - - /* switch to prior memory context to ensure memory allocated in correct context */ - MemoryContextSwitchTo(mainMemCtx); - - pgraster = rt_raster_serialize(raster); - rt_raster_destroy(raster); - - /* finish SPI */ - SPI_finish(); - - if (!pgraster) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); -} - -/* ---------------------------------------------------------------- */ -/* ST_Union aggregate functions */ -/* ---------------------------------------------------------------- */ - -typedef enum { - UT_LAST = 0, - UT_FIRST, - UT_MIN, - UT_MAX, - UT_COUNT, - UT_SUM, - UT_MEAN, - UT_RANGE -} rtpg_union_type; - -/* internal function translating text of UNION type to enum */ -static rtpg_union_type rtpg_uniontype_index_from_name(const char *cutype) { - assert(cutype && strlen(cutype) > 0); - - if (strcmp(cutype, "LAST") == 0) - return UT_LAST; - else if (strcmp(cutype, "FIRST") == 0) - return UT_FIRST; - else if (strcmp(cutype, "MIN") == 0) - return UT_MIN; - else if (strcmp(cutype, "MAX") == 0) - return UT_MAX; - else if (strcmp(cutype, "COUNT") == 0) - return UT_COUNT; - else if (strcmp(cutype, "SUM") == 0) - return UT_SUM; - else if (strcmp(cutype, "MEAN") == 0) - return UT_MEAN; - else if (strcmp(cutype, "RANGE") == 0) - return UT_RANGE; - - return UT_LAST; -} - -typedef struct rtpg_union_band_arg_t *rtpg_union_band_arg; -struct rtpg_union_band_arg_t { - int nband; /* source raster's band index, 0-based */ - rtpg_union_type uniontype; - - int numraster; - rt_raster *raster; -}; - -typedef struct rtpg_union_arg_t *rtpg_union_arg; -struct rtpg_union_arg_t { - int numband; /* number of bandargs */ - rtpg_union_band_arg bandarg; -}; - -static void rtpg_union_arg_destroy(rtpg_union_arg arg) { - int i = 0; - int j = 0; - int k = 0; - - if (arg->bandarg != NULL) { - for (i = 0; i < arg->numband; i++) { - if (!arg->bandarg[i].numraster) - continue; - - for (j = 0; j < arg->bandarg[i].numraster; j++) { - if (arg->bandarg[i].raster[j] == NULL) - continue; - - for (k = rt_raster_get_num_bands(arg->bandarg[i].raster[j]) - 1; k >= 0; k--) - rt_band_destroy(rt_raster_get_band(arg->bandarg[i].raster[j], k)); - rt_raster_destroy(arg->bandarg[i].raster[j]); - } - - pfree(arg->bandarg[i].raster); - } - - pfree(arg->bandarg); - } - - pfree(arg); -} - -static int rtpg_union_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - rtpg_union_type *utype = (rtpg_union_type *) userarg; - - if (arg == NULL) - return 0; - - if ( - arg->rasters != 2 || - arg->rows != 1 || - arg->columns != 1 - ) { - elog(ERROR, "rtpg_union_callback: Invalid arguments passed to callback"); - return 0; - } - - *value = 0; - *nodata = 0; - - /* handle NODATA situations except for COUNT, which is a special case */ - if (*utype != UT_COUNT) { - /* both NODATA */ - if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) { - *nodata = 1; - POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); - return 1; - } - /* second NODATA */ - else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) { - *value = arg->values[0][0][0]; - POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); - return 1; - } - /* first NODATA */ - else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) { - *value = arg->values[1][0][0]; - POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); - return 1; - } - } - - switch (*utype) { - case UT_FIRST: - *value = arg->values[0][0][0]; - break; - case UT_MIN: - if (arg->values[0][0][0] < arg->values[1][0][0]) - *value = arg->values[0][0][0]; - else - *value = arg->values[1][0][0]; - break; - case UT_MAX: - if (arg->values[0][0][0] > arg->values[1][0][0]) - *value = arg->values[0][0][0]; - else - *value = arg->values[1][0][0]; - break; - case UT_COUNT: - /* both NODATA */ - if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) - *value = 0; - /* second NODATA */ - else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) - *value = arg->values[0][0][0]; - /* first NODATA */ - else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) - *value = 1; - /* has value, increment */ - else - *value = arg->values[0][0][0] + 1; - break; - case UT_SUM: - *value = arg->values[0][0][0] + arg->values[1][0][0]; - break; - case UT_MEAN: - case UT_RANGE: - break; - case UT_LAST: - default: - *value = arg->values[1][0][0]; - break; - } - - POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); - - - return 1; -} - -static int rtpg_union_mean_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - if (arg == NULL) - return 0; - - if ( - arg->rasters != 2 || - arg->rows != 1 || - arg->columns != 1 - ) { - elog(ERROR, "rtpg_union_mean_callback: Invalid arguments passed to callback"); - return 0; - } - - *value = 0; - *nodata = 1; - - POSTGIS_RT_DEBUGF(4, "rast0: %f %d", arg->values[0][0][0], arg->nodata[0][0][0]); - POSTGIS_RT_DEBUGF(4, "rast1: %f %d", arg->values[1][0][0], arg->nodata[1][0][0]); - - if ( - !arg->nodata[0][0][0] && - FLT_NEQ(arg->values[0][0][0], 0) && - !arg->nodata[1][0][0] - ) { - *value = arg->values[1][0][0] / arg->values[0][0][0]; - *nodata = 0; - } - - POSTGIS_RT_DEBUGF(4, "value, nodata = (%f, %d)", *value, *nodata); - - return 1; -} - -static int rtpg_union_range_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - if (arg == NULL) - return 0; - - if ( - arg->rasters != 2 || - arg->rows != 1 || - arg->columns != 1 - ) { - elog(ERROR, "rtpg_union_range_callback: Invalid arguments passed to callback"); - return 0; - } - - *value = 0; - *nodata = 1; - - POSTGIS_RT_DEBUGF(4, "rast0: %f %d", arg->values[0][0][0], arg->nodata[0][0][0]); - POSTGIS_RT_DEBUGF(4, "rast1: %f %d", arg->values[1][0][0], arg->nodata[1][0][0]); - - if ( - !arg->nodata[0][0][0] && - !arg->nodata[1][0][0] - ) { - *value = arg->values[1][0][0] - arg->values[0][0][0]; - *nodata = 0; - } - - POSTGIS_RT_DEBUGF(4, "value, nodata = (%f, %d)", *value, *nodata); - - return 1; -} - -/* called for ST_Union(raster, unionarg[]) */ -static int rtpg_union_unionarg_process(rtpg_union_arg arg, ArrayType *array) { - Oid etype; - Datum *e; - bool *nulls; - int16 typlen; - bool typbyval; - char typalign; - int n = 0; - - HeapTupleHeader tup; - bool isnull; - Datum tupv; - - int i; - int nband = 1; - char *utypename = NULL; - rtpg_union_type utype = UT_LAST; - - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - deconstruct_array( - array, - etype, - typlen, typbyval, typalign, - &e, &nulls, &n - ); - - if (!n) { - elog(ERROR, "rtpg_union_unionarg_process: Invalid argument for unionarg"); - return 0; - } - - /* prep arg */ - arg->numband = n; - arg->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * arg->numband); - if (arg->bandarg == NULL) { - elog(ERROR, "rtpg_union_unionarg_process: Could not allocate memory for band information"); - return 0; - } - - /* process each element */ - for (i = 0; i < n; i++) { - if (nulls[i]) { - arg->numband--; - continue; - } - - POSTGIS_RT_DEBUGF(4, "Processing unionarg at index %d", i); - - /* each element is a tuple */ - tup = (HeapTupleHeader) DatumGetPointer(e[i]); - if (NULL == tup) { - elog(ERROR, "rtpg_union_unionarg_process: Invalid argument for unionarg"); - return 0; - } - - /* first element, bandnum */ - tupv = GetAttributeByName(tup, "nband", &isnull); - if (isnull) { - nband = i + 1; - elog(NOTICE, "First argument (nband) of unionarg is NULL. Assuming nband = %d", nband); - } - else - nband = DatumGetInt32(tupv); - - if (nband < 1) { - elog(ERROR, "rtpg_union_unionarg_process: Band number must be greater than zero (1-based)"); - return 0; - } - - /* second element, uniontype */ - tupv = GetAttributeByName(tup, "uniontype", &isnull); - if (isnull) { - elog(NOTICE, "Second argument (uniontype) of unionarg is NULL. Assuming uniontype = LAST"); - utype = UT_LAST; - } - else { - utypename = text_to_cstring((text *) DatumGetPointer(tupv)); - utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); - } - - arg->bandarg[i].uniontype = utype; - arg->bandarg[i].nband = nband - 1; - arg->bandarg[i].raster = NULL; - - if ( - utype != UT_MEAN && - utype != UT_RANGE - ) { - arg->bandarg[i].numraster = 1; - } - else - arg->bandarg[i].numraster = 2; - } - - if (arg->numband < n) { - arg->bandarg = repalloc(arg->bandarg, sizeof(struct rtpg_union_band_arg_t) * arg->numband); - if (arg->bandarg == NULL) { - elog(ERROR, "rtpg_union_unionarg_process: Could not reallocate memory for band information"); - return 0; - } - } - - return 1; -} - -/* called for ST_Union(raster) */ -static int rtpg_union_noarg(rtpg_union_arg arg, rt_raster raster) { - int numbands; - int i; - - if (rt_raster_is_empty(raster)) - return 1; - - numbands = rt_raster_get_num_bands(raster); - if (numbands <= arg->numband) - return 1; - - /* more bands to process */ - POSTGIS_RT_DEBUG(4, "input raster has more bands, adding more bandargs"); - if (arg->numband) - arg->bandarg = repalloc(arg->bandarg, sizeof(struct rtpg_union_band_arg_t) * numbands); - else - arg->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * numbands); - if (arg->bandarg == NULL) { - elog(ERROR, "rtpg_union_noarg: Could not reallocate memory for band information"); - return 0; - } - - i = arg->numband; - arg->numband = numbands; - for (; i < arg->numband; i++) { - POSTGIS_RT_DEBUGF(4, "Adding bandarg for band at index %d", i); - arg->bandarg[i].uniontype = UT_LAST; - arg->bandarg[i].nband = i; - arg->bandarg[i].numraster = 1; - - arg->bandarg[i].raster = (rt_raster *) palloc(sizeof(rt_raster) * arg->bandarg[i].numraster); - if (arg->bandarg[i].raster == NULL) { - elog(ERROR, "rtpg_union_noarg: Could not allocate memory for working rasters"); - return 0; - } - memset(arg->bandarg[i].raster, 0, sizeof(rt_raster) * arg->bandarg[i].numraster); - - /* add new working rt_raster but only if working raster already exists */ - if (!rt_raster_is_empty(arg->bandarg[0].raster[0])) { - arg->bandarg[i].raster[0] = rt_raster_clone(arg->bandarg[0].raster[0], 0); /* shallow clone */ - if (arg->bandarg[i].raster[0] == NULL) { - elog(ERROR, "rtpg_union_noarg: Could not create working raster"); - return 0; - } - } - } - - return 1; -} - -/* UNION aggregate transition function */ -PG_FUNCTION_INFO_V1(RASTER_union_transfn); -Datum RASTER_union_transfn(PG_FUNCTION_ARGS) -{ - MemoryContext aggcontext; - MemoryContext oldcontext; - rtpg_union_arg iwr = NULL; - int skiparg = 0; - - rt_pgraster *pgraster = NULL; - rt_raster raster = NULL; - rt_raster _raster = NULL; - rt_band _band = NULL; - int nband = 1; - int noerr = 1; - int isempty[2] = {0}; - int hasband[2] = {0}; - int nargs = 0; - double _offset[4] = {0.}; - int nbnodata = 0; /* 1 if adding bands */ - - int i = 0; - int j = 0; - int k = 0; - - rt_iterator itrset; - char *utypename = NULL; - rtpg_union_type utype = UT_LAST; - rt_pixtype pixtype = PT_END; - int hasnodata = 1; - double nodataval = 0; - - rt_raster iraster = NULL; - rt_band iband = NULL; - int reuserast = 0; - int y = 0; - uint16_t _dim[2] = {0}; - void *vals = NULL; - uint16_t nvals = 0; - - POSTGIS_RT_DEBUG(3, "Starting..."); - - /* cannot be called directly as this is exclusive aggregate function */ - if (!AggCheckCallContext(fcinfo, &aggcontext)) { - elog(ERROR, "RASTER_union_transfn: Cannot be called in a non-aggregate context"); - PG_RETURN_NULL(); - } - - /* switch to aggcontext */ - oldcontext = MemoryContextSwitchTo(aggcontext); - - if (PG_ARGISNULL(0)) { - POSTGIS_RT_DEBUG(3, "Creating state variable"); - /* allocate container in aggcontext */ - iwr = palloc(sizeof(struct rtpg_union_arg_t)); - if (iwr == NULL) { - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not allocate memory for state variable"); - PG_RETURN_NULL(); - } - - iwr->numband = 0; - iwr->bandarg = NULL; - - skiparg = 0; - } - else { - POSTGIS_RT_DEBUG(3, "State variable already exists"); - iwr = (rtpg_union_arg) PG_GETARG_POINTER(0); - skiparg = 1; - } - - /* raster arg is NOT NULL */ - if (!PG_ARGISNULL(1)) { - /* deserialize raster */ - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - - /* Get raster object */ - raster = rt_raster_deserialize(pgraster, FALSE); - if (raster == NULL) { - - rtpg_union_arg_destroy(iwr); - PG_FREE_IF_COPY(pgraster, 1); - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not deserialize raster"); - PG_RETURN_NULL(); - } - } - - /* process additional args if needed */ - nargs = PG_NARGS(); - POSTGIS_RT_DEBUGF(4, "nargs = %d", nargs); - if (nargs > 2) { - POSTGIS_RT_DEBUG(4, "processing additional arguments"); - - /* if more than 2 arguments, determine the type of argument 3 */ - /* band number, UNION type or unionarg */ - if (!PG_ARGISNULL(2)) { - Oid calltype = get_fn_expr_argtype(fcinfo->flinfo, 2); - - switch (calltype) { - /* UNION type */ - case TEXTOID: { - int idx = 0; - int numband = 0; - - POSTGIS_RT_DEBUG(4, "Processing arg 3 as UNION type"); - nbnodata = 1; - - utypename = text_to_cstring(PG_GETARG_TEXT_P(2)); - utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); - POSTGIS_RT_DEBUGF(4, "Union type: %s", utypename); - - POSTGIS_RT_DEBUGF(4, "iwr->numband: %d", iwr->numband); - /* see if we need to append new bands */ - if (raster) { - idx = iwr->numband; - numband = rt_raster_get_num_bands(raster); - POSTGIS_RT_DEBUGF(4, "numband: %d", numband); - - /* only worry about appended bands */ - if (numband > iwr->numband) - iwr->numband = numband; - } - - if (!iwr->numband) - iwr->numband = 1; - POSTGIS_RT_DEBUGF(4, "iwr->numband: %d", iwr->numband); - POSTGIS_RT_DEBUGF(4, "numband, idx: %d, %d", numband, idx); - - /* bandarg set. only possible after the first call to function */ - if (iwr->bandarg) { - /* only reallocate if new bands need to be added */ - if (numband > idx) { - POSTGIS_RT_DEBUG(4, "Reallocating iwr->bandarg"); - iwr->bandarg = repalloc(iwr->bandarg, sizeof(struct rtpg_union_band_arg_t) * iwr->numband); - } - /* prevent initial values step happening below */ - else - idx = iwr->numband; - } - /* bandarg not set, first call to function */ - else { - POSTGIS_RT_DEBUG(4, "Allocating iwr->bandarg"); - iwr->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * iwr->numband); - } - if (iwr->bandarg == NULL) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not allocate memory for band information"); - PG_RETURN_NULL(); - } - - /* set initial values for bands that are "new" */ - for (i = idx; i < iwr->numband; i++) { - iwr->bandarg[i].uniontype = utype; - iwr->bandarg[i].nband = i; - - if ( - utype == UT_MEAN || - utype == UT_RANGE - ) { - iwr->bandarg[i].numraster = 2; - } - else - iwr->bandarg[i].numraster = 1; - iwr->bandarg[i].raster = NULL; - } - - break; - } - /* band number */ - case INT2OID: - case INT4OID: - if (skiparg) - break; - - POSTGIS_RT_DEBUG(4, "Processing arg 3 as band number"); - nband = PG_GETARG_INT32(2); - if (nband < 1) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Band number must be greater than zero (1-based)"); - PG_RETURN_NULL(); - } - - iwr->numband = 1; - iwr->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * iwr->numband); - if (iwr->bandarg == NULL) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not allocate memory for band information"); - PG_RETURN_NULL(); - } - - iwr->bandarg[0].uniontype = UT_LAST; - iwr->bandarg[0].nband = nband - 1; - - iwr->bandarg[0].numraster = 1; - iwr->bandarg[0].raster = NULL; - break; - /* only other type allowed is unionarg */ - default: - if (skiparg) - break; - - POSTGIS_RT_DEBUG(4, "Processing arg 3 as unionarg"); - if (!rtpg_union_unionarg_process(iwr, PG_GETARG_ARRAYTYPE_P(2))) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not process unionarg"); - PG_RETURN_NULL(); - } - - break; - } - } - - /* UNION type */ - if (nargs > 3 && !PG_ARGISNULL(3)) { - utypename = text_to_cstring(PG_GETARG_TEXT_P(3)); - utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); - iwr->bandarg[0].uniontype = utype; - POSTGIS_RT_DEBUGF(4, "Union type: %s", utypename); - - if ( - utype == UT_MEAN || - utype == UT_RANGE - ) { - iwr->bandarg[0].numraster = 2; - } - } - - /* allocate space for pointers to rt_raster */ - for (i = 0; i < iwr->numband; i++) { - POSTGIS_RT_DEBUGF(4, "iwr->bandarg[%d].raster @ %p", i, iwr->bandarg[i].raster); - - /* no need to allocate */ - if (iwr->bandarg[i].raster != NULL) - continue; - - POSTGIS_RT_DEBUGF(4, "Allocating space for working rasters of band %d", i); - - iwr->bandarg[i].raster = (rt_raster *) palloc(sizeof(rt_raster) * iwr->bandarg[i].numraster); - if (iwr->bandarg[i].raster == NULL) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not allocate memory for working raster(s)"); - PG_RETURN_NULL(); - } - - memset(iwr->bandarg[i].raster, 0, sizeof(rt_raster) * iwr->bandarg[i].numraster); - - /* add new working rt_raster but only if working raster already exists */ - if (i > 0 && !rt_raster_is_empty(iwr->bandarg[0].raster[0])) { - for (j = 0; j < iwr->bandarg[i].numraster; j++) { - iwr->bandarg[i].raster[j] = rt_raster_clone(iwr->bandarg[0].raster[0], 0); /* shallow clone */ - if (iwr->bandarg[i].raster[j] == NULL) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not create working raster"); - PG_RETURN_NULL(); - } - } - } - } - } - /* only raster, no additional args */ - /* only do this if raster isn't empty */ - else { - POSTGIS_RT_DEBUG(4, "no additional args, checking input raster"); - nbnodata = 1; - if (!rtpg_union_noarg(iwr, raster)) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not check and balance number of bands"); - PG_RETURN_NULL(); - } - } - - /* init itrset */ - itrset = palloc(sizeof(struct rt_iterator_t) * 2); - if (itrset == NULL) { - - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not allocate memory for iterator arguments"); - PG_RETURN_NULL(); - } - - /* by band to UNION */ - for (i = 0; i < iwr->numband; i++) { - - /* by raster */ - for (j = 0; j < iwr->bandarg[i].numraster; j++) { - reuserast = 0; - - /* type of union */ - utype = iwr->bandarg[i].uniontype; - - /* raster flags */ - isempty[0] = rt_raster_is_empty(iwr->bandarg[i].raster[j]); - isempty[1] = rt_raster_is_empty(raster); - - if (!isempty[0]) - hasband[0] = rt_raster_has_band(iwr->bandarg[i].raster[j], 0); - if (!isempty[1]) - hasband[1] = rt_raster_has_band(raster, iwr->bandarg[i].nband); - - /* determine pixtype, hasnodata and nodataval */ - _band = NULL; - if (!isempty[0] && hasband[0]) - _band = rt_raster_get_band(iwr->bandarg[i].raster[j], 0); - else if (!isempty[1] && hasband[1]) - _band = rt_raster_get_band(raster, iwr->bandarg[i].nband); - else { - pixtype = PT_64BF; - hasnodata = 1; - nodataval = rt_pixtype_get_min_value(pixtype); - } - if (_band != NULL) { - pixtype = rt_band_get_pixtype(_band); - hasnodata = 1; - if (rt_band_get_hasnodata_flag(_band)) - rt_band_get_nodata(_band, &nodataval); - else - nodataval = rt_band_get_min_value(_band); - } - - /* UT_MEAN and UT_RANGE require two passes */ - /* UT_MEAN: first for UT_COUNT and second for UT_SUM */ - if (iwr->bandarg[i].uniontype == UT_MEAN) { - /* first pass, UT_COUNT */ - if (j < 1) - utype = UT_COUNT; - else - utype = UT_SUM; - } - /* UT_RANGE: first for UT_MIN and second for UT_MAX */ - else if (iwr->bandarg[i].uniontype == UT_RANGE) { - /* first pass, UT_MIN */ - if (j < 1) - utype = UT_MIN; - else - utype = UT_MAX; - } - - /* force band settings for UT_COUNT */ - if (utype == UT_COUNT) { - pixtype = PT_32BUI; - hasnodata = 0; - nodataval = 0; - } - - POSTGIS_RT_DEBUGF(4, "(pixtype, hasnodata, nodataval) = (%s, %d, %f)", rt_pixtype_name(pixtype), hasnodata, nodataval); - - /* set itrset */ - itrset[0].raster = iwr->bandarg[i].raster[j]; - itrset[0].nband = 0; - itrset[1].raster = raster; - itrset[1].nband = iwr->bandarg[i].nband; - - /* allow use NODATA to replace missing bands */ - if (nbnodata) { - itrset[0].nbnodata = 1; - itrset[1].nbnodata = 1; - } - /* missing bands are not ignored */ - else { - itrset[0].nbnodata = 0; - itrset[1].nbnodata = 0; - } - - /* if rasters AND bands are present, use copy approach */ - if (!isempty[0] && !isempty[1] && hasband[0] && hasband[1]) { - POSTGIS_RT_DEBUG(3, "using line method"); - - /* generate empty out raster */ - if (rt_raster_from_two_rasters( - iwr->bandarg[i].raster[j], raster, - ET_UNION, - &iraster, _offset - ) != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not create internal raster"); - PG_RETURN_NULL(); - } - POSTGIS_RT_DEBUGF(4, "_offset = %f, %f, %f, %f", - _offset[0], _offset[1], _offset[2], _offset[3]); - - /* rasters are spatially the same? */ - if ( - rt_raster_get_width(iwr->bandarg[i].raster[j]) == rt_raster_get_width(iraster) && - rt_raster_get_height(iwr->bandarg[i].raster[j]) == rt_raster_get_height(iraster) - ) { - double igt[6] = {0}; - double gt[6] = {0}; - - rt_raster_get_geotransform_matrix(iwr->bandarg[i].raster[j], gt); - rt_raster_get_geotransform_matrix(iraster, igt); - - reuserast = rt_util_same_geotransform_matrix(gt, igt); - } - - /* use internal raster */ - if (!reuserast) { - /* create band of same type */ - if (rt_raster_generate_new_band( - iraster, - pixtype, - nodataval, - hasnodata, nodataval, - 0 - ) == -1) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - rt_raster_destroy(iraster); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not add new band to internal raster"); - PG_RETURN_NULL(); - } - iband = rt_raster_get_band(iraster, 0); - - /* copy working raster to output raster */ - _dim[0] = rt_raster_get_width(iwr->bandarg[i].raster[j]); - _dim[1] = rt_raster_get_height(iwr->bandarg[i].raster[j]); - for (y = 0; y < _dim[1]; y++) { - POSTGIS_RT_DEBUGF(4, "Getting pixel line of working raster at (x, y, length) = (0, %d, %d)", y, _dim[0]); - if (rt_band_get_pixel_line( - _band, - 0, y, - _dim[0], - &vals, &nvals - ) != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - rt_band_destroy(iband); - rt_raster_destroy(iraster); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not get pixel line from band of working raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(4, "Setting pixel line at (x, y, length) = (%d, %d, %d)", (int) _offset[0], (int) _offset[1] + y, nvals); - if (rt_band_set_pixel_line( - iband, - (int) _offset[0], (int) _offset[1] + y, - vals, nvals - ) != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - rt_band_destroy(iband); - rt_raster_destroy(iraster); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not set pixel line to band of internal raster"); - PG_RETURN_NULL(); - } - } - } - else { - rt_raster_destroy(iraster); - iraster = iwr->bandarg[i].raster[j]; - iband = rt_raster_get_band(iraster, 0); - } - - /* run iterator for extent of input raster */ - noerr = rt_raster_iterator( - itrset, 2, - ET_LAST, NULL, - pixtype, - hasnodata, nodataval, - 0, 0, - &utype, - rtpg_union_callback, - &_raster - ); - if (noerr != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (!reuserast) { - rt_band_destroy(iband); - rt_raster_destroy(iraster); - } - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - - /* with iterator raster, copy data to output raster */ - _band = rt_raster_get_band(_raster, 0); - _dim[0] = rt_raster_get_width(_raster); - _dim[1] = rt_raster_get_height(_raster); - for (y = 0; y < _dim[1]; y++) { - POSTGIS_RT_DEBUGF(4, "Getting pixel line of iterator raster at (x, y, length) = (0, %d, %d)", y, _dim[0]); - if (rt_band_get_pixel_line( - _band, - 0, y, - _dim[0], - &vals, &nvals - ) != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (!reuserast) { - rt_band_destroy(iband); - rt_raster_destroy(iraster); - } - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not get pixel line from band of working raster"); - PG_RETURN_NULL(); - } - - POSTGIS_RT_DEBUGF(4, "Setting pixel line at (x, y, length) = (%d, %d, %d)", (int) _offset[2], (int) _offset[3] + y, nvals); - if (rt_band_set_pixel_line( - iband, - (int) _offset[2], (int) _offset[3] + y, - vals, nvals - ) != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (!reuserast) { - rt_band_destroy(iband); - rt_raster_destroy(iraster); - } - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not set pixel line to band of internal raster"); - PG_RETURN_NULL(); - } - } - - /* free _raster */ - rt_band_destroy(_band); - rt_raster_destroy(_raster); - - /* replace working raster with output raster */ - _raster = iraster; - } - else { - POSTGIS_RT_DEBUG(3, "using pixel method"); - - /* pass everything to iterator */ - noerr = rt_raster_iterator( - itrset, 2, - ET_UNION, NULL, - pixtype, - hasnodata, nodataval, - 0, 0, - &utype, - rtpg_union_callback, - &_raster - ); - - if (noerr != ES_NONE) { - - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - MemoryContextSwitchTo(oldcontext); - elog(ERROR, "RASTER_union_transfn: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - } - - /* replace working raster */ - if (iwr->bandarg[i].raster[j] != NULL && !reuserast) { - for (k = rt_raster_get_num_bands(iwr->bandarg[i].raster[j]) - 1; k >= 0; k--) - rt_band_destroy(rt_raster_get_band(iwr->bandarg[i].raster[j], k)); - rt_raster_destroy(iwr->bandarg[i].raster[j]); - } - iwr->bandarg[i].raster[j] = _raster; - } - - } - - pfree(itrset); - if (raster != NULL) { - rt_raster_destroy(raster); - PG_FREE_IF_COPY(pgraster, 1); - } - - /* switch back to local context */ - MemoryContextSwitchTo(oldcontext); - - POSTGIS_RT_DEBUG(3, "Finished"); - - PG_RETURN_POINTER(iwr); -} - -/* UNION aggregate final function */ -PG_FUNCTION_INFO_V1(RASTER_union_finalfn); -Datum RASTER_union_finalfn(PG_FUNCTION_ARGS) -{ - rtpg_union_arg iwr; - rt_raster _rtn = NULL; - rt_raster _raster = NULL; - rt_pgraster *pgraster = NULL; - - int i = 0; - int j = 0; - rt_iterator itrset = NULL; - rt_band _band = NULL; - int noerr = 1; - int status = 0; - rt_pixtype pixtype = PT_END; - int hasnodata = 0; - double nodataval = 0; - - POSTGIS_RT_DEBUG(3, "Starting..."); - - /* cannot be called directly as this is exclusive aggregate function */ - if (!AggCheckCallContext(fcinfo, NULL)) { - elog(ERROR, "RASTER_union_finalfn: Cannot be called in a non-aggregate context"); - PG_RETURN_NULL(); - } - - /* NULL, return null */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); - - iwr = (rtpg_union_arg) PG_GETARG_POINTER(0); - - /* init itrset */ - itrset = palloc(sizeof(struct rt_iterator_t) * 2); - if (itrset == NULL) { - rtpg_union_arg_destroy(iwr); - elog(ERROR, "RASTER_union_finalfn: Could not allocate memory for iterator arguments"); - PG_RETURN_NULL(); - } - - for (i = 0; i < iwr->numband; i++) { - if ( - iwr->bandarg[i].uniontype == UT_MEAN || - iwr->bandarg[i].uniontype == UT_RANGE - ) { - /* raster containing the SUM or MAX is at index 1 */ - _band = rt_raster_get_band(iwr->bandarg[i].raster[1], 0); - - pixtype = rt_band_get_pixtype(_band); - hasnodata = rt_band_get_hasnodata_flag(_band); - if (hasnodata) - rt_band_get_nodata(_band, &nodataval); - POSTGIS_RT_DEBUGF(4, "(pixtype, hasnodata, nodataval) = (%s, %d, %f)", rt_pixtype_name(pixtype), hasnodata, nodataval); - - itrset[0].raster = iwr->bandarg[i].raster[0]; - itrset[0].nband = 0; - itrset[1].raster = iwr->bandarg[i].raster[1]; - itrset[1].nband = 0; - - /* pass everything to iterator */ - if (iwr->bandarg[i].uniontype == UT_MEAN) { - noerr = rt_raster_iterator( - itrset, 2, - ET_UNION, NULL, - pixtype, - hasnodata, nodataval, - 0, 0, - NULL, - rtpg_union_mean_callback, - &_raster - ); - } - else if (iwr->bandarg[i].uniontype == UT_RANGE) { - noerr = rt_raster_iterator( - itrset, 2, - ET_UNION, NULL, - pixtype, - hasnodata, nodataval, - 0, 0, - NULL, - rtpg_union_range_callback, - &_raster - ); - } - - if (noerr != ES_NONE) { - pfree(itrset); - rtpg_union_arg_destroy(iwr); - if (_rtn != NULL) - rt_raster_destroy(_rtn); - elog(ERROR, "RASTER_union_finalfn: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - } - else - _raster = iwr->bandarg[i].raster[0]; - - /* first band, _rtn doesn't exist */ - if (i < 1) { - uint32_t bandNums[1] = {0}; - _rtn = rt_raster_from_band(_raster, bandNums, 1); - status = (_rtn == NULL) ? -1 : 0; - } - else - status = rt_raster_copy_band(_rtn, _raster, 0, i); - - POSTGIS_RT_DEBUG(4, "destroying source rasters"); - - /* destroy source rasters */ - if ( - iwr->bandarg[i].uniontype == UT_MEAN || - iwr->bandarg[i].uniontype == UT_RANGE - ) { - rt_raster_destroy(_raster); - } - - for (j = 0; j < iwr->bandarg[i].numraster; j++) { - if (iwr->bandarg[i].raster[j] == NULL) - continue; - rt_raster_destroy(iwr->bandarg[i].raster[j]); - iwr->bandarg[i].raster[j] = NULL; - } - - if (status < 0) { - rtpg_union_arg_destroy(iwr); - rt_raster_destroy(_rtn); - elog(ERROR, "RASTER_union_finalfn: Could not add band to final raster"); - PG_RETURN_NULL(); - } - } - - /* cleanup */ - pfree(itrset); - rtpg_union_arg_destroy(iwr); - - pgraster = rt_raster_serialize(_rtn); - rt_raster_destroy(_rtn); - - POSTGIS_RT_DEBUG(3, "Finished"); - - if (!pgraster) - PG_RETURN_NULL(); - - SET_VARSIZE(pgraster, pgraster->size); - PG_RETURN_POINTER(pgraster); -} - -/* ---------------------------------------------------------------- */ -/* Clip raster with geometry */ -/* ---------------------------------------------------------------- */ - -typedef struct rtpg_clip_band_t *rtpg_clip_band; -struct rtpg_clip_band_t { - int nband; /* band index */ - int hasnodata; /* is there a user-specified NODATA? */ - double nodataval; /* user-specified NODATA */ -}; - -typedef struct rtpg_clip_arg_t *rtpg_clip_arg; -struct rtpg_clip_arg_t { - rt_extenttype extenttype; - rt_raster raster; - rt_raster mask; - - int numbands; /* number of bandargs */ - rtpg_clip_band band; -}; - -static rtpg_clip_arg rtpg_clip_arg_init() { - rtpg_clip_arg arg = NULL; - - arg = palloc(sizeof(struct rtpg_clip_arg_t)); - if (arg == NULL) { - elog(ERROR, "rtpg_clip_arg_init: Could not allocate memory for function arguments"); - return NULL; - } - - arg->extenttype = ET_INTERSECTION; - arg->raster = NULL; - arg->mask = NULL; - arg->numbands = 0; - arg->band = NULL; - - return arg; -} - -static void rtpg_clip_arg_destroy(rtpg_clip_arg arg) { - if (arg->band != NULL) - pfree(arg->band); - - if (arg->raster != NULL) - rt_raster_destroy(arg->raster); - if (arg->mask != NULL) - rt_raster_destroy(arg->mask); - - pfree(arg); -} - -static int rtpg_clip_callback( - rt_iterator_arg arg, void *userarg, - double *value, int *nodata -) { - *value = 0; - *nodata = 0; - - /* either is NODATA, output is NODATA */ - if (arg->nodata[0][0][0] || arg->nodata[1][0][0]) - *nodata = 1; - /* set to value */ - else - *value = arg->values[0][0][0]; - - return 1; -} - -PG_FUNCTION_INFO_V1(RASTER_clip); -Datum RASTER_clip(PG_FUNCTION_ARGS) -{ - rt_pgraster *pgraster = NULL; - LWGEOM *rastgeom = NULL; - double gt[6] = {0}; - int srid = SRID_UNKNOWN; - - rt_pgraster *pgrtn = NULL; - rt_raster rtn = NULL; - - GSERIALIZED *gser = NULL; - LWGEOM *geom = NULL; - unsigned char *wkb = NULL; - size_t wkb_len; - - ArrayType *array; - Oid etype; - Datum *e; - bool *nulls; - - int16 typlen; - bool typbyval; - char typalign; - - int i = 0; - int j = 0; - int k = 0; - rtpg_clip_arg arg = NULL; - LWGEOM *tmpgeom = NULL; - rt_iterator itrset; - - rt_raster _raster = NULL; - rt_band band = NULL; - rt_pixtype pixtype; - int hasnodata; - double nodataval; - int noerr = 0; - - POSTGIS_RT_DEBUG(3, "Starting..."); - - /* raster or geometry is NULL, return NULL */ - if (PG_ARGISNULL(0) || PG_ARGISNULL(2)) - PG_RETURN_NULL(); - - /* init arg */ - arg = rtpg_clip_arg_init(); - if (arg == NULL) { - elog(ERROR, "RASTER_clip: Could not initialize argument structure"); - PG_RETURN_NULL(); - } - - /* raster (0) */ - pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - - /* Get raster object */ - arg->raster = rt_raster_deserialize(pgraster, FALSE); - if (arg->raster == NULL) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not deserialize raster"); - PG_RETURN_NULL(); - } - - /* raster is empty, return empty raster */ - if (rt_raster_is_empty(arg->raster)) { - elog(NOTICE, "Input raster is empty. Returning empty raster"); - - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - - rtn = rt_raster_new(0, 0); - if (rtn == NULL) { - elog(ERROR, "RASTER_clip: Could not create empty raster"); - PG_RETURN_NULL(); - } - - pgrtn = rt_raster_serialize(rtn); - rt_raster_destroy(rtn); - if (NULL == pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* metadata */ - rt_raster_get_geotransform_matrix(arg->raster, gt); - srid = clamp_srid(rt_raster_get_srid(arg->raster)); - - /* geometry (2) */ - gser = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(2)); - geom = lwgeom_from_gserialized(gser); - - /* Get a 2D version of the geometry if necessary */ - if (lwgeom_ndims(geom) > 2) { - LWGEOM *geom2d = lwgeom_force_2d(geom); - lwgeom_free(geom); - geom = geom2d; - } - - /* check that SRIDs match */ - if (srid != clamp_srid(gserialized_get_srid(gser))) { - elog(NOTICE, "Geometry provided does not have the same SRID as the raster. Returning NULL"); - - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 2); - - PG_RETURN_NULL(); - } - - /* crop (4) */ - if (!PG_ARGISNULL(4) && !PG_GETARG_BOOL(4)) - arg->extenttype = ET_FIRST; - - /* get intersection geometry of input raster and input geometry */ - if (rt_raster_get_convex_hull(arg->raster, &rastgeom) != ES_NONE) { - - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 2); - - elog(ERROR, "RASTER_clip: Could not get convex hull of raster"); - PG_RETURN_NULL(); - } - - tmpgeom = lwgeom_intersection(rastgeom, geom); - lwgeom_free(rastgeom); - lwgeom_free(geom); - PG_FREE_IF_COPY(gser, 2); - geom = tmpgeom; - - /* intersection is empty AND extent type is INTERSECTION, return empty */ - if (lwgeom_is_empty(geom) && arg->extenttype == ET_INTERSECTION) { - elog(NOTICE, "The input raster and input geometry do not intersect. Returning empty raster"); - - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - - rtn = rt_raster_new(0, 0); - if (rtn == NULL) { - elog(ERROR, "RASTER_clip: Could not create empty raster"); - PG_RETURN_NULL(); - } - - pgrtn = rt_raster_serialize(rtn); - rt_raster_destroy(rtn); - if (NULL == pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); - } - - /* nband (1) */ - 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_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - elog(ERROR, "RASTER_clip: Invalid data type for band indexes"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array( - array, etype, - typlen, typbyval, typalign, - &e, &nulls, &(arg->numbands) - ); - - arg->band = palloc(sizeof(struct rtpg_clip_band_t) * arg->numbands); - if (arg->band == NULL) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - elog(ERROR, "RASTER_clip: Could not allocate memory for band arguments"); - PG_RETURN_NULL(); - } - - for (i = 0, j = 0; i < arg->numbands; i++) { - if (nulls[i]) continue; - - switch (etype) { - case INT2OID: - arg->band[j].nband = DatumGetInt16(e[i]) - 1; - break; - case INT4OID: - arg->band[j].nband = DatumGetInt32(e[i]) - 1; - break; - } - - j++; - } - - if (j < arg->numbands) { - arg->band = repalloc(arg->band, sizeof(struct rtpg_clip_band_t) * j); - if (arg->band == NULL) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - elog(ERROR, "RASTER_clip: Could not reallocate memory for band arguments"); - PG_RETURN_NULL(); - } - - arg->numbands = j; - } - - /* validate band */ - for (i = 0; i < arg->numbands; i++) { - if (!rt_raster_has_band(arg->raster, arg->band[i].nband)) { - elog(NOTICE, "Band at index %d not found in raster", arg->band[i].nband + 1); - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - PG_RETURN_NULL(); - } - - arg->band[i].hasnodata = 0; - arg->band[i].nodataval = 0; - } - } - else { - arg->numbands = rt_raster_get_num_bands(arg->raster); - - /* raster may have no bands */ - if (arg->numbands) { - arg->band = palloc(sizeof(struct rtpg_clip_band_t) * arg->numbands); - if (arg->band == NULL) { - - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - - elog(ERROR, "RASTER_clip: Could not allocate memory for band arguments"); - PG_RETURN_NULL(); - } - - for (i = 0; i < arg->numbands; i++) { - arg->band[i].nband = i; - arg->band[i].hasnodata = 0; - arg->band[i].nodataval = 0; - } - } - } - - /* nodataval (3) */ - if (!PG_ARGISNULL(3)) { - array = PG_GETARG_ARRAYTYPE_P(3); - etype = ARR_ELEMTYPE(array); - get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); - - switch (etype) { - case FLOAT4OID: - case FLOAT8OID: - break; - default: - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - lwgeom_free(geom); - elog(ERROR, "RASTER_clip: Invalid data type for NODATA values"); - PG_RETURN_NULL(); - break; - } - - deconstruct_array( - array, etype, - typlen, typbyval, typalign, - &e, &nulls, &k - ); - - /* it doesn't matter if there are more nodataval */ - for (i = 0, j = 0; i < arg->numbands; i++, j++) { - /* cap j to the last nodataval element */ - if (j >= k) - j = k - 1; - - if (nulls[j]) - continue; - - arg->band[i].hasnodata = 1; - switch (etype) { - case FLOAT4OID: - arg->band[i].nodataval = DatumGetFloat4(e[j]); - break; - case FLOAT8OID: - arg->band[i].nodataval = DatumGetFloat8(e[j]); - break; - } - } - } - - /* get wkb of geometry */ - POSTGIS_RT_DEBUG(3, "getting wkb of geometry"); - wkb = lwgeom_to_wkb(geom, WKB_SFSQL, &wkb_len); - lwgeom_free(geom); - - /* rasterize geometry */ - arg->mask = rt_raster_gdal_rasterize( - wkb, wkb_len, - 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 (arg->mask == NULL) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not rasterize intersection geometry"); - PG_RETURN_NULL(); - } - - /* set SRID */ - rt_raster_set_srid(arg->mask, srid); - - /* run iterator */ - - /* init itrset */ - itrset = palloc(sizeof(struct rt_iterator_t) * 2); - if (itrset == NULL) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not allocate memory for iterator arguments"); - PG_RETURN_NULL(); - } - - /* one band at a time */ - for (i = 0; i < arg->numbands; i++) { - POSTGIS_RT_DEBUGF(4, "band arg %d (nband, hasnodata, nodataval) = (%d, %d, %f)", - i, arg->band[i].nband, arg->band[i].hasnodata, arg->band[i].nodataval); - - band = rt_raster_get_band(arg->raster, arg->band[i].nband); - - /* band metadata */ - pixtype = rt_band_get_pixtype(band); - - if (arg->band[i].hasnodata) { - hasnodata = 1; - nodataval = arg->band[i].nodataval; - } - else if (rt_band_get_hasnodata_flag(band)) { - hasnodata = 1; - rt_band_get_nodata(band, &nodataval); - } - else { - hasnodata = 0; - nodataval = rt_band_get_min_value(band); - } - - /* band is NODATA, create NODATA band and continue */ - if (rt_band_get_isnodata_flag(band)) { - /* create raster */ - if (rtn == NULL) { - noerr = rt_raster_from_two_rasters(arg->raster, arg->mask, arg->extenttype, &rtn, NULL); - if (noerr != ES_NONE) { - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not create output raster"); - PG_RETURN_NULL(); - } - } - - /* create NODATA band */ - if (rt_raster_generate_new_band(rtn, pixtype, nodataval, hasnodata, nodataval, i) < 0) { - rt_raster_destroy(rtn); - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not add NODATA band to output raster"); - PG_RETURN_NULL(); - } - - continue; - } - - /* raster */ - itrset[0].raster = arg->raster; - itrset[0].nband = arg->band[i].nband; - itrset[0].nbnodata = 1; - - /* mask */ - itrset[1].raster = arg->mask; - itrset[1].nband = 0; - itrset[1].nbnodata = 1; - - /* pass to iterator */ - noerr = rt_raster_iterator( - itrset, 2, - arg->extenttype, NULL, - pixtype, - hasnodata, nodataval, - 0, 0, - NULL, - rtpg_clip_callback, - &_raster - ); - - if (noerr != ES_NONE) { - pfree(itrset); - rtpg_clip_arg_destroy(arg); - if (rtn != NULL) rt_raster_destroy(rtn); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not run raster iterator function"); - PG_RETURN_NULL(); - } - - /* new raster */ - if (rtn == NULL) - rtn = _raster; - /* copy band */ - else { - band = rt_raster_get_band(_raster, 0); - if (band == NULL) { - pfree(itrset); - rtpg_clip_arg_destroy(arg); - rt_raster_destroy(_raster); - rt_raster_destroy(rtn); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not get band from working raster"); - PG_RETURN_NULL(); - } - - if (rt_raster_add_band(rtn, band, i) < 0) { - pfree(itrset); - rtpg_clip_arg_destroy(arg); - rt_raster_destroy(_raster); - rt_raster_destroy(rtn); - PG_FREE_IF_COPY(pgraster, 0); - elog(ERROR, "RASTER_clip: Could not add new band to output raster"); - PG_RETURN_NULL(); - } - - rt_raster_destroy(_raster); - } - } - - pfree(itrset); - rtpg_clip_arg_destroy(arg); - PG_FREE_IF_COPY(pgraster, 0); - - pgrtn = rt_raster_serialize(rtn); - rt_raster_destroy(rtn); - - POSTGIS_RT_DEBUG(3, "Finished"); - - if (!pgrtn) - PG_RETURN_NULL(); - - SET_VARSIZE(pgrtn, pgrtn->size); - PG_RETURN_POINTER(pgrtn); -} - -/* ---------------------------------------------------------------- */ -/* Memory allocation / error reporting hooks */ -/* TODO: reuse the ones in libpgcommon ? */ -/* ---------------------------------------------------------------- */ - -static void * -rt_pg_alloc(size_t size) -{ - void * result; - - POSTGIS_RT_DEBUGF(5, "rt_pgalloc(%ld) called", (long int) size); - - result = palloc(size); - - return result; -} - -static void * -rt_pg_realloc(void *mem, size_t size) -{ - void * result; - - POSTGIS_RT_DEBUGF(5, "rt_pg_realloc(%ld) called", (long int) size); - - if (mem) - result = repalloc(mem, size); - - else - result = palloc(size); - - return result; -} - -static void -rt_pg_free(void *ptr) -{ - POSTGIS_RT_DEBUG(5, "rt_pfree called"); - pfree(ptr); -} - -static void -rt_pg_error(const char *fmt, va_list ap) -{ -#define ERRMSG_MAXLEN 256 - - char errmsg[ERRMSG_MAXLEN+1]; - - vsnprintf (errmsg, ERRMSG_MAXLEN, fmt, ap); - - errmsg[ERRMSG_MAXLEN]='\0'; - ereport(ERROR, (errmsg_internal("%s", errmsg))); -} - -static void -rt_pg_notice(const char *fmt, va_list ap) -{ - char *msg; - - /* - * This is a GNU extension. - * Dunno how to handle errors here. - */ - if (!lw_vasprintf (&msg, fmt, ap)) - { - va_end (ap); - return; - } - ereport(NOTICE, (errmsg_internal("%s", msg))); - free(msg); -} - - -void -rt_init_allocators(void) -{ - /* raster callback - install raster handlers */ - rt_set_handlers(rt_pg_alloc, rt_pg_realloc, rt_pg_free, rt_pg_error, - rt_pg_notice, rt_pg_notice); -} - diff --git a/raster/rt_pg/rtpg_band_properties.c b/raster/rt_pg/rtpg_band_properties.c new file mode 100644 index 000000000..9a282c4f3 --- /dev/null +++ b/raster/rt_pg/rtpg_band_properties.c @@ -0,0 +1,726 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ +#include "utils/array.h" /* for ArrayType */ +#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ + +#include "rtpostgis.h" + +/* Get all the properties of a raster band */ +Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS); +Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS); +Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS); +Datum RASTER_getBandPath(PG_FUNCTION_ARGS); +Datum RASTER_bandIsNoData(PG_FUNCTION_ARGS); + +/* get raster band's meta data */ +Datum RASTER_bandmetadata(PG_FUNCTION_ARGS); + +/* Set all the properties of a raster band */ +Datum RASTER_setBandIsNoData(PG_FUNCTION_ARGS); +Datum RASTER_setBandNoDataValue(PG_FUNCTION_ARGS); + +/** + * Return pixel type of the specified band of raster. + * Band index is 1-based. + */ +PG_FUNCTION_INFO_V1(RASTER_getBandPixelType); +Datum RASTER_getBandPixelType(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + rt_pixtype pixtype; + int32_t bandindex; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + raster = rt_raster_deserialize(pgraster, FALSE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getBandPixelType: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Fetch requested band and its pixel type */ + band = rt_raster_get_band(raster, bandindex - 1); + if ( ! band ) { + elog(NOTICE, "Could not find raster band of index %d when getting pixel type. Returning NULL", bandindex); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + pixtype = rt_band_get_pixtype(band); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_INT32(pixtype); +} + +/** + * Return name of pixel type of the specified band of raster. + * Band index is 1-based. + * NOTE: This is unofficial utility not included in the spec. + */ +PG_FUNCTION_INFO_V1(RASTER_getBandPixelTypeName); +Datum RASTER_getBandPixelTypeName(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + rt_pixtype pixtype; + int32_t bandindex; + const size_t name_size = 8; /* size of type name */ + size_t size = 0; + char *ptr = NULL; + text *result = NULL; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + raster = rt_raster_deserialize(pgraster, FALSE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getBandPixelTypeName: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Fetch requested band and its pixel type */ + band = rt_raster_get_band(raster, bandindex - 1); + if ( ! band ) { + elog(NOTICE, "Could not find raster band of index %d when getting pixel type name. Returning NULL", bandindex); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + pixtype = rt_band_get_pixtype(band); + + result = palloc(VARHDRSZ + name_size); + /* We don't need to check for NULL pointer, because if out of memory, palloc + * exit via elog(ERROR). It never returns NULL. + */ + + memset(VARDATA(result), 0, name_size); + ptr = (char *)result + VARHDRSZ; + strcpy(ptr, rt_pixtype_name(pixtype)); + + size = VARHDRSZ + strlen(ptr); + SET_VARSIZE(result, size); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_TEXT_P(result); +} + +/** + * Return nodata value of the specified band of raster. + * The value is always returned as FLOAT32 even if the pixel type is INTEGER. + */ +PG_FUNCTION_INFO_V1(RASTER_getBandNoDataValue); +Datum RASTER_getBandNoDataValue(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex; + double nodata; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(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_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + raster = rt_raster_deserialize(pgraster, FALSE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getBandNoDataValue: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Fetch requested band and its nodata value */ + band = rt_raster_get_band(raster, bandindex - 1); + if ( ! band ) { + elog(NOTICE, "Could not find raster band of index %d when getting band nodata value. Returning NULL", bandindex); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + if ( ! rt_band_get_hasnodata_flag(band) ) { + /* Raster does not have a nodata value set so we return NULL */ + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + rt_band_get_nodata(band, &nodata); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(nodata); +} + + +PG_FUNCTION_INFO_V1(RASTER_bandIsNoData); +Datum RASTER_bandIsNoData(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex; + bool forcechecking = FALSE; + bool bandisnodata = FALSE; + + /* 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(); + } + + /* 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_bandIsNoData: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Fetch requested band and its nodata value */ + band = rt_raster_get_band(raster, bandindex - 1); + if ( ! band ) { + elog(NOTICE, "Could not find raster band of index %d when determining if band is nodata. Returning NULL", bandindex); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + forcechecking = PG_GETARG_BOOL(2); + + bandisnodata = (forcechecking) ? + rt_band_check_is_nodata(band) : rt_band_get_isnodata_flag(band); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_BOOL(bandisnodata); +} + +/** + * Return the path of the raster for out-db raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getBandPath); +Datum RASTER_getBandPath(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex; + const char *bandpath; + text *result; + + /* 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(); + } + + /* 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_getBandPath: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Fetch requested band and its nodata value */ + band = rt_raster_get_band(raster, bandindex - 1); + if ( ! band ) { + elog(NOTICE, "Could not find raster band of index %d when getting band path. Returning NULL", bandindex); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + bandpath = rt_band_get_ext_path(band); + rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if ( ! bandpath ) + { + PG_RETURN_NULL(); + } + + result = (text *) palloc(VARHDRSZ + strlen(bandpath) + 1); + + SET_VARSIZE(result, VARHDRSZ + strlen(bandpath) + 1); + + strcpy((char *) VARDATA(result), bandpath); + + PG_RETURN_TEXT_P(result); +} + +/** + * Get raster bands' meta data + */ +PG_FUNCTION_INFO_V1(RASTER_bandmetadata); +Datum RASTER_bandmetadata(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + int call_cntr; + int max_calls; + + struct bandmetadata { + uint32_t bandnum; + char *pixeltype; + bool hasnodata; + double nodataval; + bool isoutdb; + char *bandpath; + }; + struct bandmetadata *bmd = NULL; + struct bandmetadata *bmd2 = NULL; + + HeapTuple tuple; + Datum result; + + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int i = 0; + int j = 0; + int n = 0; + + uint32_t numBands; + uint32_t idx = 1; + uint32_t *bandNums = NULL; + const char *tmp = NULL; + + POSTGIS_RT_DEBUG(3, "RASTER_bandmetadata: Starting"); + + /* 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); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) { + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_bandmetadata: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(raster); + if (numBands < 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 */ + array = PG_GETARG_ARRAYTYPE_P(1); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case INT2OID: + case INT4OID: + break; + default: + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_bandmetadata: Invalid data type for band number(s)"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + bandNums = palloc(sizeof(uint32_t) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case INT2OID: + idx = (uint32_t) DatumGetInt16(e[i]); + break; + case INT4OID: + idx = (uint32_t) DatumGetInt32(e[i]); + break; + } + + POSTGIS_RT_DEBUGF(3, "band idx (before): %d", idx); + if (idx > numBands || idx < 1) { + elog(NOTICE, "Invalid band index: %d. Indices must be 1-based. Returning NULL", idx); + pfree(bandNums); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + bandNums[j] = idx; + POSTGIS_RT_DEBUGF(3, "bandNums[%d] = %d", j, bandNums[j]); + j++; + } + + if (j < 1) { + j = numBands; + bandNums = repalloc(bandNums, sizeof(uint32_t) * j); + for (i = 0; i < j; i++) + bandNums[i] = i + 1; + } + else if (j < n) + bandNums = repalloc(bandNums, sizeof(uint32_t) * j); + + bmd = (struct bandmetadata *) palloc(sizeof(struct bandmetadata) * j); + + for (i = 0; i < j; i++) { + band = rt_raster_get_band(raster, bandNums[i] - 1); + if (NULL == band) { + elog(NOTICE, "Could not get raster band at index %d", bandNums[i]); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* bandnum */ + bmd[i].bandnum = bandNums[i]; + + /* pixeltype */ + tmp = rt_pixtype_name(rt_band_get_pixtype(band)); + bmd[i].pixeltype = palloc(sizeof(char) * (strlen(tmp) + 1)); + strncpy(bmd[i].pixeltype, tmp, strlen(tmp) + 1); + + /* hasnodatavalue */ + if (rt_band_get_hasnodata_flag(band)) + bmd[i].hasnodata = TRUE; + else + bmd[i].hasnodata = FALSE; + + /* nodatavalue */ + if (bmd[i].hasnodata) + rt_band_get_nodata(band, &(bmd[i].nodataval)); + else + bmd[i].nodataval = 0; + + /* path */ + tmp = rt_band_get_ext_path(band); + if (tmp) { + bmd[i].bandpath = palloc(sizeof(char) * (strlen(tmp) + 1)); + strncpy(bmd[i].bandpath, tmp, strlen(tmp) + 1); + } + else + bmd[i].bandpath = NULL; + + /* isoutdb */ + bmd[i].isoutdb = bmd[i].bandpath ? TRUE : FALSE; + + rt_band_destroy(band); + } + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Store needed information */ + funcctx->user_fctx = bmd; + + /* total number of tuples to be returned */ + funcctx->max_calls = j; + + /* 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; + bmd2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 5; + Datum values[values_length]; + bool nulls[values_length]; + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = UInt32GetDatum(bmd2[call_cntr].bandnum); + values[1] = CStringGetTextDatum(bmd2[call_cntr].pixeltype); + + if (bmd2[call_cntr].hasnodata) + values[2] = Float8GetDatum(bmd2[call_cntr].nodataval); + else + nulls[2] = TRUE; + + values[3] = BoolGetDatum(bmd2[call_cntr].isoutdb); + if (bmd2[call_cntr].bandpath && strlen(bmd2[call_cntr].bandpath)) + values[4] = CStringGetTextDatum(bmd2[call_cntr].bandpath); + else + nulls[4] = TRUE; + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + /* clean up */ + pfree(bmd2[call_cntr].pixeltype); + if (bmd2[call_cntr].bandpath) pfree(bmd2[call_cntr].bandpath); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(bmd2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Set the nodata value of the specified band of raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setBandNoDataValue); +Datum RASTER_setBandNoDataValue(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + double nodata; + int32_t bandindex; + bool forcechecking = FALSE; + bool skipset = FALSE; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* Check index is not NULL or smaller than 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). Nodata value not set. Returning original raster"); + skipset = TRUE; + } + + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_setBandNoDataValue: 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. Nodata value not set. Returning original raster", bandindex); + } + else { + if (!PG_ARGISNULL(3)) + forcechecking = PG_GETARG_BOOL(3); + + if (PG_ARGISNULL(2)) { + /* Set the hasnodata flag to FALSE */ + rt_band_set_hasnodata_flag(band, FALSE); + POSTGIS_RT_DEBUGF(3, "Raster band %d does not have a nodata value", bandindex); + } + else { + /* Get the nodata value */ + nodata = PG_GETARG_FLOAT8(2); + + /* Set the band's nodata value */ + rt_band_set_nodata(band, nodata, NULL); + + /* Recheck all pixels if requested */ + if (forcechecking) + rt_band_check_is_nodata(band); + } + } + } + + 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); +} + +PG_FUNCTION_INFO_V1(RASTER_setBandIsNoData); +Datum RASTER_setBandIsNoData(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex; + + 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_setBandIsNoData: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Check index is not NULL or smaller than 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). Isnodata flag not set. Returning original raster"); + else { + /* Fetch requested band */ + band = rt_raster_get_band(raster, bandindex - 1); + + if (!band) + elog(NOTICE, "Could not find raster band of index %d. Isnodata flag not set. Returning original raster", bandindex); + else { + if (!rt_band_get_hasnodata_flag(band)) { + elog(NOTICE, "Band of index %d has no NODATA so cannot be NODATA. Returning original raster", bandindex); + } + /* Set the band's nodata value */ + else { + rt_band_set_isnodata_flag(band, 1); + } + } + } + + /* Serialize raster again */ + 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); +} + diff --git a/raster/rt_pg/rtpg_create.c b/raster/rt_pg/rtpg_create.c new file mode 100644 index 000000000..b033b92d6 --- /dev/null +++ b/raster/rt_pg/rtpg_create.c @@ -0,0 +1,1610 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include /* for text_to_cstring() */ +#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ +#include "utils/array.h" /* for ArrayType */ +#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ + +#include "rtpostgis.h" + +/* Raster and band creation */ +Datum RASTER_makeEmpty(PG_FUNCTION_ARGS); +Datum RASTER_addBand(PG_FUNCTION_ARGS); +Datum RASTER_addBandRasterArray(PG_FUNCTION_ARGS); +Datum RASTER_addBandOutDB(PG_FUNCTION_ARGS); +Datum RASTER_copyBand(PG_FUNCTION_ARGS); +Datum RASTER_tile(PG_FUNCTION_ARGS); + +/* create new raster from existing raster's bands */ +Datum RASTER_band(PG_FUNCTION_ARGS); + +/** + * Make a new raster with no bands + */ +PG_FUNCTION_INFO_V1(RASTER_makeEmpty); +Datum RASTER_makeEmpty(PG_FUNCTION_ARGS) +{ + uint16 width = 0, height = 0; + double ipx = 0, ipy = 0, scalex = 0, scaley = 0, skewx = 0, skewy = 0; + int32_t srid = SRID_UNKNOWN; + rt_pgraster *pgraster = NULL; + rt_raster raster; + + if (PG_NARGS() < 9) { + elog(ERROR, "RASTER_makeEmpty: ST_MakeEmptyRaster requires 9 args"); + PG_RETURN_NULL(); + } + + if (!PG_ARGISNULL(0)) + width = PG_GETARG_UINT16(0); + + if (!PG_ARGISNULL(1)) + height = PG_GETARG_UINT16(1); + + if (!PG_ARGISNULL(2)) + ipx = PG_GETARG_FLOAT8(2); + + if (!PG_ARGISNULL(3)) + ipy = PG_GETARG_FLOAT8(3); + + if (!PG_ARGISNULL(4)) + scalex = PG_GETARG_FLOAT8(4); + + if (!PG_ARGISNULL(5)) + scaley = PG_GETARG_FLOAT8(5); + + if (!PG_ARGISNULL(6)) + skewx = PG_GETARG_FLOAT8(6); + + if (!PG_ARGISNULL(7)) + skewy = PG_GETARG_FLOAT8(7); + + if (!PG_ARGISNULL(8)) + srid = PG_GETARG_INT32(8); + + POSTGIS_RT_DEBUGF(4, "%dx%d, ip:%g,%g, scale:%g,%g, skew:%g,%g srid:%d", + width, height, ipx, ipy, scalex, scaley, + skewx, skewy, srid); + + raster = rt_raster_new(width, height); + if (raster == NULL) + PG_RETURN_NULL(); /* error was supposedly printed already */ + + rt_raster_set_scale(raster, scalex, scaley); + rt_raster_set_offsets(raster, ipx, ipy); + rt_raster_set_skews(raster, skewx, skewy); + rt_raster_set_srid(raster, srid); + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgraster) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); +} + +/** + * Add band(s) to the given raster at the given position(s). + */ +PG_FUNCTION_INFO_V1(RASTER_addBand); +Datum RASTER_addBand(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + int bandindex = 0; + int maxbandindex = 0; + int numbands = 0; + int lastnumbands = 0; + + text *text_pixtype = NULL; + char *char_pixtype = NULL; + + struct addbandarg { + int index; + bool append; + rt_pixtype pixtype; + double initialvalue; + bool hasnodata; + double nodatavalue; + }; + struct addbandarg *arg = NULL; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + + HeapTupleHeader tup; + bool isnull; + Datum tupv; + + int i = 0; + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* process set of addbandarg */ + POSTGIS_RT_DEBUG(3, "Processing Arg 1 (addbandargset)"); + array = PG_GETARG_ARRAYTYPE_P(1); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (!n) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset"); + PG_RETURN_NULL(); + } + + /* allocate addbandarg */ + arg = (struct addbandarg *) palloc(sizeof(struct addbandarg) * n); + if (arg == NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Could not allocate memory for addbandarg"); + PG_RETURN_NULL(); + } + + /* + process each element of addbandargset + each element is the index of where to add the new band, + new band's pixeltype, the new band's initial value and + the new band's NODATA value if NOT NULL + */ + for (i = 0; i < n; i++) { + if (nulls[i]) continue; + + POSTGIS_RT_DEBUGF(4, "Processing addbandarg at index %d", i); + + /* each element is a tuple */ + tup = (HeapTupleHeader) DatumGetPointer(e[i]); + if (NULL == tup) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset"); + PG_RETURN_NULL(); + } + + /* new band index, 1-based */ + arg[i].index = 0; + arg[i].append = TRUE; + tupv = GetAttributeByName(tup, "index", &isnull); + if (!isnull) { + arg[i].index = DatumGetInt32(tupv); + arg[i].append = FALSE; + } + + /* for now, only check that band index is 1-based */ + if (!arg[i].append && arg[i].index < 1) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Invalid band index (must be 1-based) for addbandarg of index %d", i); + PG_RETURN_NULL(); + } + + /* new band pixeltype */ + arg[i].pixtype = PT_END; + tupv = GetAttributeByName(tup, "pixeltype", &isnull); + if (isnull) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Pixel type cannot be NULL for addbandarg of index %d", i); + PG_RETURN_NULL(); + } + text_pixtype = (text *) DatumGetPointer(tupv); + if (text_pixtype == NULL) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Pixel type cannot be NULL for addbandarg of index %d", i); + PG_RETURN_NULL(); + } + char_pixtype = text_to_cstring(text_pixtype); + + arg[i].pixtype = rt_pixtype_index_from_name(char_pixtype); + pfree(char_pixtype); + if (arg[i].pixtype == PT_END) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Invalid argument for addbandargset. Invalid pixel type for addbandarg of index %d", i); + PG_RETURN_NULL(); + } + + /* new band initialvalue */ + arg[i].initialvalue = 0; + tupv = GetAttributeByName(tup, "initialvalue", &isnull); + if (!isnull) + arg[i].initialvalue = DatumGetFloat8(tupv); + + /* new band NODATA value */ + arg[i].hasnodata = FALSE; + arg[i].nodatavalue = 0; + tupv = GetAttributeByName(tup, "nodataval", &isnull); + if (!isnull) { + arg[i].hasnodata = TRUE; + arg[i].nodatavalue = DatumGetFloat8(tupv); + } + } + + /* add new bands to raster */ + lastnumbands = rt_raster_get_num_bands(raster); + for (i = 0; i < n; i++) { + if (nulls[i]) continue; + + POSTGIS_RT_DEBUGF(3, "%d bands in old raster", lastnumbands); + maxbandindex = lastnumbands + 1; + + /* check that new band's index doesn't exceed maxbandindex */ + if (!arg[i].append) { + if (arg[i].index > maxbandindex) { + elog(NOTICE, "Band index for addbandarg of index %d exceeds possible value. Adding band at index %d", i, maxbandindex); + arg[i].index = maxbandindex; + } + } + /* append, so use maxbandindex */ + else + arg[i].index = maxbandindex; + + POSTGIS_RT_DEBUGF(4, "new band (index, pixtype, initialvalue, hasnodata, nodatavalue) = (%d, %s, %f, %s, %f)", + arg[i].index, + rt_pixtype_name(arg[i].pixtype), + arg[i].initialvalue, + arg[i].hasnodata ? "TRUE" : "FALSE", + arg[i].nodatavalue + ); + + bandindex = rt_raster_generate_new_band( + raster, + arg[i].pixtype, arg[i].initialvalue, + arg[i].hasnodata, arg[i].nodatavalue, + arg[i].index - 1 + ); + + numbands = rt_raster_get_num_bands(raster); + if (numbands == lastnumbands || bandindex == -1) { + pfree(arg); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBand: Could not add band defined by addbandarg of index %d to raster", i); + PG_RETURN_NULL(); + } + + lastnumbands = numbands; + POSTGIS_RT_DEBUGF(3, "%d bands in new raster", lastnumbands); + } + + pfree(arg); + + 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); +} + +/** + * Add bands from array of rasters to a destination raster + */ +PG_FUNCTION_INFO_V1(RASTER_addBandRasterArray); +Datum RASTER_addBandRasterArray(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgsrc = NULL; + rt_pgraster *pgrtn = NULL; + + rt_raster raster = NULL; + rt_raster src = NULL; + + int srcnband = 1; + bool appendband = FALSE; + int dstnband = 1; + int srcnumbands = 0; + int dstnumbands = 0; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + + int rtn = 0; + int i = 0; + + /* destination raster */ + if (!PG_ARGISNULL(0)) { + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandRasterArray: Could not deserialize destination raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUG(4, "destination raster isn't NULL"); + } + + /* source rasters' band index, 1-based */ + if (!PG_ARGISNULL(2)) + srcnband = PG_GETARG_INT32(2); + if (srcnband < 1) { + elog(NOTICE, "Invalid band index for source rasters (must be 1-based). Returning original raster"); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + else + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(4, "srcnband = %d", srcnband); + + /* destination raster's band index, 1-based */ + if (!PG_ARGISNULL(3)) { + dstnband = PG_GETARG_INT32(3); + appendband = FALSE; + + if (dstnband < 1) { + elog(NOTICE, "Invalid band index for destination raster (must be 1-based). Returning original raster"); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + else + PG_RETURN_NULL(); + } + } + else + appendband = TRUE; + + /* additional processing of dstnband */ + if (raster != NULL) { + dstnumbands = rt_raster_get_num_bands(raster); + + if (dstnumbands < 1) { + appendband = TRUE; + dstnband = 1; + } + else if (appendband) + dstnband = dstnumbands + 1; + else if (dstnband > dstnumbands) { + elog(NOTICE, "Band index provided for destination raster is greater than the number of bands in the raster. Bands will be appended"); + appendband = TRUE; + dstnband = dstnumbands + 1; + } + } + POSTGIS_RT_DEBUGF(4, "appendband = %d", appendband); + POSTGIS_RT_DEBUGF(4, "dstnband = %d", dstnband); + + /* process set of source rasters */ + POSTGIS_RT_DEBUG(3, "Processing array of source rasters"); + array = PG_GETARG_ARRAYTYPE_P(1); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + /* decrement srcnband and dstnband by 1, now 0-based */ + srcnband--; + dstnband--; + POSTGIS_RT_DEBUGF(4, "0-based nband (src, dst) = (%d, %d)", srcnband, dstnband); + + /* time to copy bands */ + for (i = 0; i < n; i++) { + if (nulls[i]) continue; + src = NULL; + + pgsrc = (rt_pgraster *) PG_DETOAST_DATUM(e[i]); + src = rt_raster_deserialize(pgsrc, FALSE); + if (src == NULL) { + pfree(nulls); + pfree(e); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandRasterArray: Could not deserialize source raster at index %d", i + 1); + PG_RETURN_NULL(); + } + + srcnumbands = rt_raster_get_num_bands(src); + POSTGIS_RT_DEBUGF(4, "source raster %d has %d bands", i + 1, srcnumbands); + + /* band index isn't valid */ + if (srcnband > srcnumbands - 1) { + elog(NOTICE, "Invalid band index for source raster at index %d. Returning original raster", i + 1); + pfree(nulls); + pfree(e); + rt_raster_destroy(src); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + else + PG_RETURN_NULL(); + } + + /* destination raster is empty, new raster */ + if (raster == NULL) { + uint32_t srcnbands[1] = {srcnband}; + + POSTGIS_RT_DEBUG(4, "empty destination raster, using rt_raster_from_band"); + + raster = rt_raster_from_band(src, srcnbands, 1); + rt_raster_destroy(src); + if (raster == NULL) { + pfree(nulls); + pfree(e); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandRasterArray: Could not create raster from source raster at index %d", i + 1); + PG_RETURN_NULL(); + } + } + /* copy band */ + else { + rtn = rt_raster_copy_band( + raster, src, + srcnband, dstnband + ); + rt_raster_destroy(src); + + if (rtn == -1 || rt_raster_get_num_bands(raster) == dstnumbands) { + elog(NOTICE, "Could not add band from source raster at index %d to destination raster. Returning original raster", i + 1); + rt_raster_destroy(raster); + pfree(nulls); + pfree(e); + if (pgraster != NULL) + PG_RETURN_POINTER(pgraster); + else + PG_RETURN_NULL(); + } + } + + dstnband++; + dstnumbands++; + } + + if (raster != NULL) { + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + if (!pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + PG_RETURN_NULL(); +} + +/** + * Add out-db band to the given raster at the given position + */ +PG_FUNCTION_INFO_V1(RASTER_addBandOutDB); +Datum RASTER_addBandOutDB(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + + rt_raster raster = NULL; + rt_band band = NULL; + int numbands = 0; + int dstnband = 1; /* 1-based */ + int appendband = FALSE; + char *outdbfile = NULL; + int *srcnband = NULL; /* 1-based */ + int numsrcnband = 0; + int allbands = FALSE; + int hasnodata = FALSE; + double nodataval = 0.; + uint16_t width = 0; + uint16_t height = 0; + char *authname = NULL; + char *authcode = NULL; + + int i = 0; + int j = 0; + + GDALDatasetH hdsOut; + GDALRasterBandH hbandOut; + GDALDataType gdpixtype; + + rt_pixtype pt = PT_END; + double gt[6] = {0.}; + double ogt[6] = {0.}; + rt_raster _rast = NULL; + int aligned = 0; + int err = 0; + + /* destination raster */ + if (!PG_ARGISNULL(0)) { + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not deserialize destination raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUG(4, "destination raster isn't NULL"); + } + + /* destination band index (1) */ + if (!PG_ARGISNULL(1)) + dstnband = PG_GETARG_INT32(1); + else + appendband = TRUE; + + /* outdb file (2) */ + if (PG_ARGISNULL(2)) { + elog(NOTICE, "Out-db raster file not provided. Returning original raster"); + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + else + PG_RETURN_NULL(); + } + else { + outdbfile = text_to_cstring(PG_GETARG_TEXT_P(2)); + if (!strlen(outdbfile)) { + elog(NOTICE, "Out-db raster file not provided. Returning original raster"); + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + else + PG_RETURN_NULL(); + } + } + + /* outdb band index (3) */ + if (!PG_ARGISNULL(3)) { + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + + int16 typlen; + bool typbyval; + char typalign; + + allbands = FALSE; + + array = PG_GETARG_ARRAYTYPE_P(3); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case INT2OID: + case INT4OID: + break; + default: + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + } + elog(ERROR, "RASTER_addBandOutDB: Invalid data type for band indexes"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, &nulls, &numsrcnband); + + srcnband = palloc(sizeof(int) * numsrcnband); + if (srcnband == NULL) { + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + } + elog(ERROR, "RASTER_addBandOutDB: Could not allocate memory for band indexes"); + PG_RETURN_NULL(); + } + + for (i = 0, j = 0; i < numsrcnband; i++) { + if (nulls[i]) continue; + + switch (etype) { + case INT2OID: + srcnband[j] = DatumGetInt16(e[i]); + break; + case INT4OID: + srcnband[j] = DatumGetInt32(e[i]); + break; + } + j++; + } + + if (j < numsrcnband) { + srcnband = repalloc(srcnband, sizeof(int) * j); + if (srcnband == NULL) { + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + } + elog(ERROR, "RASTER_addBandOutDB: Could not reallocate memory for band indexes"); + PG_RETURN_NULL(); + } + + numsrcnband = j; + } + } + else + allbands = TRUE; + + /* nodataval (4) */ + if (!PG_ARGISNULL(4)) { + hasnodata = TRUE; + nodataval = PG_GETARG_FLOAT8(4); + } + else + hasnodata = FALSE; + + /* validate input */ + + /* make sure dstnband is valid */ + if (raster != NULL) { + numbands = rt_raster_get_num_bands(raster); + if (!appendband) { + if (dstnband < 1) { + elog(NOTICE, "Invalid band index %d for adding bands. Using band index 1", dstnband); + dstnband = 1; + } + else if (dstnband > numbands) { + elog(NOTICE, "Invalid band index %d for adding bands. Using band index %d", dstnband, numbands); + dstnband = numbands + 1; + } + } + else + dstnband = numbands + 1; + } + + /* open outdb raster file */ + rt_util_gdal_register_all(); + hdsOut = GDALOpenShared(outdbfile, GA_ReadOnly); + if (hdsOut == NULL) { + if (pgraster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + } + elog(ERROR, "RASTER_addBandOutDB: Could not open out-db file with GDAL"); + PG_RETURN_NULL(); + } + + /* get offline raster's geotransform */ + if (GDALGetGeoTransform(hdsOut, ogt) != CE_None) { + ogt[0] = 0; + ogt[1] = 1; + ogt[2] = 0; + ogt[3] = 0; + ogt[4] = 0; + ogt[5] = -1; + } + + /* raster doesn't exist, create it now */ + if (raster == NULL) { + raster = rt_raster_new(GDALGetRasterXSize(hdsOut), GDALGetRasterYSize(hdsOut)); + if (rt_raster_is_empty(raster)) { + elog(ERROR, "RASTER_addBandOutDB: Could not create new raster"); + PG_RETURN_NULL(); + } + rt_raster_set_geotransform_matrix(raster, ogt); + rt_raster_get_geotransform_matrix(raster, gt); + + if (rt_util_gdal_sr_auth_info(hdsOut, &authname, &authcode) == ES_NONE) { + if ( + authname != NULL && + strcmp(authname, "EPSG") == 0 && + authcode != NULL + ) { + rt_raster_set_srid(raster, atoi(authcode)); + } + else + elog(INFO, "Unknown SRS auth name and code from out-db file. Defaulting SRID of new raster to %d", SRID_UNKNOWN); + } + else + elog(INFO, "Could not get SRS auth name and code from out-db file. Defaulting SRID of new raster to %d", SRID_UNKNOWN); + } + + /* some raster info */ + width = rt_raster_get_width(raster); + height = rt_raster_get_width(raster); + + /* are rasters aligned? */ + _rast = rt_raster_new(1, 1); + rt_raster_set_geotransform_matrix(_rast, ogt); + rt_raster_set_srid(_rast, rt_raster_get_srid(raster)); + err = rt_raster_same_alignment(raster, _rast, &aligned, NULL); + rt_raster_destroy(_rast); + + if (err != ES_NONE) { + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not test alignment of out-db file"); + return ES_ERROR; + } + else if (!aligned) + elog(WARNING, "The in-db representation of the out-db raster is not aligned. Band data may be incorrect"); + + numbands = GDALGetRasterCount(hdsOut); + + /* build up srcnband */ + if (allbands) { + numsrcnband = numbands; + srcnband = palloc(sizeof(int) * numsrcnband); + if (srcnband == NULL) { + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not allocate memory for band indexes"); + PG_RETURN_NULL(); + } + + for (i = 0, j = 1; i < numsrcnband; i++, j++) + srcnband[i] = j; + } + + /* check band properties and add band */ + for (i = 0, j = dstnband - 1; i < numsrcnband; i++, j++) { + /* valid index? */ + if (srcnband[i] < 1 || srcnband[i] > numbands) { + elog(NOTICE, "Out-db file does not have a band at index %d. Returning original raster", srcnband[i]); + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_RETURN_POINTER(pgraster); + else + PG_RETURN_NULL(); + } + + /* get outdb band */ + hbandOut = NULL; + hbandOut = GDALGetRasterBand(hdsOut, srcnband[i]); + if (NULL == hbandOut) { + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not get band %d from GDAL dataset", srcnband[i]); + PG_RETURN_NULL(); + } + + /* supported pixel type */ + gdpixtype = GDALGetRasterDataType(hbandOut); + pt = rt_util_gdal_datatype_to_pixtype(gdpixtype); + if (pt == PT_END) { + elog(NOTICE, "Pixel type %s of band %d from GDAL dataset is not supported. Returning original raster", GDALGetDataTypeName(gdpixtype), srcnband[i]); + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_RETURN_POINTER(pgraster); + else + PG_RETURN_NULL(); + } + + /* use out-db band's nodata value if nodataval not already set */ + if (!hasnodata) + nodataval = GDALGetRasterNoDataValue(hbandOut, &hasnodata); + + /* add band */ + band = rt_band_new_offline( + width, height, + pt, + hasnodata, nodataval, + srcnband[i] - 1, outdbfile + ); + if (band == NULL) { + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not create new out-db band"); + PG_RETURN_NULL(); + } + + if (rt_raster_add_band(raster, band, j) < 0) { + GDALClose(hdsOut); + if (raster != NULL) + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_addBandOutDB: Could not add new out-db band to raster"); + PG_RETURN_NULL(); + } + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (pgraster != NULL) + PG_FREE_IF_COPY(pgraster, 0); + if (!pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/** + * Copy a band from one raster to another one at the given position. + */ +PG_FUNCTION_INFO_V1(RASTER_copyBand); +Datum RASTER_copyBand(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgto = NULL; + rt_pgraster *pgfrom = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster torast = NULL; + rt_raster fromrast = NULL; + int toindex = 0; + int fromband = 0; + int oldtorastnumbands = 0; + int newtorastnumbands = 0; + int newbandindex = 0; + + /* Deserialize torast */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgto = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + torast = rt_raster_deserialize(pgto, FALSE); + if (!torast) { + PG_FREE_IF_COPY(pgto, 0); + elog(ERROR, "RASTER_copyBand: Could not deserialize first raster"); + PG_RETURN_NULL(); + } + + /* Deserialize fromrast */ + if (!PG_ARGISNULL(1)) { + pgfrom = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + + fromrast = rt_raster_deserialize(pgfrom, FALSE); + if (!fromrast) { + rt_raster_destroy(torast); + PG_FREE_IF_COPY(pgfrom, 1); + PG_FREE_IF_COPY(pgto, 0); + elog(ERROR, "RASTER_copyBand: Could not deserialize second raster"); + PG_RETURN_NULL(); + } + + oldtorastnumbands = rt_raster_get_num_bands(torast); + + if (PG_ARGISNULL(2)) + fromband = 1; + else + fromband = PG_GETARG_INT32(2); + + if (PG_ARGISNULL(3)) + toindex = oldtorastnumbands + 1; + else + toindex = PG_GETARG_INT32(3); + + /* Copy band fromrast torast */ + newbandindex = rt_raster_copy_band( + torast, fromrast, + fromband - 1, toindex - 1 + ); + + newtorastnumbands = rt_raster_get_num_bands(torast); + if (newtorastnumbands == oldtorastnumbands || newbandindex == -1) { + elog(NOTICE, "RASTER_copyBand: Could not add band to raster. " + "Returning original raster." + ); + } + + rt_raster_destroy(fromrast); + PG_FREE_IF_COPY(pgfrom, 1); + } + + /* Serialize and return torast */ + pgrtn = rt_raster_serialize(torast); + rt_raster_destroy(torast); + PG_FREE_IF_COPY(pgto, 0); + if (!pgrtn) PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/** + * Break up a raster into smaller tiles. SRF function + */ +PG_FUNCTION_INFO_V1(RASTER_tile); +Datum RASTER_tile(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + int call_cntr; + int max_calls; + int i = 0; + int j = 0; + + struct tile_arg_t { + + struct { + rt_raster raster; + double gt[6]; + int srid; + int width; + int height; + } raster; + + struct { + int width; + int height; + + int nx; + int ny; + } tile; + + int numbands; + int *nbands; + + struct { + int pad; + double hasnodata; + double nodataval; + } pad; + }; + struct tile_arg_t *arg1 = NULL; + struct tile_arg_t *arg2 = NULL; + + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + rt_pgraster *pgraster = NULL; + int numbands; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + + int16 typlen; + bool typbyval; + char typalign; + + POSTGIS_RT_DEBUG(2, "RASTER_tile: 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); + } + + /* allocate arg1 */ + arg1 = palloc(sizeof(struct tile_arg_t)); + if (arg1 == NULL) { + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_tile: Could not allocate memory for arguments"); + SRF_RETURN_DONE(funcctx); + } + + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)); + arg1->raster.raster = rt_raster_deserialize(pgraster, FALSE); + if (!arg1->raster.raster) { + ereport(ERROR, ( + errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("Could not deserialize raster") + )); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* raster has bands */ + numbands = rt_raster_get_num_bands(arg1->raster.raster); + /* + if (!numbands) { + elog(NOTICE, "Raster provided has no bands"); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + */ + + /* width (1) */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Width cannot be NULL. Returning NULL"); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + arg1->tile.width = PG_GETARG_INT32(1); + if (arg1->tile.width < 1) { + elog(NOTICE, "Width must be greater than zero. Returning NULL"); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* height (2) */ + if (PG_ARGISNULL(2)) { + elog(NOTICE, "Height cannot be NULL. Returning NULL"); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + arg1->tile.height = PG_GETARG_INT32(2); + if (arg1->tile.height < 1) { + elog(NOTICE, "Height must be greater than zero. Returning NULL"); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* nband, array (3) */ + if (numbands && !PG_ARGISNULL(3)) { + array = PG_GETARG_ARRAYTYPE_P(3); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case INT2OID: + case INT4OID: + break; + default: + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_tile: 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) { + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_tile: 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) { + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_tile: 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(arg1->raster.raster, arg1->nbands[i])) { + elog(NOTICE, "Band at index %d not found in raster", arg1->nbands[i] + 1); + rt_raster_destroy(arg1->raster.raster); + pfree(arg1->nbands); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + } + } + else { + arg1->numbands = numbands; + + if (numbands) { + arg1->nbands = palloc(sizeof(int) * arg1->numbands); + + if (arg1->nbands == NULL) { + rt_raster_destroy(arg1->raster.raster); + pfree(arg1); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_dumpValues: Could not allocate memory for pixel values"); + 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); + } + } + } + + /* pad (4) and padnodata (5) */ + if (!PG_ARGISNULL(4)) { + arg1->pad.pad = PG_GETARG_BOOL(4) ? 1 : 0; + + if (arg1->pad.pad && !PG_ARGISNULL(5)) { + arg1->pad.hasnodata = 1; + arg1->pad.nodataval = PG_GETARG_FLOAT8(5); + } + else { + arg1->pad.hasnodata = 0; + arg1->pad.nodataval = 0; + } + } + else { + arg1->pad.pad = 0; + arg1->pad.hasnodata = 0; + arg1->pad.nodataval = 0; + } + + /* store some additional metadata */ + arg1->raster.srid = rt_raster_get_srid(arg1->raster.raster); + arg1->raster.width = rt_raster_get_width(arg1->raster.raster); + arg1->raster.height = rt_raster_get_height(arg1->raster.raster); + rt_raster_get_geotransform_matrix(arg1->raster.raster, arg1->raster.gt); + + /* determine maximum number of tiles from raster */ + arg1->tile.nx = ceil(arg1->raster.width / (double) arg1->tile.width); + arg1->tile.ny = ceil(arg1->raster.height / (double) arg1->tile.height); + POSTGIS_RT_DEBUGF(4, "# of tiles (x, y) = (%d, %d)", arg1->tile.nx, arg1->tile.ny); + + /* Store needed information */ + funcctx->user_fctx = arg1; + + /* total number of tuples to be returned */ + funcctx->max_calls = (arg1->tile.nx * arg1->tile.ny); + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + arg2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + rt_pgraster *pgtile = NULL; + rt_raster tile = NULL; + rt_band _band = NULL; + rt_band band = NULL; + rt_pixtype pixtype = PT_END; + int hasnodata = 0; + double nodataval = 0; + int width = 0; + int height = 0; + + int k = 0; + int tx = 0; + int ty = 0; + int rx = 0; + int ry = 0; + int ex = 0; /* edge tile on right */ + int ey = 0; /* edge tile on bottom */ + double ulx = 0; + double uly = 0; + uint16_t len = 0; + void *vals = NULL; + uint16_t nvals; + + POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); + + /* + find offset based upon tile # + + 0 1 2 + 3 4 5 + 6 7 8 + */ + ty = call_cntr / arg2->tile.nx; + tx = call_cntr % arg2->tile.nx; + POSTGIS_RT_DEBUGF(4, "tile (x, y) = (%d, %d)", tx, ty); + + /* edge tile? only important if padding is false */ + if (!arg2->pad.pad) { + if (ty + 1 == arg2->tile.ny) + ey = 1; + if (tx + 1 == arg2->tile.nx) + ex = 1; + } + + /* upper-left of tile in raster coordinates */ + rx = tx * arg2->tile.width; + ry = ty * arg2->tile.height; + POSTGIS_RT_DEBUGF(4, "raster coordinates = %d, %d", rx, ry); + + /* determine tile width and height */ + /* default to user-defined */ + width = arg2->tile.width; + height = arg2->tile.height; + + /* override user-defined if edge tile (only possible if padding is false */ + if (ex || ey) { + /* right edge */ + if (ex) + width = arg2->raster.width - rx; + /* bottom edge */ + if (ey) + height = arg2->raster.height - ry; + } + + /* create empty raster */ + tile = rt_raster_new(width, height); + rt_raster_set_geotransform_matrix(tile, arg2->raster.gt); + rt_raster_set_srid(tile, arg2->raster.srid); + + /* upper-left of tile in spatial coordinates */ + if (rt_raster_cell_to_geopoint(arg2->raster.raster, rx, ry, &ulx, &uly, arg2->raster.gt) != ES_NONE) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not compute the coordinates of the upper-left corner of the output tile"); + SRF_RETURN_DONE(funcctx); + } + rt_raster_set_offsets(tile, ulx, uly); + POSTGIS_RT_DEBUGF(4, "spatial coordinates = %f, %f", ulx, uly); + + /* compute length of pixel line to read */ + len = arg2->tile.width; + if (rx + arg2->tile.width >= arg2->raster.width) + len = arg2->raster.width - rx; + POSTGIS_RT_DEBUGF(3, "read line len = %d", len); + + /* copy bands to tile */ + for (i = 0; i < arg2->numbands; i++) { + POSTGIS_RT_DEBUGF(4, "copying band %d to tile %d", arg2->nbands[i], call_cntr); + + _band = rt_raster_get_band(arg2->raster.raster, arg2->nbands[i]); + if (_band == NULL) { + int nband = arg2->nbands[i] + 1; + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not get band %d from source raster", nband); + SRF_RETURN_DONE(funcctx); + } + + pixtype = rt_band_get_pixtype(_band); + hasnodata = rt_band_get_hasnodata_flag(_band); + if (hasnodata) + rt_band_get_nodata(_band, &nodataval); + else if (arg2->pad.pad && arg2->pad.hasnodata) { + hasnodata = 1; + nodataval = arg2->pad.nodataval; + } + else + nodataval = rt_band_get_min_value(_band); + + /* inline band */ + if (!rt_band_is_offline(_band)) { + if (rt_raster_generate_new_band(tile, pixtype, nodataval, hasnodata, nodataval, i) < 0) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not add new band to output tile"); + SRF_RETURN_DONE(funcctx); + } + band = rt_raster_get_band(tile, i); + if (band == NULL) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not get newly added band from output tile"); + SRF_RETURN_DONE(funcctx); + } + + /* if isnodata, set flag and continue */ + if (rt_band_get_isnodata_flag(_band)) { + rt_band_set_isnodata_flag(band, 1); + continue; + } + + /* copy data */ + for (j = 0; j < arg2->tile.height; j++) { + k = ry + j; + + if (k >= arg2->raster.height) { + POSTGIS_RT_DEBUGF(4, "row %d is beyond extent of source raster. skipping", k); + continue; + } + + POSTGIS_RT_DEBUGF(4, "getting pixel line %d, %d for %d pixels", rx, k, len); + if (rt_band_get_pixel_line(_band, rx, k, len, &vals, &nvals) != ES_NONE) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not get pixel line from source raster"); + SRF_RETURN_DONE(funcctx); + } + + if (nvals && rt_band_set_pixel_line(band, 0, j, vals, nvals) != ES_NONE) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not set pixel line of output tile"); + SRF_RETURN_DONE(funcctx); + } + } + } + /* offline */ + else { + uint8_t bandnum = 0; + rt_band_get_ext_band_num(_band, &bandnum); + + band = rt_band_new_offline( + width, height, + pixtype, + hasnodata, nodataval, + bandnum, rt_band_get_ext_path(_band) + ); + + if (band == NULL) { + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not create new offline band for output tile"); + SRF_RETURN_DONE(funcctx); + } + + if (rt_raster_add_band(tile, band, i) < 0) { + rt_band_destroy(band); + rt_raster_destroy(tile); + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + elog(ERROR, "RASTER_tile: Could not add new offline band to output tile"); + SRF_RETURN_DONE(funcctx); + } + } + } + + pgtile = rt_raster_serialize(tile); + rt_raster_destroy(tile); + if (!pgtile) { + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + SRF_RETURN_DONE(funcctx); + } + + SET_VARSIZE(pgtile, pgtile->size); + SRF_RETURN_NEXT(funcctx, PointerGetDatum(pgtile)); + } + /* do when there is no more left */ + else { + rt_raster_destroy(arg2->raster.raster); + if (arg2->numbands) pfree(arg2->nbands); + pfree(arg2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Return new raster from selected bands of existing raster through ST_Band. + * second argument is an array of band numbers (1 based) + */ +PG_FUNCTION_INFO_V1(RASTER_band); +Datum RASTER_band(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_pgraster *pgrast; + rt_raster raster; + rt_raster rast; + + bool skip = FALSE; + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + + uint32_t numBands; + uint32_t *bandNums; + uint32 idx = 0; + int n; + int i = 0; + int j = 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_band: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* process bandNums */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Band number(s) not provided. Returning original raster"); + skip = TRUE; + } + do { + if (skip) break; + + numBands = rt_raster_get_num_bands(raster); + + array = PG_GETARG_ARRAYTYPE_P(1); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case INT2OID: + case INT4OID: + break; + default: + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_band: Invalid data type for band number(s)"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + bandNums = palloc(sizeof(uint32_t) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case INT2OID: + idx = (uint32_t) DatumGetInt16(e[i]); + break; + case INT4OID: + idx = (uint32_t) DatumGetInt32(e[i]); + break; + } + + POSTGIS_RT_DEBUGF(3, "band idx (before): %d", idx); + if (idx > numBands || idx < 1) { + elog(NOTICE, "Invalid band index (must use 1-based). Returning original raster"); + skip = TRUE; + break; + } + + bandNums[j] = idx - 1; + POSTGIS_RT_DEBUGF(3, "bandNums[%d] = %d", j, bandNums[j]); + j++; + } + + if (skip || j < 1) { + pfree(bandNums); + skip = TRUE; + } + } + while (0); + + if (!skip) { + rast = rt_raster_from_band(raster, bandNums, j); + pfree(bandNums); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (!rast) { + elog(ERROR, "RASTER_band: Could not create new raster"); + PG_RETURN_NULL(); + } + + pgrast = rt_raster_serialize(rast); + rt_raster_destroy(rast); + + if (!pgrast) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrast, pgrast->size); + PG_RETURN_POINTER(pgrast); + } + + PG_RETURN_POINTER(pgraster); +} diff --git a/raster/rt_pg/rtpg_gdal.c b/raster/rt_pg/rtpg_gdal.c new file mode 100644 index 000000000..f964d50cb --- /dev/null +++ b/raster/rt_pg/rtpg_gdal.c @@ -0,0 +1,689 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include /* for SRF */ +#include /* for text_to_cstring() */ +#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ +#include "utils/array.h" /* for ArrayType */ +#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ + +#include "rtpostgis.h" +#include "rtpg_internal.h" + +/* convert GDAL raster to raster */ +Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS); + +/* convert raster to GDAL raster */ +Datum RASTER_asGDALRaster(PG_FUNCTION_ARGS); +Datum RASTER_getGDALDrivers(PG_FUNCTION_ARGS); + +/* warp a raster using GDAL Warp API */ +Datum RASTER_GDALWarp(PG_FUNCTION_ARGS); + +/* ---------------------------------------------------------------- */ +/* Returns raster from GDAL raster */ +/* ---------------------------------------------------------------- */ +PG_FUNCTION_INFO_V1(RASTER_fromGDALRaster); +Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS) +{ + bytea *bytea_data; + uint8_t *data; + int data_len = 0; + VSILFILE *vsifp = NULL; + GDALDatasetH hdsSrc; + int srid = -1; /* -1 for NULL */ + + rt_pgraster *pgraster = NULL; + rt_raster raster; + + /* NULL if NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* get data */ + bytea_data = (bytea *) PG_GETARG_BYTEA_P(0); + data = (uint8_t *) VARDATA(bytea_data); + data_len = VARSIZE(bytea_data) - VARHDRSZ; + + /* process srid */ + /* NULL srid means try to determine SRID from bytea */ + if (!PG_ARGISNULL(1)) + srid = clamp_srid(PG_GETARG_INT32(1)); + + /* create memory "file" */ + vsifp = VSIFileFromMemBuffer("/vsimem/in.dat", data, data_len, FALSE); + if (vsifp == NULL) { + PG_FREE_IF_COPY(bytea_data, 0); + elog(ERROR, "RASTER_fromGDALRaster: Could not load bytea into memory file for use by GDAL"); + PG_RETURN_NULL(); + } + + /* register all GDAL drivers */ + rt_util_gdal_register_all(); + + /* open GDAL raster */ + hdsSrc = GDALOpenShared("/vsimem/in.dat", GA_ReadOnly); + if (hdsSrc == NULL) { + VSIFCloseL(vsifp); + PG_FREE_IF_COPY(bytea_data, 0); + elog(ERROR, "RASTER_fromGDALRaster: Could not open bytea with GDAL. Check that the bytea is of a GDAL supported format"); + PG_RETURN_NULL(); + } + +#if POSTGIS_DEBUG_LEVEL > 3 + { + GDALDriverH hdrv = GDALGetDatasetDriver(hdsSrc); + + POSTGIS_RT_DEBUGF(4, "Input GDAL Raster info: %s, (%d x %d)", + GDALGetDriverShortName(hdrv), + GDALGetRasterXSize(hdsSrc), + GDALGetRasterYSize(hdsSrc) + ); + } +#endif + + /* convert GDAL raster to raster */ + raster = rt_raster_from_gdal_dataset(hdsSrc); + + GDALClose(hdsSrc); + VSIFCloseL(vsifp); + PG_FREE_IF_COPY(bytea_data, 0); + + if (raster == NULL) { + elog(ERROR, "RASTER_fromGDALRaster: Could not convert GDAL raster to raster"); + PG_RETURN_NULL(); + } + + /* apply SRID if set */ + if (srid != -1) + rt_raster_set_srid(raster, srid); + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgraster) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); +} + +/** + * Returns formatted GDAL raster as bytea object of raster + */ +PG_FUNCTION_INFO_V1(RASTER_asGDALRaster); +Datum RASTER_asGDALRaster(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster; + + text *formattext = NULL; + char *format = NULL; + char **options = NULL; + text *optiontext = NULL; + char *option = NULL; + int srid = SRID_UNKNOWN; + char *srs = NULL; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + int i = 0; + int j = 0; + + uint8_t *gdal = NULL; + uint64_t gdal_size = 0; + bytea *result = NULL; + uint64_t result_size = 0; + + POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Starting"); + + /* pgraster is null, return null */ + 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_asGDALRaster: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* format is required */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Format must be provided"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + else { + formattext = PG_GETARG_TEXT_P(1); + format = text_to_cstring(formattext); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: Arg 1 (format) is %s", format); + + /* process options */ + if (!PG_ARGISNULL(2)) { + POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Processing Arg 2 (options)"); + array = PG_GETARG_ARRAYTYPE_P(2); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case TEXTOID: + break; + default: + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_asGDALRaster: Invalid data type for options"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (n) { + options = (char **) palloc(sizeof(char *) * (n + 1)); + if (options == NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_asGDALRaster: Could not allocate memory for options"); + PG_RETURN_NULL(); + } + + /* clean each option */ + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + option = NULL; + switch (etype) { + case TEXTOID: + optiontext = (text *) DatumGetPointer(e[i]); + if (NULL == optiontext) break; + option = text_to_cstring(optiontext); + + /* trim string */ + option = rtpg_trim(option); + POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: option is '%s'", option); + break; + } + + if (strlen(option)) { + options[j] = (char *) palloc(sizeof(char) * (strlen(option) + 1)); + options[j] = option; + j++; + } + } + + if (j > 0) { + /* trim allocation */ + options = repalloc(options, (j + 1) * sizeof(char *)); + + /* add NULL to end */ + options[j] = NULL; + + } + else { + pfree(options); + options = NULL; + } + } + } + + /* process srid */ + /* NULL srid means use raster's srid */ + if (PG_ARGISNULL(3)) + srid = rt_raster_get_srid(raster); + else + srid = PG_GETARG_INT32(3); + + /* get srs from srid */ + if (clamp_srid(srid) != SRID_UNKNOWN) { + srs = rtpg_getSR(srid); + if (NULL == srs) { + if (NULL != options) { + for (i = j - 1; i >= 0; i--) pfree(options[i]); + pfree(options); + } + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_asGDALRaster: Could not find srtext for SRID (%d)", srid); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: Arg 3 (srs) is %s", srs); + } + else + srs = NULL; + + POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Generating GDAL raster"); + gdal = rt_raster_to_gdal(raster, srs, format, options, &gdal_size); + + /* free memory */ + if (NULL != options) { + for (i = j - 1; i >= 0; i--) pfree(options[i]); + pfree(options); + } + if (NULL != srs) pfree(srs); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + if (!gdal) { + elog(ERROR, "RASTER_asGDALRaster: Could not allocate and generate GDAL raster"); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(3, "RASTER_asGDALRaster: GDAL raster generated with %d bytes", (int) gdal_size); + + /* result is a varlena */ + result_size = gdal_size + VARHDRSZ; + result = (bytea *) palloc(result_size); + if (NULL == result) { + elog(ERROR, "RASTER_asGDALRaster: Insufficient virtual memory for GDAL raster"); + PG_RETURN_NULL(); + } + SET_VARSIZE(result, result_size); + memcpy(VARDATA(result), gdal, VARSIZE(result) - VARHDRSZ); + + /* for test output + FILE *fh = NULL; + fh = fopen("/tmp/out.dat", "w"); + fwrite(gdal, sizeof(uint8_t), gdal_size, fh); + fclose(fh); + */ + + /* free gdal mem buffer */ + if (gdal) CPLFree(gdal); + + POSTGIS_RT_DEBUG(3, "RASTER_asGDALRaster: Returning pointer to GDAL raster"); + PG_RETURN_POINTER(result); +} + +/** + * Returns available GDAL drivers + */ +PG_FUNCTION_INFO_V1(RASTER_getGDALDrivers); +Datum RASTER_getGDALDrivers(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + + uint32_t drv_count; + rt_gdaldriver drv_set; + rt_gdaldriver drv_set2; + int call_cntr; + int max_calls; + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + /* 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); + + drv_set = rt_raster_gdal_drivers(&drv_count, 1); + if (NULL == drv_set || !drv_count) { + elog(NOTICE, "No GDAL drivers found"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d drivers returned", (int) drv_count); + + /* Store needed information */ + funcctx->user_fctx = drv_set; + + /* total number of tuples to be returned */ + funcctx->max_calls = drv_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; + drv_set2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 4; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Int32GetDatum(drv_set2[call_cntr].idx); + values[1] = CStringGetTextDatum(drv_set2[call_cntr].short_name); + values[2] = CStringGetTextDatum(drv_set2[call_cntr].long_name); + values[3] = CStringGetTextDatum(drv_set2[call_cntr].create_options); + + POSTGIS_RT_DEBUGF(4, "Result %d, Index %d", call_cntr, drv_set2[call_cntr].idx); + POSTGIS_RT_DEBUGF(4, "Result %d, Short Name %s", call_cntr, drv_set2[call_cntr].short_name); + POSTGIS_RT_DEBUGF(4, "Result %d, Full Name %s", call_cntr, drv_set2[call_cntr].long_name); + POSTGIS_RT_DEBUGF(5, "Result %d, Create Options %s", call_cntr, drv_set2[call_cntr].create_options); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + /* clean up */ + pfree(drv_set2[call_cntr].short_name); + pfree(drv_set2[call_cntr].long_name); + pfree(drv_set2[call_cntr].create_options); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(drv_set2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * warp a raster using GDAL Warp API + */ +PG_FUNCTION_INFO_V1(RASTER_GDALWarp); +Datum RASTER_GDALWarp(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrast = NULL; + rt_raster raster = NULL; + rt_raster rast = NULL; + + text *algtext = NULL; + char *algchar = NULL; + GDALResampleAlg alg = GRA_NearestNeighbour; + double max_err = 0.125; + + int src_srid = SRID_UNKNOWN; + char *src_srs = NULL; + int dst_srid = SRID_UNKNOWN; + char *dst_srs = NULL; + int no_srid = 0; + + double scale[2] = {0}; + double *scale_x = NULL; + double *scale_y = NULL; + + double gridw[2] = {0}; + double *grid_xw = NULL; + double *grid_yw = NULL; + + double skew[2] = {0}; + double *skew_x = NULL; + double *skew_y = NULL; + + int dim[2] = {0}; + int *dim_x = NULL; + int *dim_y = NULL; + + POSTGIS_RT_DEBUG(3, "RASTER_GDALWarp: Starting"); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_GDALWarp: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* resampling algorithm */ + if (!PG_ARGISNULL(1)) { + algtext = PG_GETARG_TEXT_P(1); + algchar = rtpg_trim(rtpg_strtoupper(text_to_cstring(algtext))); + alg = rt_util_gdal_resample_alg(algchar); + } + POSTGIS_RT_DEBUGF(4, "Resampling algorithm: %d", alg); + + /* max error */ + if (!PG_ARGISNULL(2)) { + max_err = PG_GETARG_FLOAT8(2); + if (max_err < 0.) max_err = 0.; + } + POSTGIS_RT_DEBUGF(4, "max_err: %f", max_err); + + /* source SRID */ + src_srid = clamp_srid(rt_raster_get_srid(raster)); + POSTGIS_RT_DEBUGF(4, "source SRID: %d", src_srid); + + /* target SRID */ + if (!PG_ARGISNULL(3)) { + dst_srid = clamp_srid(PG_GETARG_INT32(3)); + if (dst_srid == SRID_UNKNOWN) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_GDALWarp: %d is an invalid target SRID", dst_srid); + PG_RETURN_NULL(); + } + } + else + dst_srid = src_srid; + POSTGIS_RT_DEBUGF(4, "destination SRID: %d", dst_srid); + + /* target SRID != src SRID, error */ + if (src_srid == SRID_UNKNOWN && dst_srid != src_srid) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_GDALWarp: Input raster has unknown (%d) SRID", src_srid); + PG_RETURN_NULL(); + } + /* target SRID == src SRID, no reprojection */ + else if (dst_srid == src_srid) { + no_srid = 1; + } + + /* scale x */ + if (!PG_ARGISNULL(4)) { + scale[0] = PG_GETARG_FLOAT8(4); + if (FLT_NEQ(scale[0], 0)) scale_x = &scale[0]; + } + + /* scale y */ + if (!PG_ARGISNULL(5)) { + scale[1] = PG_GETARG_FLOAT8(5); + if (FLT_NEQ(scale[1], 0)) scale_y = &scale[1]; + } + + /* grid alignment x */ + if (!PG_ARGISNULL(6)) { + gridw[0] = PG_GETARG_FLOAT8(6); + grid_xw = &gridw[0]; + } + + /* grid alignment y */ + if (!PG_ARGISNULL(7)) { + gridw[1] = PG_GETARG_FLOAT8(7); + grid_yw = &gridw[1]; + } + + /* skew x */ + if (!PG_ARGISNULL(8)) { + skew[0] = PG_GETARG_FLOAT8(8); + if (FLT_NEQ(skew[0], 0)) skew_x = &skew[0]; + } + + /* skew y */ + if (!PG_ARGISNULL(9)) { + skew[1] = PG_GETARG_FLOAT8(9); + if (FLT_NEQ(skew[1], 0)) skew_y = &skew[1]; + } + + /* width */ + if (!PG_ARGISNULL(10)) { + dim[0] = PG_GETARG_INT32(10); + if (dim[0] < 0) dim[0] = 0; + if (dim[0] > 0) dim_x = &dim[0]; + } + + /* height */ + if (!PG_ARGISNULL(11)) { + dim[1] = PG_GETARG_INT32(11); + if (dim[1] < 0) dim[1] = 0; + if (dim[1] > 0) dim_y = &dim[1]; + } + + /* check that at least something is to be done */ + if ( + (dst_srid == SRID_UNKNOWN) && + (scale_x == NULL) && + (scale_y == NULL) && + (grid_xw == NULL) && + (grid_yw == NULL) && + (skew_x == NULL) && + (skew_y == NULL) && + (dim_x == NULL) && + (dim_y == NULL) + ) { + elog(NOTICE, "No resampling parameters provided. Returning original raster"); + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + /* both values of alignment must be provided if any one is provided */ + else if ( + (grid_xw != NULL && grid_yw == NULL) || + (grid_xw == NULL && grid_yw != NULL) + ) { + elog(NOTICE, "Values must be provided for both X and Y when specifying the alignment. Returning original raster"); + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + /* both values of scale must be provided if any one is provided */ + else if ( + (scale_x != NULL && scale_y == NULL) || + (scale_x == NULL && scale_y != NULL) + ) { + elog(NOTICE, "Values must be provided for both X and Y when specifying the scale. Returning original raster"); + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + /* scale and width/height provided */ + else if ( + (scale_x != NULL || scale_y != NULL) && + (dim_x != NULL || dim_y != NULL) + ) { + elog(NOTICE, "Scale X/Y and width/height are mutually exclusive. Only provide one. Returning original raster"); + rt_raster_destroy(raster); + PG_RETURN_POINTER(pgraster); + } + + /* get srses from srids */ + if (!no_srid) { + /* source srs */ + src_srs = rtpg_getSR(src_srid); + if (NULL == src_srs) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_GDALWarp: Input raster has unknown SRID (%d)", src_srid); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(4, "src srs: %s", src_srs); + + dst_srs = rtpg_getSR(dst_srid); + if (NULL == dst_srs) { + if (!no_srid) pfree(src_srs); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_GDALWarp: Target SRID (%d) is unknown", dst_srid); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(4, "dst srs: %s", dst_srs); + } + + rast = rt_raster_gdal_warp( + raster, + src_srs, dst_srs, + scale_x, scale_y, + dim_x, dim_y, + NULL, NULL, + grid_xw, grid_yw, + skew_x, skew_y, + alg, max_err); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (!no_srid) { + pfree(src_srs); + pfree(dst_srs); + } + if (!rast) { + elog(ERROR, "RASTER_band: Could not create transformed raster"); + PG_RETURN_NULL(); + } + + /* add target SRID */ + rt_raster_set_srid(rast, dst_srid); + + pgrast = rt_raster_serialize(rast); + rt_raster_destroy(rast); + + if (NULL == pgrast) PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "RASTER_GDALWarp: done"); + + SET_VARSIZE(pgrast, pgrast->size); + PG_RETURN_POINTER(pgrast); +} + diff --git a/raster/rt_pg/rtpg_geometry.c b/raster/rt_pg/rtpg_geometry.c new file mode 100644 index 000000000..a7a6d6167 --- /dev/null +++ b/raster/rt_pg/rtpg_geometry.c @@ -0,0 +1,1239 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include /* for get_typlenbyvalalign */ +#include /* for ArrayType */ +#include /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ +#include /* for text_to_cstring() */ + +#include "../../postgis_config.h" +#include "lwgeom_pg.h" + +#include "rtpostgis.h" +#include "rtpg_internal.h" + +Datum RASTER_convex_hull(PG_FUNCTION_ARGS); +Datum RASTER_dumpAsPolygons(PG_FUNCTION_ARGS); + +/* Get pixel geographical shape */ +Datum RASTER_getPixelPolygons(PG_FUNCTION_ARGS); + +/* Get raster band's polygon */ +Datum RASTER_getPolygon(PG_FUNCTION_ARGS); + +/* rasterize a geometry */ +Datum RASTER_asRaster(PG_FUNCTION_ARGS); + +/** + * Return the convex hull of this raster + */ +PG_FUNCTION_INFO_V1(RASTER_convex_hull); +Datum RASTER_convex_hull(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + LWGEOM *geom = NULL; + GSERIALIZED* gser = NULL; + size_t gser_size; + int err = ES_NONE; + + bool minhull = FALSE; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* # of args */ + if (PG_NARGS() > 1) + minhull = TRUE; + + if (!minhull) { + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + raster = rt_raster_deserialize(pgraster, TRUE); + } + else { + 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_convex_hull: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + if (!minhull) + err = rt_raster_get_convex_hull(raster, &geom); + else { + int nband = -1; + + /* get arg 1 */ + if (!PG_ARGISNULL(1)) { + nband = PG_GETARG_INT32(1); + if (!rt_raster_has_band(raster, nband - 1)) { + elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + nband = nband - 1; + } + + err = rt_raster_get_perimeter(raster, nband, &geom); + } + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + if (err != ES_NONE) { + elog(ERROR, "RASTER_convex_hull: Could not get raster's convex hull"); + PG_RETURN_NULL(); + } + else if (geom == NULL) { + elog(NOTICE, "Raster's convex hull is NULL"); + PG_RETURN_NULL(); + } + + gser = gserialized_from_lwgeom(geom, 0, &gser_size); + lwgeom_free(geom); + + SET_VARSIZE(gser, gser_size); + PG_RETURN_POINTER(gser); +} + +PG_FUNCTION_INFO_V1(RASTER_dumpAsPolygons); +Datum RASTER_dumpAsPolygons(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tupdesc; + rt_geomval geomval; + rt_geomval geomval2; + int call_cntr; + int max_calls; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + int numbands; + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + int nband; + bool exclude_nodata_value = TRUE; + int nElements; + + POSTGIS_RT_DEBUG(2, "RASTER_dumpAsPolygons 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); + } + + if (!PG_ARGISNULL(1)) + nband = PG_GETARG_UINT32(1); + else + nband = 1; /* By default, first band */ + + POSTGIS_RT_DEBUGF(3, "band %d", nband); + numbands = rt_raster_get_num_bands(raster); + + if (nband < 1 || nband > numbands) { + 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); + } + + if (!PG_ARGISNULL(2)) + exclude_nodata_value = PG_GETARG_BOOL(2); + + /* see if band is NODATA */ + if (rt_band_get_isnodata_flag(rt_raster_get_band(raster, nband - 1))) { + POSTGIS_RT_DEBUGF(3, "Band at index %d is NODATA. Returning NULL", nband); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* Polygonize raster */ + + /** + * Dump raster + */ + geomval = rt_raster_gdal_polygonize(raster, nband - 1, exclude_nodata_value, &nElements); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (NULL == geomval) { + ereport(ERROR, ( + errcode(ERRCODE_NO_DATA_FOUND), + errmsg("Could not polygonize raster") + )); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "raster dump, %d elements returned", nElements); + + /* Store needed information */ + funcctx->user_fctx = geomval; + + /* total number of tuples to be returned */ + funcctx->max_calls = nElements; + + /* 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; + geomval2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + GSERIALIZED *gser = NULL; + size_t gser_size = 0; + + POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* convert LWGEOM to GSERIALIZED */ + gser = gserialized_from_lwgeom(lwpoly_as_lwgeom(geomval2[call_cntr].geom), 0, &gser_size); + lwgeom_free(lwpoly_as_lwgeom(geomval2[call_cntr].geom)); + + values[0] = PointerGetDatum(gser); + values[1] = Float8GetDatum(geomval2[call_cntr].val); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(geomval2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Return the geographical shape of all pixels + */ +PG_FUNCTION_INFO_V1(RASTER_getPixelPolygons); +Datum RASTER_getPixelPolygons(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + rt_pixel pix = NULL; + rt_pixel pix2; + int call_cntr; + int max_calls; + int i = 0; + + /* 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 nband = 1; + int numbands; + bool noband = FALSE; + bool exclude_nodata_value = TRUE; + bool nocolumnx = FALSE; + bool norowy = FALSE; + int x = 0; + int y = 0; + int bounds[4] = {0}; + int pixcount = 0; + int isnodata = 0; + + LWPOLY *poly; + + /* 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)); + + /* band */ + if (PG_ARGISNULL(1)) + noband = TRUE; + else { + nband = PG_GETARG_INT32(1); + noband = FALSE; + } + + /* column */ + if (PG_ARGISNULL(2)) + nocolumnx = TRUE; + else { + bounds[0] = PG_GETARG_INT32(2); + bounds[1] = bounds[0]; + } + + /* row */ + if (PG_ARGISNULL(3)) + norowy = TRUE; + else { + bounds[2] = PG_GETARG_INT32(3); + bounds[3] = bounds[2]; + } + + /* exclude NODATA */ + if (!PG_ARGISNULL(4)) + exclude_nodata_value = PG_GETARG_BOOL(4); + + 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); + } + + /* raster empty, return NULL */ + if (rt_raster_is_empty(raster)) { + elog(NOTICE, "Raster is empty. Returning NULL"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* band specified, load band and info */ + if (!noband) { + do { + numbands = rt_raster_get_num_bands(raster); + POSTGIS_RT_DEBUGF(3, "band %d", nband); + POSTGIS_RT_DEBUGF(3, "# of bands %d", numbands); + + if (nband < 1 || nband > numbands) { + elog(NOTICE, "Invalid band index (must use 1-based). Returning pixel values will be NULL"); + noband = TRUE; + break; + } + + band = rt_raster_get_band(raster, nband - 1); + if (!band) { + elog(NOTICE, "Could not find band at index %d. Returning pixel values will be NULL", nband); + noband = TRUE; + break; + } + + if (!rt_band_get_hasnodata_flag(band)) + exclude_nodata_value = FALSE; + } + while (0); + } + + /* set bounds if columnx, rowy not set */ + if (nocolumnx) { + bounds[0] = 1; + bounds[1] = rt_raster_get_width(raster); + } + if (norowy) { + bounds[2] = 1; + bounds[3] = rt_raster_get_height(raster); + } + POSTGIS_RT_DEBUGF(3, "bounds (min x, max x, min y, max y) = (%d, %d, %d, %d)", + bounds[0], bounds[1], bounds[2], bounds[3]); + + /* rowy */ + pixcount = 0; + for (y = bounds[2]; y <= bounds[3]; y++) { + /* columnx */ + for (x = bounds[0]; x <= bounds[1]; x++) { + /* geometry */ + poly = rt_raster_pixel_as_polygon(raster, x - 1, y - 1); + if (!poly) { + for (i = 0; i < pixcount; i++) + lwgeom_free(pix[i].geom); + if (pixcount) pfree(pix); + + if (!noband) rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_getPixelPolygons: Could not get pixel polygon"); + SRF_RETURN_DONE(funcctx); + } + + if (!pixcount) + pix = palloc(sizeof(struct rt_pixel_t) * (pixcount + 1)); + else + pix = repalloc(pix, sizeof(struct rt_pixel_t) * (pixcount + 1)); + if (pix == NULL) { + + lwpoly_free(poly); + if (!noband) rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_getPixelPolygons: Could not allocate memory for storing pixel polygons"); + SRF_RETURN_DONE(funcctx); + } + pix[pixcount].geom = (LWGEOM *) poly; + /* + POSTGIS_RT_DEBUGF(4, "poly @ %p", poly); + POSTGIS_RT_DEBUGF(4, "geom @ %p", pix[pixcount].geom); + */ + + /* x, y */ + pix[pixcount].x = x; + pix[pixcount].y = y; + + /* value, NODATA flag */ + if (!noband) { + if (rt_band_get_pixel(band, x - 1, y - 1, &(pix[pixcount].value), &isnodata) != ES_NONE) { + + for (i = 0; i < pixcount; i++) + lwgeom_free(pix[i].geom); + if (pixcount) pfree(pix); + + if (!noband) rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_getPixelPolygons: Could not get pixel value"); + SRF_RETURN_DONE(funcctx); + } + + if (!exclude_nodata_value || !isnodata) { + pix[pixcount].nodata = 0; + } + else { + pix[pixcount].nodata = 1; + } + } + else { + pix[pixcount].nodata = 1; + } + + pixcount++; + } + } + + if (!noband) rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Store needed information */ + funcctx->user_fctx = pix; + + /* total number of tuples to be returned */ + funcctx->max_calls = pixcount; + + /* 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; + pix2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 4; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + GSERIALIZED *gser = NULL; + size_t gser_size = 0; + + POSTGIS_RT_DEBUGF(3, "call number %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* convert LWGEOM to GSERIALIZED */ + gser = gserialized_from_lwgeom(pix2[call_cntr].geom, 0, &gser_size); + lwgeom_free(pix2[call_cntr].geom); + + values[0] = PointerGetDatum(gser); + if (pix2[call_cntr].nodata) + nulls[1] = TRUE; + else + values[1] = Float8GetDatum(pix2[call_cntr].value); + values[2] = Int32GetDatum(pix2[call_cntr].x); + values[3] = Int32GetDatum(pix2[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); + } + /* do when there is no more left */ + else { + pfree(pix2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Get raster band's polygon + */ +PG_FUNCTION_INFO_V1(RASTER_getPolygon); +Datum RASTER_getPolygon(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + int num_bands = 0; + int nband = 1; + int err; + LWMPOLY *surface = NULL; + GSERIALIZED *rtn = NULL; + + /* 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_getPolygon: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* 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); + PG_RETURN_NULL(); + } + + /* 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); + PG_RETURN_NULL(); + } + + /* get band surface */ + err = rt_raster_surface(raster, nband - 1, &surface); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + if (err != ES_NONE) { + elog(ERROR, "RASTER_getPolygon: Could not get raster band's surface"); + PG_RETURN_NULL(); + } + else if (surface == NULL) { + elog(NOTICE, "Raster is empty or all pixels of band are NODATA. Returning NULL"); + PG_RETURN_NULL(); + } + + rtn = geometry_serialize(lwmpoly_as_lwgeom(surface)); + lwmpoly_free(surface); + + PG_RETURN_POINTER(rtn); +} + +/** + * Rasterize a geometry + */ +PG_FUNCTION_INFO_V1(RASTER_asRaster); +Datum RASTER_asRaster(PG_FUNCTION_ARGS) +{ + GSERIALIZED *gser = NULL; + + LWGEOM *geom = NULL; + rt_raster rast = NULL; + rt_pgraster *pgrast = NULL; + + unsigned char *wkb; + size_t wkb_len = 0; + unsigned char variant = WKB_SFSQL; + + double scale[2] = {0}; + double *scale_x = NULL; + double *scale_y = NULL; + + int dim[2] = {0}; + int *dim_x = NULL; + int *dim_y = NULL; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + int i = 0; + int j = 0; + int haserr = 0; + + text *pixeltypetext = NULL; + char *pixeltype = NULL; + rt_pixtype pixtype = PT_END; + rt_pixtype *pixtypes = NULL; + int pixtypes_len = 0; + + double *values = NULL; + int values_len = 0; + + uint8_t *hasnodatas = NULL; + double *nodatavals = NULL; + int nodatavals_len = 0; + + double ulw[2] = {0}; + double *ul_xw = NULL; + double *ul_yw = NULL; + + double gridw[2] = {0}; + double *grid_xw = NULL; + double *grid_yw = NULL; + + double skew[2] = {0}; + double *skew_x = NULL; + double *skew_y = NULL; + + char **options = NULL; + int options_len = 0; + + uint32_t num_bands = 0; + + int srid = SRID_UNKNOWN; + char *srs = NULL; + + POSTGIS_RT_DEBUG(3, "RASTER_asRaster: Starting"); + + /* based upon LWGEOM_asBinary function in postgis/lwgeom_ogc.c */ + + /* Get the geometry */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + gser = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + geom = lwgeom_from_gserialized(gser); + + /* Get a 2D version of the geometry if necessary */ + if (lwgeom_ndims(geom) > 2) { + LWGEOM *geom2d = lwgeom_force_2d(geom); + lwgeom_free(geom); + geom = geom2d; + } + + /* empty geometry, return empty raster */ + if (lwgeom_is_empty(geom)) { + POSTGIS_RT_DEBUG(3, "Input geometry is empty. Returning empty raster"); + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + rast = rt_raster_new(0, 0); + if (rast == NULL) + PG_RETURN_NULL(); + + pgrast = rt_raster_serialize(rast); + rt_raster_destroy(rast); + + if (NULL == pgrast) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrast, pgrast->size); + PG_RETURN_POINTER(pgrast); + } + + /* scale x */ + if (!PG_ARGISNULL(1)) { + scale[0] = PG_GETARG_FLOAT8(1); + if (FLT_NEQ(scale[0], 0)) scale_x = &scale[0]; + } + + /* scale y */ + if (!PG_ARGISNULL(2)) { + scale[1] = PG_GETARG_FLOAT8(2); + if (FLT_NEQ(scale[1], 0)) scale_y = &scale[1]; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: scale (x, y) = %f, %f", scale[0], scale[1]); + + /* width */ + if (!PG_ARGISNULL(3)) { + dim[0] = PG_GETARG_INT32(3); + if (dim[0] < 0) dim[0] = 0; + if (dim[0] != 0) dim_x = &dim[0]; + } + + /* height */ + if (!PG_ARGISNULL(4)) { + dim[1] = PG_GETARG_INT32(4); + if (dim[1] < 0) dim[1] = 0; + if (dim[1] != 0) dim_y = &dim[1]; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: dim (x, y) = %d, %d", dim[0], dim[1]); + + /* pixeltype */ + if (!PG_ARGISNULL(5)) { + array = PG_GETARG_ARRAYTYPE_P(5); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case TEXTOID: + break; + default: + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + elog(ERROR, "RASTER_asRaster: Invalid data type for pixeltype"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (n) { + pixtypes = (rt_pixtype *) palloc(sizeof(rt_pixtype) * n); + /* clean each pixeltype */ + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) { + pixtypes[j++] = PT_64BF; + continue; + } + + pixeltype = NULL; + switch (etype) { + case TEXTOID: + pixeltypetext = (text *) DatumGetPointer(e[i]); + if (NULL == pixeltypetext) break; + pixeltype = text_to_cstring(pixeltypetext); + + /* trim string */ + pixeltype = rtpg_trim(pixeltype); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixeltype is '%s'", pixeltype); + break; + } + + if (strlen(pixeltype)) { + pixtype = rt_pixtype_index_from_name(pixeltype); + if (pixtype == PT_END) { + + pfree(pixtypes); + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + elog(ERROR, "RASTER_asRaster: Invalid pixel type provided: %s", pixeltype); + PG_RETURN_NULL(); + } + + pixtypes[j] = pixtype; + j++; + } + } + + if (j > 0) { + /* trim allocation */ + pixtypes = repalloc(pixtypes, j * sizeof(rt_pixtype)); + pixtypes_len = j; + } + else { + pfree(pixtypes); + pixtypes = NULL; + pixtypes_len = 0; + } + } + } +#if POSTGIS_DEBUG_LEVEL > 0 + for (i = 0; i < pixtypes_len; i++) + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixtypes[%d] = %d", i, (int) pixtypes[i]); +#endif + + /* value */ + if (!PG_ARGISNULL(6)) { + array = PG_GETARG_ARRAYTYPE_P(6); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + + if (pixtypes_len) pfree(pixtypes); + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + elog(ERROR, "RASTER_asRaster: Invalid data type for value"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (n) { + values = (double *) palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) { + values[j++] = 1; + continue; + } + + switch (etype) { + case FLOAT4OID: + values[j] = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + values[j] = (double) DatumGetFloat8(e[i]); + break; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values[%d] = %f", j, values[j]); + + j++; + } + + if (j > 0) { + /* trim allocation */ + values = repalloc(values, j * sizeof(double)); + values_len = j; + } + else { + pfree(values); + values = NULL; + values_len = 0; + } + } + } +#if POSTGIS_DEBUG_LEVEL > 0 + for (i = 0; i < values_len; i++) + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values[%d] = %f", i, values[i]); +#endif + + /* nodataval */ + if (!PG_ARGISNULL(7)) { + array = PG_GETARG_ARRAYTYPE_P(7); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + + if (pixtypes_len) pfree(pixtypes); + if (values_len) pfree(values); + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + elog(ERROR, "RASTER_asRaster: Invalid data type for nodataval"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (n) { + nodatavals = (double *) palloc(sizeof(double) * n); + hasnodatas = (uint8_t *) palloc(sizeof(uint8_t) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) { + hasnodatas[j] = 0; + nodatavals[j] = 0; + j++; + continue; + } + + hasnodatas[j] = 1; + switch (etype) { + case FLOAT4OID: + nodatavals[j] = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + nodatavals[j] = (double) DatumGetFloat8(e[i]); + break; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: hasnodatas[%d] = %d", j, hasnodatas[j]); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals[%d] = %f", j, nodatavals[j]); + + j++; + } + + if (j > 0) { + /* trim allocation */ + nodatavals = repalloc(nodatavals, j * sizeof(double)); + hasnodatas = repalloc(hasnodatas, j * sizeof(uint8_t)); + nodatavals_len = j; + } + else { + pfree(nodatavals); + pfree(hasnodatas); + nodatavals = NULL; + hasnodatas = NULL; + nodatavals_len = 0; + } + } + } +#if POSTGIS_DEBUG_LEVEL > 0 + for (i = 0; i < nodatavals_len; i++) { + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: hasnodatas[%d] = %d", i, hasnodatas[i]); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals[%d] = %f", i, nodatavals[i]); + } +#endif + + /* upperleftx */ + if (!PG_ARGISNULL(8)) { + ulw[0] = PG_GETARG_FLOAT8(8); + ul_xw = &ulw[0]; + } + + /* upperlefty */ + if (!PG_ARGISNULL(9)) { + ulw[1] = PG_GETARG_FLOAT8(9); + ul_yw = &ulw[1]; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: upperleft (x, y) = %f, %f", ulw[0], ulw[1]); + + /* gridx */ + if (!PG_ARGISNULL(10)) { + gridw[0] = PG_GETARG_FLOAT8(10); + grid_xw = &gridw[0]; + } + + /* gridy */ + if (!PG_ARGISNULL(11)) { + gridw[1] = PG_GETARG_FLOAT8(11); + grid_yw = &gridw[1]; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: grid (x, y) = %f, %f", gridw[0], gridw[1]); + + /* check dependent variables */ + haserr = 0; + do { + /* only part of scale provided */ + if ( + (scale_x == NULL && scale_y != NULL) || + (scale_x != NULL && scale_y == NULL) + ) { + elog(NOTICE, "Values must be provided for both X and Y of scale if one is specified"); + haserr = 1; + break; + } + + /* only part of dimension provided */ + if ( + (dim_x == NULL && dim_y != NULL) || + (dim_x != NULL && dim_y == NULL) + ) { + elog(NOTICE, "Values must be provided for both width and height if one is specified"); + haserr = 1; + break; + } + + /* scale and dimension provided */ + if ( + (scale_x != NULL && scale_y != NULL) && + (dim_x != NULL && dim_y != NULL) + ) { + elog(NOTICE, "Values provided for X and Y of scale and width and height. Using the width and height"); + scale_x = NULL; + scale_y = NULL; + break; + } + + /* neither scale or dimension provided */ + if ( + (scale_x == NULL && scale_y == NULL) && + (dim_x == NULL && dim_y == NULL) + ) { + elog(NOTICE, "Values must be provided for X and Y of scale or width and height"); + haserr = 1; + break; + } + + /* only part of upper-left provided */ + if ( + (ul_xw == NULL && ul_yw != NULL) || + (ul_xw != NULL && ul_yw == NULL) + ) { + elog(NOTICE, "Values must be provided for both X and Y when specifying the upper-left corner"); + haserr = 1; + break; + } + + /* only part of alignment provided */ + if ( + (grid_xw == NULL && grid_yw != NULL) || + (grid_xw != NULL && grid_yw == NULL) + ) { + elog(NOTICE, "Values must be provided for both X and Y when specifying the alignment"); + haserr = 1; + break; + } + + /* upper-left and alignment provided */ + if ( + (ul_xw != NULL && ul_yw != NULL) && + (grid_xw != NULL && grid_yw != NULL) + ) { + elog(NOTICE, "Values provided for both X and Y of upper-left corner and alignment. Using the values of upper-left corner"); + grid_xw = NULL; + grid_yw = NULL; + break; + } + } + while (0); + + if (haserr) { + if (pixtypes_len) pfree(pixtypes); + if (values_len) pfree(values); + if (nodatavals_len) { + pfree(nodatavals); + pfree(hasnodatas); + } + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + PG_RETURN_NULL(); + } + + /* skewx */ + if (!PG_ARGISNULL(12)) { + skew[0] = PG_GETARG_FLOAT8(12); + if (FLT_NEQ(skew[0], 0)) skew_x = &skew[0]; + } + + /* skewy */ + if (!PG_ARGISNULL(13)) { + skew[1] = PG_GETARG_FLOAT8(13); + if (FLT_NEQ(skew[1], 0)) skew_y = &skew[1]; + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: skew (x, y) = %f, %f", skew[0], skew[1]); + + /* all touched */ + if (!PG_ARGISNULL(14) && PG_GETARG_BOOL(14) == TRUE) { + if (options_len < 1) { + options_len = 1; + options = (char **) palloc(sizeof(char *) * options_len); + } + else { + options_len++; + options = (char **) repalloc(options, sizeof(char *) * options_len); + } + + options[options_len - 1] = palloc(sizeof(char*) * (strlen("ALL_TOUCHED=TRUE") + 1)); + options[options_len - 1] = "ALL_TOUCHED=TRUE"; + } + + if (options_len) { + options_len++; + options = (char **) repalloc(options, sizeof(char *) * options_len); + options[options_len - 1] = NULL; + } + + /* get geometry's srid */ + srid = gserialized_get_srid(gser); + + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: srid = %d", srid); + if (clamp_srid(srid) != SRID_UNKNOWN) { + srs = rtpg_getSR(srid); + if (NULL == srs) { + + if (pixtypes_len) pfree(pixtypes); + if (values_len) pfree(values); + if (nodatavals_len) { + pfree(hasnodatas); + pfree(nodatavals); + } + if (options_len) pfree(options); + + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + elog(ERROR, "RASTER_asRaster: Could not find srtext for SRID (%d)", srid); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: srs is %s", srs); + } + else + srs = NULL; + + /* determine number of bands */ + /* MIN macro is from GDAL's cpl_port.h */ + num_bands = MIN(pixtypes_len, values_len); + num_bands = MIN(num_bands, nodatavals_len); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: pixtypes_len = %d", pixtypes_len); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: values_len = %d", values_len); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: nodatavals_len = %d", nodatavals_len); + POSTGIS_RT_DEBUGF(3, "RASTER_asRaster: num_bands = %d", num_bands); + + /* warn of imbalanced number of band elements */ + if (!( + (pixtypes_len == values_len) && + (values_len == nodatavals_len) + )) { + elog( + NOTICE, + "Imbalanced number of values provided for pixeltype (%d), value (%d) and nodataval (%d). Using the first %d values of each parameter", + pixtypes_len, + values_len, + nodatavals_len, + num_bands + ); + } + + /* get wkb of geometry */ + POSTGIS_RT_DEBUG(3, "RASTER_asRaster: getting wkb of geometry"); + wkb = lwgeom_to_wkb(geom, variant, &wkb_len); + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 0); + + /* rasterize geometry */ + POSTGIS_RT_DEBUG(3, "RASTER_asRaster: rasterizing geometry"); + /* use nodatavals for the init parameter */ + rast = rt_raster_gdal_rasterize(wkb, + (uint32_t) wkb_len, srs, + num_bands, pixtypes, + nodatavals, values, + nodatavals, hasnodatas, + dim_x, dim_y, + scale_x, scale_y, + ul_xw, ul_yw, + grid_xw, grid_yw, + skew_x, skew_y, + options + ); + + if (pixtypes_len) pfree(pixtypes); + if (values_len) pfree(values); + if (nodatavals_len) { + pfree(hasnodatas); + pfree(nodatavals); + } + if (options_len) pfree(options); + + if (!rast) { + elog(ERROR, "RASTER_asRaster: Could not rasterize geometry"); + PG_RETURN_NULL(); + } + + /* add target srid */ + rt_raster_set_srid(rast, srid); + + pgrast = rt_raster_serialize(rast); + rt_raster_destroy(rast); + + if (NULL == pgrast) PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "RASTER_asRaster: done"); + + SET_VARSIZE(pgrast, pgrast->size); + PG_RETURN_POINTER(pgrast); +} + diff --git a/raster/rt_pg/rtpg_inout.c b/raster/rt_pg/rtpg_inout.c new file mode 100644 index 000000000..ea25b13fa --- /dev/null +++ b/raster/rt_pg/rtpg_inout.c @@ -0,0 +1,203 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include "rtpostgis.h" + +Datum RASTER_in(PG_FUNCTION_ARGS); +Datum RASTER_out(PG_FUNCTION_ARGS); + +Datum RASTER_to_bytea(PG_FUNCTION_ARGS); +Datum RASTER_to_binary(PG_FUNCTION_ARGS); + +/** + * Input is a string with hex chars in it. + * Convert to binary and put in the result + */ +PG_FUNCTION_INFO_V1(RASTER_in); +Datum RASTER_in(PG_FUNCTION_ARGS) +{ + rt_raster raster; + char *hexwkb = PG_GETARG_CSTRING(0); + void *result = NULL; + + POSTGIS_RT_DEBUG(3, "Starting"); + + raster = rt_raster_from_hexwkb(hexwkb, strlen(hexwkb)); + if (raster == NULL) + PG_RETURN_NULL(); + + result = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (result == NULL) + PG_RETURN_NULL(); + + SET_VARSIZE(result, ((rt_pgraster*)result)->size); + PG_RETURN_POINTER(result); +} + +/** + * Given a RASTER structure, convert it to Hex and put it in a string + */ +PG_FUNCTION_INFO_V1(RASTER_out); +Datum RASTER_out(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + uint32_t hexwkbsize = 0; + char *hexwkb = NULL; + + POSTGIS_RT_DEBUG(3, "Starting"); + + 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_out: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + hexwkb = rt_raster_to_hexwkb(raster, FALSE, &hexwkbsize); + if (!hexwkb) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_out: Could not HEX-WKBize raster"); + PG_RETURN_NULL(); + } + + /* Free the raster objects used */ + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_CSTRING(hexwkb); +} + +/** + * Return bytea object with raster in Well-Known-Binary form. + */ +PG_FUNCTION_INFO_V1(RASTER_to_bytea); +Datum RASTER_to_bytea(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + uint8_t *wkb = NULL; + uint32_t wkb_size = 0; + bytea *result = NULL; + int result_size = 0; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* Get raster object */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_to_bytea: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* Parse raster to wkb object */ + wkb = rt_raster_to_wkb(raster, FALSE, &wkb_size); + if (!wkb) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_to_bytea: Could not allocate and generate WKB data"); + PG_RETURN_NULL(); + } + + /* Create varlena object */ + result_size = wkb_size + VARHDRSZ; + result = (bytea *)palloc(result_size); + SET_VARSIZE(result, result_size); + memcpy(VARDATA(result), wkb, VARSIZE(result) - VARHDRSZ); + + /* Free raster objects used */ + rt_raster_destroy(raster); + pfree(wkb); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_POINTER(result); +} + +/** + * Return bytea object with raster in Well-Known-Binary form requested using ST_AsBinary function. + */ +PG_FUNCTION_INFO_V1(RASTER_to_binary); +Datum RASTER_to_binary(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + uint8_t *wkb = NULL; + uint32_t wkb_size = 0; + char *result = NULL; + int result_size = 0; + int outasin = FALSE; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* Get raster object */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_to_binary: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + if (!PG_ARGISNULL(1)) + outasin = PG_GETARG_BOOL(1); + + /* Parse raster to wkb object */ + wkb = rt_raster_to_wkb(raster, outasin, &wkb_size); + if (!wkb) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_to_binary: Could not allocate and generate WKB data"); + PG_RETURN_NULL(); + } + + /* Create varlena object */ + result_size = wkb_size + VARHDRSZ; + result = (char *)palloc(result_size); + SET_VARSIZE(result, result_size); + memcpy(VARDATA(result), wkb, VARSIZE(result) - VARHDRSZ); + + /* Free raster objects used */ + rt_raster_destroy(raster); + pfree(wkb); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_POINTER(result); +} + diff --git a/raster/rt_pg/rtpg_internal.c b/raster/rt_pg/rtpg_internal.c new file mode 100644 index 000000000..17c7a5d1a --- /dev/null +++ b/raster/rt_pg/rtpg_internal.c @@ -0,0 +1,365 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include /* for isspace */ +#include /* for palloc */ +#include + +#include "rtpg_internal.h" + +/* string replacement function taken from + * http://ubuntuforums.org/showthread.php?s=aa6f015109fd7e4c7e30d2fd8b717497&t=141670&page=3 + */ +/* --------------------------------------------------------------------------- + Name : replace - Search & replace a substring by another one. + Creation : Thierry Husson, Sept 2010 + Parameters : + str : Big string where we search + oldstr : Substring we are looking for + newstr : Substring we want to replace with + count : Optional pointer to int (input / output value). NULL to ignore. + Input: Maximum replacements to be done. NULL or < 1 to do all. + Output: Number of replacements done or -1 if not enough memory. + Returns : Pointer to the new string or NULL if error. + Notes : + - Case sensitive - Otherwise, replace functions "strstr" by "strcasestr" + - Always allocates memory for the result. +--------------------------------------------------------------------------- */ +char* +rtpg_strreplace( + const char *str, + const char *oldstr, const char *newstr, + int *count +) { + const char *tmp = str; + char *result; + int found = 0; + int length, reslen; + int oldlen = strlen(oldstr); + int newlen = strlen(newstr); + int limit = (count != NULL && *count > 0) ? *count : -1; + + tmp = str; + while ((tmp = strstr(tmp, oldstr)) != NULL && found != limit) + found++, tmp += oldlen; + + length = strlen(str) + found * (newlen - oldlen); + if ((result = (char *) palloc(length + 1)) == NULL) { + fprintf(stderr, "Not enough memory\n"); + found = -1; + } + else { + tmp = str; + limit = found; /* Countdown */ + reslen = 0; /* length of current result */ + + /* Replace each old string found with new string */ + while ((limit-- > 0) && (tmp = strstr(tmp, oldstr)) != NULL) { + length = (tmp - str); /* Number of chars to keep intouched */ + strncpy(result + reslen, str, length); /* Original part keeped */ + strcpy(result + (reslen += length), newstr); /* Insert new string */ + + reslen += newlen; + tmp += oldlen; + str = tmp; + } + strcpy(result + reslen, str); /* Copies last part and ending null char */ + } + + if (count != NULL) *count = found; + return result; +} + +char * +rtpg_strtoupper(char * str) { + int j; + + for (j = strlen(str) - 1; j >= 0; j--) + str[j] = toupper(str[j]); + + return str; +} + +char* +rtpg_chartrim(const char *input, char *remove) { + char *rtn = NULL; + char *ptr = NULL; + uint32_t offset = 0; + + if (!input) + return NULL; + else if (!*input) + return (char *) input; + + /* trim left */ + while (strchr(remove, *input) != NULL) + input++; + + /* trim right */ + ptr = ((char *) input) + strlen(input); + while (strchr(remove, *--ptr) != NULL) + offset++; + + rtn = palloc(sizeof(char) * (strlen(input) - offset + 1)); + if (rtn == NULL) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + strncpy(rtn, input, strlen(input) - offset); + rtn[strlen(input) - offset] = '\0'; + + return rtn; +} + +/* split a string based on a delimiter */ +char** +rtpg_strsplit(const char *str, const char *delimiter, int *n) { + char *tmp = NULL; + char **rtn = NULL; + char *token = NULL; + + *n = 0; + if (!str) + return NULL; + + /* copy str to tmp as strtok will mangle the string */ + tmp = palloc(sizeof(char) * (strlen(str) + 1)); + if (NULL == tmp) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + strcpy(tmp, str); + + if (!strlen(tmp) || !delimiter || !strlen(delimiter)) { + *n = 1; + rtn = (char **) palloc(*n * sizeof(char *)); + if (NULL == rtn) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + rtn[0] = (char *) palloc(sizeof(char) * (strlen(tmp) + 1)); + if (NULL == rtn[0]) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + strcpy(rtn[0], tmp); + pfree(tmp); + return rtn; + } + + token = strtok(tmp, delimiter); + while (token != NULL) { + if (*n < 1) { + rtn = (char **) palloc(sizeof(char *)); + } + else { + rtn = (char **) repalloc(rtn, (*n + 1) * sizeof(char *)); + } + if (NULL == rtn) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + + rtn[*n] = NULL; + rtn[*n] = (char *) palloc(sizeof(char) * (strlen(token) + 1)); + if (NULL == rtn[*n]) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + + strcpy(rtn[*n], token); + *n = *n + 1; + + token = strtok(NULL, delimiter); + } + + pfree(tmp); + return rtn; +} + +char * +rtpg_removespaces(char *str) { + char *rtn; + char *tmp; + + rtn = rtpg_strreplace(str, " ", "", NULL); + + tmp = rtpg_strreplace(rtn, "\n", "", NULL); + pfree(rtn); + rtn = rtpg_strreplace(tmp, "\t", "", NULL); + pfree(tmp); + tmp = rtpg_strreplace(rtn, "\f", "", NULL); + pfree(rtn); + rtn = rtpg_strreplace(tmp, "\r", "", NULL); + pfree(tmp); + + return rtn; +} + +char* +rtpg_trim(const char *input) { + char *rtn; + char *ptr; + uint32_t offset = 0; + int inputlen = 0; + + if (!input) + return NULL; + else if (!*input) + return (char *) input; + + /* trim left */ + while (isspace(*input) && *input != '\0') + input++; + + /* trim right */ + inputlen = strlen(input); + if (inputlen) { + ptr = ((char *) input) + inputlen; + while (isspace(*--ptr)) + offset++; + } + + rtn = palloc(sizeof(char) * (inputlen - offset + 1)); + if (rtn == NULL) { + fprintf(stderr, "Not enough memory\n"); + return NULL; + } + strncpy(rtn, input, inputlen - offset); + rtn[inputlen - offset] = '\0'; + + return rtn; +} + +char* +rtpg_getSR(int srid) { + int i = 0; + int len = 0; + char *sql = NULL; + int spi_result; + TupleDesc tupdesc; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + char *tmp = NULL; + char *srs = NULL; + +/* +SELECT + CASE + WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 + THEN upper(auth_name) || ':' || auth_srid + WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 + THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') + ELSE '' + END, + proj4text, + srtext +FROM spatial_ref_sys +WHERE srid = X +LIMIT 1 +*/ + + len = sizeof(char) * (strlen("SELECT CASE WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 THEN upper(auth_name) || ':' || auth_srid WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') ELSE '' END, proj4text, srtext FROM spatial_ref_sys WHERE srid = LIMIT 1") + MAX_INT_CHARLEN + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + elog(ERROR, "rtpg_getSR: Could not allocate memory for sql\n"); + return NULL; + } + + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) { + pfree(sql); + elog(ERROR, "rtpg_getSR: Could not connect to database using SPI\n"); + return NULL; + } + + /* execute query */ + snprintf(sql, len, "SELECT CASE WHEN (upper(auth_name) = 'EPSG' OR upper(auth_name) = 'EPSGA') AND length(COALESCE(auth_srid::text, '')) > 0 THEN upper(auth_name) || ':' || auth_srid WHEN length(COALESCE(auth_name, '') || COALESCE(auth_srid::text, '')) > 0 THEN COALESCE(auth_name, '') || COALESCE(auth_srid::text, '') ELSE '' END, proj4text, srtext FROM spatial_ref_sys WHERE srid = %d LIMIT 1", srid); + POSTGIS_RT_DEBUGF(4, "SRS query: %s", sql); + spi_result = SPI_execute(sql, TRUE, 0); + SPI_pfree(sql); + if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + elog(ERROR, "rtpg_getSR: Cannot find SRID (%d) in spatial_ref_sys", srid); + return NULL; + } + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + /* which column to use? */ + for (i = 1; i < 4; i++) { + tmp = SPI_getvalue(tuple, tupdesc, i); + + /* value AND GDAL supports this SR */ + if ( + SPI_result != SPI_ERROR_NOATTRIBUTE && + SPI_result != SPI_ERROR_NOOUTFUNC && + tmp != NULL && + strlen(tmp) && + rt_util_gdal_supported_sr(tmp) + ) { + POSTGIS_RT_DEBUGF(4, "Value for column %d is %s", i, tmp); + + len = strlen(tmp) + 1; + srs = SPI_palloc(sizeof(char) * len); + if (NULL == srs) { + pfree(tmp); + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + elog(ERROR, "rtpg_getSR: Could not allocate memory for spatial reference text\n"); + return NULL; + } + strncpy(srs, tmp, len); + pfree(tmp); + + break; + } + + if (tmp != NULL) + pfree(tmp); + continue; + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + /* unable to get SR info */ + if (srs == NULL) { + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + elog(ERROR, "rtpg_getSR: Could not find a viable spatial reference for SRID (%d)", srid); + return NULL; + } + + return srs; +} diff --git a/raster/rt_pg/rtpg_internal.h b/raster/rt_pg/rtpg_internal.h new file mode 100644 index 000000000..5e0860057 --- /dev/null +++ b/raster/rt_pg/rtpg_internal.h @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef RTPG_INTERNAL_H_INCLUDED +#define RTPG_INTERNAL_H_INCLUDED + +#include "rtpostgis.h" + +/*************************************************************** + * Internal functions must be prefixed with rtpg_. This is + * keeping inline with the use of pgis_ for ./postgis C utility + * functions. + ****************************************************************/ + +char +*rtpg_strreplace( + const char *str, + const char *oldstr, const char *newstr, + int *count +); + +char * +rtpg_strtoupper(char *str); + +char * +rtpg_chartrim(const char* input, char *remove); + +char ** +rtpg_strsplit(const char *str, const char *delimiter, int *n); + +char * +rtpg_removespaces(char *str); + +char * +rtpg_trim(const char* input); + +char * +rtpg_getSR(int srid); + +#endif /* RTPG_INTERNAL_H_INCLUDED */ diff --git a/raster/rt_pg/rtpg_mapalgebra.c b/raster/rt_pg/rtpg_mapalgebra.c new file mode 100644 index 000000000..17b4a4788 --- /dev/null +++ b/raster/rt_pg/rtpg_mapalgebra.c @@ -0,0 +1,7021 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include /* for palloc */ +#include +#include +#include /* for get_typlenbyvalalign */ +#include /* for ArrayType */ +#include +#include /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ +#include /* for GetAttributeByName */ + +#include "../../postgis_config.h" +#include "lwgeom_pg.h" + +#include "rtpostgis.h" +#include "rtpg_internal.h" + +/* n-raster MapAlgebra */ +Datum RASTER_nMapAlgebra(PG_FUNCTION_ARGS); +Datum RASTER_nMapAlgebraExpr(PG_FUNCTION_ARGS); + +/* raster union aggregate */ +Datum RASTER_union_transfn(PG_FUNCTION_ARGS); +Datum RASTER_union_finalfn(PG_FUNCTION_ARGS); + +/* raster clip */ +Datum RASTER_clip(PG_FUNCTION_ARGS); + +/* reclassify specified bands of a raster */ +Datum RASTER_reclass(PG_FUNCTION_ARGS); + +/* apply colormap to specified band of a raster */ +Datum RASTER_colorMap(PG_FUNCTION_ARGS); + +/* one-raster MapAlgebra */ +Datum RASTER_mapAlgebraExpr(PG_FUNCTION_ARGS); +Datum RASTER_mapAlgebraFct(PG_FUNCTION_ARGS); + +/* one-raster neighborhood MapAlgebra */ +Datum RASTER_mapAlgebraFctNgb(PG_FUNCTION_ARGS); + +/* two-raster MapAlgebra */ +Datum RASTER_mapAlgebra2(PG_FUNCTION_ARGS); + +/* ---------------------------------------------------------------- */ +/* n-raster MapAlgebra */ +/* ---------------------------------------------------------------- */ + +typedef struct { + Oid ufc_noid; + FmgrInfo ufl_info; + FunctionCallInfoData ufc_info; + int ufc_nullcount; +} rtpg_nmapalgebra_callback_arg; + +typedef struct rtpg_nmapalgebra_arg_t *rtpg_nmapalgebra_arg; +struct rtpg_nmapalgebra_arg_t { + int numraster; + rt_pgraster **pgraster; + rt_raster *raster; + uint8_t *isempty; /* flag indicating if raster is empty */ + uint8_t *ownsdata; /* is the raster self owned or just a pointer to another raster */ + int *nband; /* source raster's band index, 0-based */ + uint8_t *hasband; /* does source raster have band at index nband? */ + + rt_pixtype pixtype; /* output raster band's pixel type */ + int hasnodata; /* NODATA flag */ + double nodataval; /* NODATA value */ + + int distance[2]; /* distance in X and Y axis */ + + rt_extenttype extenttype; /* ouput raster's extent type */ + rt_pgraster *pgcextent; /* custom extent of type rt_pgraster */ + rt_raster cextent; /* custom extent of type rt_raster */ + + rtpg_nmapalgebra_callback_arg callback; +}; + +static rtpg_nmapalgebra_arg rtpg_nmapalgebra_arg_init() { + rtpg_nmapalgebra_arg arg = NULL; + + arg = palloc(sizeof(struct rtpg_nmapalgebra_arg_t)); + if (arg == NULL) { + elog(ERROR, "rtpg_nmapalgebra_arg_init: Could not allocate memory for arguments"); + return NULL; + } + + arg->numraster = 0; + arg->pgraster = NULL; + arg->raster = NULL; + arg->isempty = NULL; + arg->ownsdata = NULL; + arg->nband = NULL; + arg->hasband = NULL; + + arg->pixtype = PT_END; + arg->hasnodata = 1; + arg->nodataval = 0; + + arg->distance[0] = 0; + arg->distance[1] = 0; + + arg->extenttype = ET_INTERSECTION; + arg->pgcextent = NULL; + arg->cextent = NULL; + + arg->callback.ufc_noid = InvalidOid; + arg->callback.ufc_nullcount = 0; + + return arg; +} + +static void rtpg_nmapalgebra_arg_destroy(rtpg_nmapalgebra_arg arg) { + int i = 0; + + if (arg->raster != NULL) { + for (i = 0; i < arg->numraster; i++) { + if (arg->raster[i] == NULL || !arg->ownsdata[i]) + continue; + + rt_raster_destroy(arg->raster[i]); + } + + pfree(arg->raster); + pfree(arg->pgraster); + pfree(arg->isempty); + pfree(arg->ownsdata); + pfree(arg->nband); + } + + if (arg->cextent != NULL) + rt_raster_destroy(arg->cextent); + + pfree(arg); +} + +static int rtpg_nmapalgebra_rastbandarg_process(rtpg_nmapalgebra_arg arg, ArrayType *array, int *allnull, int *allempty, int *noband) { + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + + HeapTupleHeader tup; + bool isnull; + Datum tupv; + + int i; + int j; + int nband; + + if (arg == NULL || array == NULL) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: NULL values not permitted for parameters"); + return 0; + } + + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + deconstruct_array( + array, + etype, + typlen, typbyval, typalign, + &e, &nulls, &n + ); + + if (!n) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Invalid argument for rastbandarg"); + return 0; + } + + /* prep arg */ + arg->numraster = n; + arg->pgraster = palloc(sizeof(rt_pgraster *) * arg->numraster); + arg->raster = palloc(sizeof(rt_raster) * arg->numraster); + arg->isempty = palloc(sizeof(uint8_t) * arg->numraster); + arg->ownsdata = palloc(sizeof(uint8_t) * arg->numraster); + arg->nband = palloc(sizeof(int) * arg->numraster); + arg->hasband = palloc(sizeof(uint8_t) * arg->numraster); + if ( + arg->pgraster == NULL || + arg->raster == NULL || + arg->isempty == NULL || + arg->ownsdata == NULL || + arg->nband == NULL || + arg->hasband == NULL + ) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not allocate memory for processing rastbandarg"); + return 0; + } + + *allnull = 0; + *allempty = 0; + *noband = 0; + + /* process each element */ + for (i = 0; i < n; i++) { + if (nulls[i]) { + arg->numraster--; + continue; + } + + POSTGIS_RT_DEBUGF(4, "Processing rastbandarg at index %d", i); + + arg->raster[i] = NULL; + arg->isempty[i] = 0; + arg->ownsdata[i] = 1; + arg->nband[i] = 0; + arg->hasband[i] = 0; + + /* each element is a tuple */ + tup = (HeapTupleHeader) DatumGetPointer(e[i]); + if (NULL == tup) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Invalid argument for rastbandarg at index %d", i); + return 0; + } + + /* first element, raster */ + POSTGIS_RT_DEBUG(4, "Processing first element (raster)"); + tupv = GetAttributeByName(tup, "rast", &isnull); + if (isnull) { + elog(NOTICE, "First argument (nband) of rastbandarg at index %d is NULL. Assuming NULL raster", i); + arg->isempty[i] = 1; + arg->ownsdata[i] = 0; + + (*allnull)++; + (*allempty)++; + (*noband)++; + + continue; + } + + arg->pgraster[i] = (rt_pgraster *) PG_DETOAST_DATUM(tupv); + + /* see if this is a copy of an existing pgraster */ + for (j = 0; j < i; j++) { + if (!arg->isempty[j] && (arg->pgraster[i] == arg->pgraster[j])) { + POSTGIS_RT_DEBUG(4, "raster matching existing same raster found"); + arg->raster[i] = arg->raster[j]; + arg->ownsdata[i] = 0; + break; + } + } + + if (arg->ownsdata[i]) { + POSTGIS_RT_DEBUG(4, "deserializing raster"); + arg->raster[i] = rt_raster_deserialize(arg->pgraster[i], FALSE); + if (arg->raster[i] == NULL) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not deserialize raster at index %d", i); + return 0; + } + } + + /* is raster empty? */ + arg->isempty[i] = rt_raster_is_empty(arg->raster[i]); + if (arg->isempty[i]) { + (*allempty)++; + (*noband)++; + + continue; + } + + /* second element, nband */ + POSTGIS_RT_DEBUG(4, "Processing second element (nband)"); + tupv = GetAttributeByName(tup, "nband", &isnull); + if (isnull) { + nband = 1; + elog(NOTICE, "First argument (nband) of rastbandarg at index %d is NULL. Assuming nband = %d", i, nband); + } + else + nband = DatumGetInt32(tupv); + + if (nband < 1) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Band number provided for rastbandarg at index %d must be greater than zero (1-based)", i); + return 0; + } + + arg->nband[i] = nband - 1; + arg->hasband[i] = rt_raster_has_band(arg->raster[i], arg->nband[i]); + if (!arg->hasband[i]) { + (*noband)++; + POSTGIS_RT_DEBUGF(4, "Band at index %d not found in raster", nband); + } + } + + if (arg->numraster < n) { + arg->pgraster = repalloc(arg->pgraster, sizeof(rt_pgraster *) * arg->numraster); + arg->raster = repalloc(arg->raster, sizeof(rt_raster) * arg->numraster); + arg->isempty = repalloc(arg->isempty, sizeof(uint8_t) * arg->numraster); + arg->ownsdata = repalloc(arg->ownsdata, sizeof(uint8_t) * arg->numraster); + arg->nband = repalloc(arg->nband, sizeof(int) * arg->numraster); + arg->hasband = repalloc(arg->hasband, sizeof(uint8_t) * arg->numraster); + if ( + arg->pgraster == NULL || + arg->raster == NULL || + arg->isempty == NULL || + arg->ownsdata == NULL || + arg->nband == NULL || + arg->hasband == NULL + ) { + elog(ERROR, "rtpg_nmapalgebra_rastbandarg_process: Could not reallocate memory for processed rastbandarg"); + return 0; + } + } + + POSTGIS_RT_DEBUGF(4, "arg->numraster = %d", arg->numraster); + + return 1; +} + +/* + Callback for RASTER_nMapAlgebra +*/ +static int rtpg_nmapalgebra_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + rtpg_nmapalgebra_callback_arg *callback = (rtpg_nmapalgebra_callback_arg *) userarg; + + int16 typlen; + bool typbyval; + char typalign; + + ArrayType *mdValues = NULL; + Datum *_values = NULL; + bool *_nodata = NULL; + + ArrayType *mdPos = NULL; + Datum *_pos = NULL; + bool *_null = NULL; + + int i = 0; + int x = 0; + int y = 0; + int z = 0; + int dim[3] = {0}; + int lbound[3] = {1, 1, 1}; + Datum datum = (Datum) NULL; + + if (arg == NULL) + return 0; + + *value = 0; + *nodata = 0; + + dim[0] = arg->rasters; + dim[1] = arg->rows; + dim[2] = arg->columns; + + _values = palloc(sizeof(Datum) * arg->rasters * arg->rows * arg->columns); + _nodata = palloc(sizeof(bool) * arg->rasters * arg->rows * arg->columns); + if (_values == NULL || _nodata == NULL) { + elog(ERROR, "rtpg_nmapalgebra_callback: Could not allocate memory for values array"); + return 0; + } + + /* build mdValues */ + i = 0; + /* raster */ + for (z = 0; z < arg->rasters; z++) { + /* Y axis */ + for (y = 0; y < arg->rows; y++) { + /* X axis */ + for (x = 0; x < arg->columns; x++) { + POSTGIS_RT_DEBUGF(4, "(z, y ,x) = (%d, %d, %d)", z, y, x); + POSTGIS_RT_DEBUGF(4, "(value, nodata) = (%f, %d)", arg->values[z][y][x], arg->nodata[z][y][x]); + + _nodata[i] = (bool) arg->nodata[z][y][x]; + if (!_nodata[i]) + _values[i] = Float8GetDatum(arg->values[z][y][x]); + else + _values[i] = (Datum) NULL; + + i++; + } + } + } + + /* info about the type of item in the multi-dimensional array (float8). */ + get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign); + + /* construct mdValues */ + mdValues = construct_md_array( + _values, _nodata, + 3, dim, lbound, + FLOAT8OID, + typlen, typbyval, typalign + ); + pfree(_nodata); + pfree(_values); + + _pos = palloc(sizeof(Datum) * (arg->rasters + 1) * 2); + _null = palloc(sizeof(bool) * (arg->rasters + 1) * 2); + if (_pos == NULL || _null == NULL) { + pfree(mdValues); + elog(ERROR, "rtpg_nmapalgebra_callback: Could not allocate memory for position array"); + return 0; + } + memset(_null, 0, sizeof(bool) * (arg->rasters + 1) * 2); + + /* build mdPos */ + i = 0; + _pos[i] = arg->dst_pixel[0] + 1; + i++; + _pos[i] = arg->dst_pixel[1] + 1; + i++; + + for (z = 0; z < arg->rasters; z++) { + _pos[i] = arg->src_pixel[z][0] + 1; + i++; + + _pos[i] = arg->src_pixel[z][1] + 1; + i++; + } + + /* info about the type of item in the multi-dimensional array (int4). */ + get_typlenbyvalalign(INT4OID, &typlen, &typbyval, &typalign); + + /* reuse dim and lbound, just tweak to what we need */ + dim[0] = arg->rasters + 1; + dim[1] = 2; + lbound[0] = 0; + + /* construct mdPos */ + mdPos = construct_md_array( + _pos, _null, + 2, dim, lbound, + INT4OID, + typlen, typbyval, typalign + ); + pfree(_pos); + pfree(_null); + + callback->ufc_info.arg[0] = PointerGetDatum(mdValues); + callback->ufc_info.arg[1] = PointerGetDatum(mdPos); + + /* function is strict and null parameter is passed */ + /* http://archives.postgresql.org/pgsql-general/2011-11/msg00424.php */ + if (callback->ufl_info.fn_strict && callback->ufc_nullcount) { + *nodata = 1; + + pfree(mdValues); + pfree(mdPos); + + return 1; + } + + /* call user callback function */ + datum = FunctionCallInvoke(&(callback->ufc_info)); + pfree(mdValues); + pfree(mdPos); + + /* result is not null*/ + if (!callback->ufc_info.isnull) + *value = DatumGetFloat8(datum); + else + *nodata = 1; + + return 1; +} + +/* + ST_MapAlgebra for n rasters +*/ +PG_FUNCTION_INFO_V1(RASTER_nMapAlgebra); +Datum RASTER_nMapAlgebra(PG_FUNCTION_ARGS) +{ + rtpg_nmapalgebra_arg arg = NULL; + rt_iterator itrset; + int i = 0; + int noerr = 0; + int allnull = 0; + int allempty = 0; + int noband = 0; + + rt_raster raster = NULL; + rt_band band = NULL; + rt_pgraster *pgraster = NULL; + + POSTGIS_RT_DEBUG(3, "Starting..."); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* init argument struct */ + arg = rtpg_nmapalgebra_arg_init(); + if (arg == NULL) { + elog(ERROR, "RASTER_nMapAlgebra: Could not initialize argument structure"); + PG_RETURN_NULL(); + } + + /* let helper function process rastbandarg (0) */ + if (!rtpg_nmapalgebra_rastbandarg_process(arg, PG_GETARG_ARRAYTYPE_P(0), &allnull, &allempty, &noband)) { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Could not process rastbandarg"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(4, "allnull, allempty, noband = %d, %d, %d", allnull, allempty, noband); + + /* all rasters are NULL, return NULL */ + if (allnull == arg->numraster) { + elog(NOTICE, "All input rasters are NULL. Returning NULL"); + rtpg_nmapalgebra_arg_destroy(arg); + PG_RETURN_NULL(); + } + + /* pixel type (2) */ + if (!PG_ARGISNULL(2)) { + char *pixtypename = text_to_cstring(PG_GETARG_TEXT_P(2)); + + /* Get the pixel type index */ + arg->pixtype = rt_pixtype_index_from_name(pixtypename); + if (arg->pixtype == PT_END) { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Invalid pixel type: %s", pixtypename); + PG_RETURN_NULL(); + } + } + + /* distancex (3) */ + if (!PG_ARGISNULL(3)) + arg->distance[0] = PG_GETARG_INT32(3); + /* distancey (4) */ + if (!PG_ARGISNULL(4)) + arg->distance[1] = PG_GETARG_INT32(4); + + if (arg->distance[0] < 0 || arg->distance[1] < 0) { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Distance for X and Y axis must be greater than or equal to zero"); + PG_RETURN_NULL(); + } + + /* extent type (5) */ + if (!PG_ARGISNULL(5)) { + char *extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(5)))); + arg->extenttype = rt_util_extent_type(extenttypename); + } + POSTGIS_RT_DEBUGF(4, "extenttype: %d", arg->extenttype); + + /* custom extent (6) */ + if (arg->extenttype == ET_CUSTOM) { + if (PG_ARGISNULL(6)) { + elog(NOTICE, "Custom extent is NULL. Returning NULL"); + rtpg_nmapalgebra_arg_destroy(arg); + PG_RETURN_NULL(); + } + + arg->pgcextent = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(6)); + + /* only need the raster header */ + arg->cextent = rt_raster_deserialize(arg->pgcextent, TRUE); + if (arg->cextent == NULL) { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Could not deserialize custom extent"); + PG_RETURN_NULL(); + } + else if (rt_raster_is_empty(arg->cextent)) { + elog(NOTICE, "Custom extent is an empty raster. Returning empty raster"); + rtpg_nmapalgebra_arg_destroy(arg); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + elog(ERROR, "RASTER_nMapAlgebra: Could not create empty raster"); + PG_RETURN_NULL(); + } + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgraster) PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); + } + } + + noerr = 1; + /* all rasters are empty, return empty raster */ + if (allempty == arg->numraster) { + elog(NOTICE, "All input rasters are empty. Returning empty raster"); + noerr = 0; + } + /* all rasters don't have indicated band, return empty raster */ + else if (noband == arg->numraster) { + elog(NOTICE, "All input rasters do not have bands at indicated indexes. Returning empty raster"); + noerr = 0; + } + if (!noerr) { + rtpg_nmapalgebra_arg_destroy(arg); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + elog(ERROR, "RASTER_nMapAlgebra: Could not create empty raster"); + PG_RETURN_NULL(); + } + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgraster) PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); + } + + /* do regprocedure last (1) */ + if (!PG_ARGISNULL(1) || get_fn_expr_argtype(fcinfo->flinfo, 1) == REGPROCEDUREOID) { + POSTGIS_RT_DEBUG(4, "processing callbackfunc"); + arg->callback.ufc_noid = PG_GETARG_OID(1); + + /* get function info */ + fmgr_info(arg->callback.ufc_noid, &(arg->callback.ufl_info)); + + /* function cannot return set */ + noerr = 0; + if (arg->callback.ufl_info.fn_retset) { + noerr = 1; + } + /* function should have correct # of args */ + else if (arg->callback.ufl_info.fn_nargs != 3) { + noerr = 2; + } + + /* + TODO: consider adding checks of the userfunction parameters + should be able to use get_fn_expr_argtype() of fmgr.c + */ + + if (noerr > 0) { + rtpg_nmapalgebra_arg_destroy(arg); + if (noerr > 1) + elog(ERROR, "RASTER_nMapAlgebra: Function provided must have three input parameters"); + else + elog(ERROR, "RASTER_nMapAlgebra: Function provided must return double precision, not resultset"); + PG_RETURN_NULL(); + } + + if (func_volatile(arg->callback.ufc_noid) == 'v') + elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); + + /* prep function call data */ +#if POSTGIS_PGSQL_VERSION > 90 + InitFunctionCallInfoData(arg->callback.ufc_info, &(arg->callback.ufl_info), arg->callback.ufl_info.fn_nargs, InvalidOid, NULL, NULL); +#else + InitFunctionCallInfoData(arg->callback.ufc_info, &(arg->callback.ufl_info), arg->callback.ufl_info.fn_nargs, NULL, NULL); +#endif + memset(arg->callback.ufc_info.argnull, FALSE, sizeof(bool) * arg->callback.ufl_info.fn_nargs); + + /* userargs (7) */ + if (!PG_ARGISNULL(7)) + arg->callback.ufc_info.arg[2] = PG_GETARG_DATUM(7); + else { + arg->callback.ufc_info.arg[2] = (Datum) NULL; + arg->callback.ufc_info.argnull[2] = TRUE; + arg->callback.ufc_nullcount++; + } + } + else { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: callbackfunc must be provided"); + PG_RETURN_NULL(); + } + + /* determine nodataval and possibly pixtype */ + /* band to check */ + switch (arg->extenttype) { + case ET_LAST: + i = arg->numraster - 1; + break; + case ET_SECOND: + if (arg->numraster > 1) { + i = 1; + break; + } + default: + i = 0; + break; + } + /* find first viable band */ + if (!arg->hasband[i]) { + for (i = 0; i < arg->numraster; i++) { + if (arg->hasband[i]) + break; + } + if (i >= arg->numraster) + i = arg->numraster - 1; + } + band = rt_raster_get_band(arg->raster[i], arg->nband[i]); + + /* set pixel type if PT_END */ + if (arg->pixtype == PT_END) + arg->pixtype = rt_band_get_pixtype(band); + + /* set hasnodata and nodataval */ + arg->hasnodata = 1; + if (rt_band_get_hasnodata_flag(band)) + rt_band_get_nodata(band, &(arg->nodataval)); + else + arg->nodataval = rt_band_get_min_value(band); + + POSTGIS_RT_DEBUGF(4, "pixtype, hasnodata, nodataval: %s, %d, %f", rt_pixtype_name(arg->pixtype), arg->hasnodata, arg->nodataval); + + /* init itrset */ + itrset = palloc(sizeof(struct rt_iterator_t) * arg->numraster); + if (itrset == NULL) { + rtpg_nmapalgebra_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Could not allocate memory for iterator arguments"); + PG_RETURN_NULL(); + } + + /* set itrset */ + for (i = 0; i < arg->numraster; i++) { + itrset[i].raster = arg->raster[i]; + itrset[i].nband = arg->nband[i]; + itrset[i].nbnodata = 1; + } + + /* pass everything to iterator */ + noerr = rt_raster_iterator( + itrset, arg->numraster, + arg->extenttype, arg->cextent, + arg->pixtype, + arg->hasnodata, arg->nodataval, + arg->distance[0], arg->distance[1], + &(arg->callback), + rtpg_nmapalgebra_callback, + &raster + ); + + /* cleanup */ + pfree(itrset); + rtpg_nmapalgebra_arg_destroy(arg); + + if (noerr != ES_NONE) { + elog(ERROR, "RASTER_nMapAlgebra: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + else if (raster == NULL) + PG_RETURN_NULL(); + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + + POSTGIS_RT_DEBUG(3, "Finished"); + + if (!pgraster) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); +} + +/* ---------------------------------------------------------------- */ +/* expression ST_MapAlgebra for n rasters */ +/* ---------------------------------------------------------------- */ + +typedef struct { + int exprcount; + + struct { + SPIPlanPtr spi_plan; + uint32_t spi_argcount; + uint8_t *spi_argpos; + + int hasval; + double val; + } expr[3]; + + struct { + int hasval; + double val; + } nodatanodata; + + struct { + int count; + char **val; + } kw; + +} rtpg_nmapalgebraexpr_callback_arg; + +typedef struct rtpg_nmapalgebraexpr_arg_t *rtpg_nmapalgebraexpr_arg; +struct rtpg_nmapalgebraexpr_arg_t { + rtpg_nmapalgebra_arg bandarg; + + rtpg_nmapalgebraexpr_callback_arg callback; +}; + +static rtpg_nmapalgebraexpr_arg rtpg_nmapalgebraexpr_arg_init(int cnt, char **kw) { + rtpg_nmapalgebraexpr_arg arg = NULL; + int i = 0; + + arg = palloc(sizeof(struct rtpg_nmapalgebraexpr_arg_t)); + if (arg == NULL) { + elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for arguments"); + return NULL; + } + + arg->bandarg = rtpg_nmapalgebra_arg_init(); + if (arg->bandarg == NULL) { + elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for arg->bandarg"); + return NULL; + } + + arg->callback.kw.count = cnt; + arg->callback.kw.val = kw; + + arg->callback.exprcount = 3; + for (i = 0; i < arg->callback.exprcount; i++) { + arg->callback.expr[i].spi_plan = NULL; + arg->callback.expr[i].spi_argcount = 0; + arg->callback.expr[i].spi_argpos = palloc(cnt * sizeof(uint8_t)); + if (arg->callback.expr[i].spi_argpos == NULL) { + elog(ERROR, "rtpg_nmapalgebraexpr_arg_init: Could not allocate memory for spi_argpos"); + return NULL; + } + memset(arg->callback.expr[i].spi_argpos, 0, sizeof(uint8_t) * cnt); + arg->callback.expr[i].hasval = 0; + arg->callback.expr[i].val = 0; + } + + arg->callback.nodatanodata.hasval = 0; + arg->callback.nodatanodata.val = 0; + + return arg; +} + +static void rtpg_nmapalgebraexpr_arg_destroy(rtpg_nmapalgebraexpr_arg arg) { + int i = 0; + + rtpg_nmapalgebra_arg_destroy(arg->bandarg); + + for (i = 0; i < arg->callback.exprcount; i++) { + if (arg->callback.expr[i].spi_plan) + SPI_freeplan(arg->callback.expr[i].spi_plan); + if (arg->callback.kw.count) + pfree(arg->callback.expr[i].spi_argpos); + } + + pfree(arg); +} + +static int rtpg_nmapalgebraexpr_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + rtpg_nmapalgebraexpr_callback_arg *callback = (rtpg_nmapalgebraexpr_callback_arg *) userarg; + SPIPlanPtr plan = NULL; + int i = 0; + int id = -1; + + if (arg == NULL) + return 0; + + *value = 0; + *nodata = 0; + + /* 2 raster */ + if (arg->rasters > 1) { + /* nodata1 = 1 AND nodata2 = 1, nodatanodataval */ + if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) { + if (callback->nodatanodata.hasval) + *value = callback->nodatanodata.val; + else + *nodata = 1; + } + /* nodata1 = 1 AND nodata2 != 1, nodata1expr */ + else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) { + id = 1; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else + *nodata = 1; + } + /* nodata1 != 1 AND nodata2 = 1, nodata2expr */ + else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) { + id = 2; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else + *nodata = 1; + } + /* expression */ + else { + id = 0; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else { + if (callback->nodatanodata.hasval) + *value = callback->nodatanodata.val; + else + *nodata = 1; + } + } + } + /* 1 raster */ + else { + /* nodata = 1, nodata1expr */ + if (arg->nodata[0][0][0]) { + id = 1; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else + *nodata = 1; + } + /* expression */ + else { + id = 0; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else { + /* see if nodata1expr is available */ + id = 1; + if (callback->expr[id].hasval) + *value = callback->expr[id].val; + else if (callback->expr[id].spi_plan) + plan = callback->expr[id].spi_plan; + else + *nodata = 1; + } + } + } + + /* run prepared plan */ + if (plan != NULL) { + Datum values[12]; + bool nulls[12]; + int err = 0; + + TupleDesc tupdesc; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isnull = FALSE; + + POSTGIS_RT_DEBUGF(4, "Running plan %d", id); + + /* init values and nulls */ + memset(values, (Datum) NULL, sizeof(Datum) * callback->kw.count); + memset(nulls, FALSE, sizeof(bool) * callback->kw.count); + + if (callback->expr[id].spi_argcount) { + int idx = 0; + + for (i = 0; i < callback->kw.count; i++) { + idx = callback->expr[id].spi_argpos[i]; + if (idx < 1) continue; + idx--; /* 1-based now 0-based */ + + switch (i) { + /* [rast.x] */ + case 0: + values[idx] = Int32GetDatum(arg->src_pixel[0][0] + 1); + break; + /* [rast.y] */ + case 1: + values[idx] = Int32GetDatum(arg->src_pixel[0][1] + 1); + break; + /* [rast.val] */ + case 2: + /* [rast] */ + case 3: + if (!arg->nodata[0][0][0]) + values[idx] = Float8GetDatum(arg->values[0][0][0]); + else + nulls[idx] = TRUE; + break; + + /* [rast1.x] */ + case 4: + values[idx] = Int32GetDatum(arg->src_pixel[0][0] + 1); + break; + /* [rast1.y] */ + case 5: + values[idx] = Int32GetDatum(arg->src_pixel[0][1] + 1); + break; + /* [rast1.val] */ + case 6: + /* [rast1] */ + case 7: + if (!arg->nodata[0][0][0]) + values[idx] = Float8GetDatum(arg->values[0][0][0]); + else + nulls[idx] = TRUE; + break; + + /* [rast2.x] */ + case 8: + values[idx] = Int32GetDatum(arg->src_pixel[1][0] + 1); + break; + /* [rast2.y] */ + case 9: + values[idx] = Int32GetDatum(arg->src_pixel[1][1] + 1); + break; + /* [rast2.val] */ + case 10: + /* [rast2] */ + case 11: + if (!arg->nodata[1][0][0]) + values[idx] = Float8GetDatum(arg->values[1][0][0]); + else + nulls[idx] = TRUE; + break; + } + + } + } + + /* run prepared plan */ + err = SPI_execute_plan(plan, values, nulls, TRUE, 1); + if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + elog(ERROR, "rtpg_nmapalgebraexpr_callback: Unexpected error when running prepared statement %d", id); + return 0; + } + + /* get output of prepared plan */ + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + if (SPI_tuptable) SPI_freetuptable(tuptable); + elog(ERROR, "rtpg_nmapalgebraexpr_callback: Could not get result of prepared statement %d", id); + return 0; + } + + if (!isnull) { + *value = DatumGetFloat8(datum); + POSTGIS_RT_DEBUG(4, "Getting value from Datum"); + } + else { + /* 2 raster, check nodatanodataval */ + if (arg->rasters > 1) { + if (callback->nodatanodata.hasval) + *value = callback->nodatanodata.val; + else + *nodata = 1; + } + /* 1 raster, check nodataval */ + else { + if (callback->expr[1].hasval) + *value = callback->expr[1].val; + else + *nodata = 1; + } + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + } + + POSTGIS_RT_DEBUGF(4, "(value, nodata) = (%f, %d)", *value, *nodata); + return 1; +} + +PG_FUNCTION_INFO_V1(RASTER_nMapAlgebraExpr); +Datum RASTER_nMapAlgebraExpr(PG_FUNCTION_ARGS) +{ + MemoryContext mainMemCtx = CurrentMemoryContext; + rtpg_nmapalgebraexpr_arg arg = NULL; + rt_iterator itrset; + uint16_t exprpos[3] = {1, 4, 5}; + + int i = 0; + int j = 0; + int k = 0; + + int numraster = 0; + int err = 0; + int allnull = 0; + int allempty = 0; + int noband = 0; + int len = 0; + + TupleDesc tupdesc; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isnull = FALSE; + + rt_raster raster = NULL; + rt_band band = NULL; + rt_pgraster *pgraster = NULL; + + const int argkwcount = 12; + char *argkw[] = { + "[rast.x]", + "[rast.y]", + "[rast.val]", + "[rast]", + "[rast1.x]", + "[rast1.y]", + "[rast1.val]", + "[rast1]", + "[rast2.x]", + "[rast2.y]", + "[rast2.val]", + "[rast2]" + }; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* init argument struct */ + arg = rtpg_nmapalgebraexpr_arg_init(argkwcount, argkw); + if (arg == NULL) { + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not initialize argument structure"); + PG_RETURN_NULL(); + } + + /* let helper function process rastbandarg (0) */ + if (!rtpg_nmapalgebra_rastbandarg_process(arg->bandarg, PG_GETARG_ARRAYTYPE_P(0), &allnull, &allempty, &noband)) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebra: Could not process rastbandarg"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(4, "allnull, allempty, noband = %d, %d, %d", allnull, allempty, noband); + + /* all rasters are NULL, return NULL */ + if (allnull == arg->bandarg->numraster) { + elog(NOTICE, "All input rasters are NULL. Returning NULL"); + rtpg_nmapalgebraexpr_arg_destroy(arg); + PG_RETURN_NULL(); + } + + /* only work on one or two rasters */ + if (arg->bandarg->numraster > 1) + numraster = 2; + else + numraster = 1; + + /* pixel type (2) */ + if (!PG_ARGISNULL(2)) { + char *pixtypename = text_to_cstring(PG_GETARG_TEXT_P(2)); + + /* Get the pixel type index */ + arg->bandarg->pixtype = rt_pixtype_index_from_name(pixtypename); + if (arg->bandarg->pixtype == PT_END) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebraExpr: Invalid pixel type: %s", pixtypename); + PG_RETURN_NULL(); + } + } + POSTGIS_RT_DEBUGF(4, "pixeltype: %d", arg->bandarg->pixtype); + + /* extent type (3) */ + if (!PG_ARGISNULL(3)) { + char *extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(3)))); + arg->bandarg->extenttype = rt_util_extent_type(extenttypename); + } + + if (arg->bandarg->extenttype == ET_CUSTOM) { + if (numraster < 2) { + elog(NOTICE, "CUSTOM extent type not supported. Defaulting to FIRST"); + arg->bandarg->extenttype = ET_FIRST; + } + else { + elog(NOTICE, "CUSTOM extent type not supported. Defaulting to INTERSECTION"); + arg->bandarg->extenttype = ET_INTERSECTION; + } + } + else if (numraster < 2) + arg->bandarg->extenttype = ET_FIRST; + + POSTGIS_RT_DEBUGF(4, "extenttype: %d", arg->bandarg->extenttype); + + /* nodatanodataval (6) */ + if (!PG_ARGISNULL(6)) { + arg->callback.nodatanodata.hasval = 1; + arg->callback.nodatanodata.val = PG_GETARG_FLOAT8(6); + } + + err = 0; + /* all rasters are empty, return empty raster */ + if (allempty == arg->bandarg->numraster) { + elog(NOTICE, "All input rasters are empty. Returning empty raster"); + err = 1; + } + /* all rasters don't have indicated band, return empty raster */ + else if (noband == arg->bandarg->numraster) { + elog(NOTICE, "All input rasters do not have bands at indicated indexes. Returning empty raster"); + err = 1; + } + if (err) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not create empty raster"); + PG_RETURN_NULL(); + } + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgraster) PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); + } + + /* connect SPI */ + if (SPI_connect() != SPI_OK_CONNECT) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not connect to the SPI manager"); + PG_RETURN_NULL(); + } + + /* + process expressions + + exprpos elements are: + 1 - expression => spi_plan[0] + 4 - nodata1expr => spi_plan[1] + 5 - nodata2expr => spi_plan[2] + */ + for (i = 0; i < arg->callback.exprcount; i++) { + char *expr = NULL; + char *tmp = NULL; + char *sql = NULL; + char place[5] = "$1"; + + if (PG_ARGISNULL(exprpos[i])) + continue; + + expr = text_to_cstring(PG_GETARG_TEXT_P(exprpos[i])); + POSTGIS_RT_DEBUGF(3, "raw expr of argument #%d: %s", exprpos[i], expr); + + for (j = 0, k = 1; j < argkwcount; j++) { + /* attempt to replace keyword with placeholder */ + len = 0; + tmp = rtpg_strreplace(expr, argkw[j], place, &len); + pfree(expr); + expr = tmp; + + if (len) { + POSTGIS_RT_DEBUGF(4, "kw #%d (%s) at pos $%d", j, argkw[j], k); + arg->callback.expr[i].spi_argcount++; + arg->callback.expr[i].spi_argpos[j] = k++; + + sprintf(place, "$%d", k); + } + else + arg->callback.expr[i].spi_argpos[j] = 0; + } + + len = strlen("SELECT (") + strlen(expr) + strlen(")::double precision"); + sql = (char *) palloc(len + 1); + if (sql == NULL) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not allocate memory for expression parameter %d", exprpos[i]); + PG_RETURN_NULL(); + } + + strncpy(sql, "SELECT (", strlen("SELECT (")); + strncpy(sql + strlen("SELECT ("), expr, strlen(expr)); + strncpy(sql + strlen("SELECT (") + strlen(expr), ")::double precision", strlen(")::double precision")); + sql[len] = '\0'; + + POSTGIS_RT_DEBUGF(3, "sql #%d: %s", exprpos[i], sql); + + /* prepared plan */ + if (arg->callback.expr[i].spi_argcount) { + Oid *argtype = (Oid *) palloc(arg->callback.expr[i].spi_argcount * sizeof(Oid)); + POSTGIS_RT_DEBUGF(3, "expression parameter %d is a prepared plan", exprpos[i]); + if (argtype == NULL) { + pfree(sql); + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not allocate memory for prepared plan argtypes of expression parameter %d", exprpos[i]); + PG_RETURN_NULL(); + } + + /* specify datatypes of parameters */ + for (j = 0, k = 0; j < argkwcount; j++) { + if (arg->callback.expr[i].spi_argpos[j] < 1) continue; + + /* positions are INT4 */ + if ( + (strstr(argkw[j], "[rast.x]") != NULL) || + (strstr(argkw[j], "[rast.y]") != NULL) || + (strstr(argkw[j], "[rast1.x]") != NULL) || + (strstr(argkw[j], "[rast1.y]") != NULL) || + (strstr(argkw[j], "[rast2.x]") != NULL) || + (strstr(argkw[j], "[rast2.y]") != NULL) + ) + argtype[k] = INT4OID; + /* everything else is FLOAT8 */ + else + argtype[k] = FLOAT8OID; + + k++; + } + + arg->callback.expr[i].spi_plan = SPI_prepare(sql, arg->callback.expr[i].spi_argcount, argtype); + pfree(argtype); + pfree(sql); + + if (arg->callback.expr[i].spi_plan == NULL) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not create prepared plan of expression parameter %d", exprpos[i]); + PG_RETURN_NULL(); + } + } + /* no args, just execute query */ + else { + POSTGIS_RT_DEBUGF(3, "expression parameter %d has no args, simply executing", exprpos[i]); + err = SPI_execute(sql, TRUE, 0); + pfree(sql); + + if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not evaluate expression parameter %d", exprpos[i]); + PG_RETURN_NULL(); + } + + /* get output of prepared plan */ + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + if (SPI_tuptable) SPI_freetuptable(tuptable); + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not get result of expression parameter %d", exprpos[i]); + PG_RETURN_NULL(); + } + + if (!isnull) { + arg->callback.expr[i].hasval = 1; + arg->callback.expr[i].val = DatumGetFloat8(datum); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + } + } + + /* determine nodataval and possibly pixtype */ + /* band to check */ + switch (arg->bandarg->extenttype) { + case ET_LAST: + case ET_SECOND: + if (numraster > 1) + i = 1; + else + i = 0; + break; + default: + i = 0; + break; + } + /* find first viable band */ + if (!arg->bandarg->hasband[i]) { + for (i = 0; i < numraster; i++) { + if (arg->bandarg->hasband[i]) + break; + } + if (i >= numraster) + i = numraster - 1; + } + band = rt_raster_get_band(arg->bandarg->raster[i], arg->bandarg->nband[i]); + + /* set pixel type if PT_END */ + if (arg->bandarg->pixtype == PT_END) + arg->bandarg->pixtype = rt_band_get_pixtype(band); + + /* set hasnodata and nodataval */ + arg->bandarg->hasnodata = 1; + if (rt_band_get_hasnodata_flag(band)) + rt_band_get_nodata(band, &(arg->bandarg->nodataval)); + else + arg->bandarg->nodataval = rt_band_get_min_value(band); + + POSTGIS_RT_DEBUGF(4, "pixtype, hasnodata, nodataval: %s, %d, %f", rt_pixtype_name(arg->bandarg->pixtype), arg->bandarg->hasnodata, arg->bandarg->nodataval); + + /* init itrset */ + itrset = palloc(sizeof(struct rt_iterator_t) * numraster); + if (itrset == NULL) { + rtpg_nmapalgebraexpr_arg_destroy(arg); + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebra: Could not allocate memory for iterator arguments"); + PG_RETURN_NULL(); + } + + /* set itrset */ + for (i = 0; i < numraster; i++) { + itrset[i].raster = arg->bandarg->raster[i]; + itrset[i].nband = arg->bandarg->nband[i]; + itrset[i].nbnodata = 1; + } + + /* pass everything to iterator */ + err = rt_raster_iterator( + itrset, numraster, + arg->bandarg->extenttype, arg->bandarg->cextent, + arg->bandarg->pixtype, + arg->bandarg->hasnodata, arg->bandarg->nodataval, + 0, 0, + &(arg->callback), + rtpg_nmapalgebraexpr_callback, + &raster + ); + + pfree(itrset); + rtpg_nmapalgebraexpr_arg_destroy(arg); + + if (err != ES_NONE) { + SPI_finish(); + elog(ERROR, "RASTER_nMapAlgebraExpr: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + else if (raster == NULL) { + SPI_finish(); + PG_RETURN_NULL(); + } + + /* switch to prior memory context to ensure memory allocated in correct context */ + MemoryContextSwitchTo(mainMemCtx); + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + + /* finish SPI */ + SPI_finish(); + + if (!pgraster) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); +} + +/* ---------------------------------------------------------------- */ +/* ST_Union aggregate functions */ +/* ---------------------------------------------------------------- */ + +typedef enum { + UT_LAST = 0, + UT_FIRST, + UT_MIN, + UT_MAX, + UT_COUNT, + UT_SUM, + UT_MEAN, + UT_RANGE +} rtpg_union_type; + +/* internal function translating text of UNION type to enum */ +static rtpg_union_type rtpg_uniontype_index_from_name(const char *cutype) { + assert(cutype && strlen(cutype) > 0); + + if (strcmp(cutype, "LAST") == 0) + return UT_LAST; + else if (strcmp(cutype, "FIRST") == 0) + return UT_FIRST; + else if (strcmp(cutype, "MIN") == 0) + return UT_MIN; + else if (strcmp(cutype, "MAX") == 0) + return UT_MAX; + else if (strcmp(cutype, "COUNT") == 0) + return UT_COUNT; + else if (strcmp(cutype, "SUM") == 0) + return UT_SUM; + else if (strcmp(cutype, "MEAN") == 0) + return UT_MEAN; + else if (strcmp(cutype, "RANGE") == 0) + return UT_RANGE; + + return UT_LAST; +} + +typedef struct rtpg_union_band_arg_t *rtpg_union_band_arg; +struct rtpg_union_band_arg_t { + int nband; /* source raster's band index, 0-based */ + rtpg_union_type uniontype; + + int numraster; + rt_raster *raster; +}; + +typedef struct rtpg_union_arg_t *rtpg_union_arg; +struct rtpg_union_arg_t { + int numband; /* number of bandargs */ + rtpg_union_band_arg bandarg; +}; + +static void rtpg_union_arg_destroy(rtpg_union_arg arg) { + int i = 0; + int j = 0; + int k = 0; + + if (arg->bandarg != NULL) { + for (i = 0; i < arg->numband; i++) { + if (!arg->bandarg[i].numraster) + continue; + + for (j = 0; j < arg->bandarg[i].numraster; j++) { + if (arg->bandarg[i].raster[j] == NULL) + continue; + + for (k = rt_raster_get_num_bands(arg->bandarg[i].raster[j]) - 1; k >= 0; k--) + rt_band_destroy(rt_raster_get_band(arg->bandarg[i].raster[j], k)); + rt_raster_destroy(arg->bandarg[i].raster[j]); + } + + pfree(arg->bandarg[i].raster); + } + + pfree(arg->bandarg); + } + + pfree(arg); +} + +static int rtpg_union_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + rtpg_union_type *utype = (rtpg_union_type *) userarg; + + if (arg == NULL) + return 0; + + if ( + arg->rasters != 2 || + arg->rows != 1 || + arg->columns != 1 + ) { + elog(ERROR, "rtpg_union_callback: Invalid arguments passed to callback"); + return 0; + } + + *value = 0; + *nodata = 0; + + /* handle NODATA situations except for COUNT, which is a special case */ + if (*utype != UT_COUNT) { + /* both NODATA */ + if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) { + *nodata = 1; + POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); + return 1; + } + /* second NODATA */ + else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) { + *value = arg->values[0][0][0]; + POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); + return 1; + } + /* first NODATA */ + else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) { + *value = arg->values[1][0][0]; + POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); + return 1; + } + } + + switch (*utype) { + case UT_FIRST: + *value = arg->values[0][0][0]; + break; + case UT_MIN: + if (arg->values[0][0][0] < arg->values[1][0][0]) + *value = arg->values[0][0][0]; + else + *value = arg->values[1][0][0]; + break; + case UT_MAX: + if (arg->values[0][0][0] > arg->values[1][0][0]) + *value = arg->values[0][0][0]; + else + *value = arg->values[1][0][0]; + break; + case UT_COUNT: + /* both NODATA */ + if (arg->nodata[0][0][0] && arg->nodata[1][0][0]) + *value = 0; + /* second NODATA */ + else if (!arg->nodata[0][0][0] && arg->nodata[1][0][0]) + *value = arg->values[0][0][0]; + /* first NODATA */ + else if (arg->nodata[0][0][0] && !arg->nodata[1][0][0]) + *value = 1; + /* has value, increment */ + else + *value = arg->values[0][0][0] + 1; + break; + case UT_SUM: + *value = arg->values[0][0][0] + arg->values[1][0][0]; + break; + case UT_MEAN: + case UT_RANGE: + break; + case UT_LAST: + default: + *value = arg->values[1][0][0]; + break; + } + + POSTGIS_RT_DEBUGF(4, "value, nodata = %f, %d", *value, *nodata); + + + return 1; +} + +static int rtpg_union_mean_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + if (arg == NULL) + return 0; + + if ( + arg->rasters != 2 || + arg->rows != 1 || + arg->columns != 1 + ) { + elog(ERROR, "rtpg_union_mean_callback: Invalid arguments passed to callback"); + return 0; + } + + *value = 0; + *nodata = 1; + + POSTGIS_RT_DEBUGF(4, "rast0: %f %d", arg->values[0][0][0], arg->nodata[0][0][0]); + POSTGIS_RT_DEBUGF(4, "rast1: %f %d", arg->values[1][0][0], arg->nodata[1][0][0]); + + if ( + !arg->nodata[0][0][0] && + FLT_NEQ(arg->values[0][0][0], 0) && + !arg->nodata[1][0][0] + ) { + *value = arg->values[1][0][0] / arg->values[0][0][0]; + *nodata = 0; + } + + POSTGIS_RT_DEBUGF(4, "value, nodata = (%f, %d)", *value, *nodata); + + return 1; +} + +static int rtpg_union_range_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + if (arg == NULL) + return 0; + + if ( + arg->rasters != 2 || + arg->rows != 1 || + arg->columns != 1 + ) { + elog(ERROR, "rtpg_union_range_callback: Invalid arguments passed to callback"); + return 0; + } + + *value = 0; + *nodata = 1; + + POSTGIS_RT_DEBUGF(4, "rast0: %f %d", arg->values[0][0][0], arg->nodata[0][0][0]); + POSTGIS_RT_DEBUGF(4, "rast1: %f %d", arg->values[1][0][0], arg->nodata[1][0][0]); + + if ( + !arg->nodata[0][0][0] && + !arg->nodata[1][0][0] + ) { + *value = arg->values[1][0][0] - arg->values[0][0][0]; + *nodata = 0; + } + + POSTGIS_RT_DEBUGF(4, "value, nodata = (%f, %d)", *value, *nodata); + + return 1; +} + +/* called for ST_Union(raster, unionarg[]) */ +static int rtpg_union_unionarg_process(rtpg_union_arg arg, ArrayType *array) { + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + + HeapTupleHeader tup; + bool isnull; + Datum tupv; + + int i; + int nband = 1; + char *utypename = NULL; + rtpg_union_type utype = UT_LAST; + + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + deconstruct_array( + array, + etype, + typlen, typbyval, typalign, + &e, &nulls, &n + ); + + if (!n) { + elog(ERROR, "rtpg_union_unionarg_process: Invalid argument for unionarg"); + return 0; + } + + /* prep arg */ + arg->numband = n; + arg->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * arg->numband); + if (arg->bandarg == NULL) { + elog(ERROR, "rtpg_union_unionarg_process: Could not allocate memory for band information"); + return 0; + } + + /* process each element */ + for (i = 0; i < n; i++) { + if (nulls[i]) { + arg->numband--; + continue; + } + + POSTGIS_RT_DEBUGF(4, "Processing unionarg at index %d", i); + + /* each element is a tuple */ + tup = (HeapTupleHeader) DatumGetPointer(e[i]); + if (NULL == tup) { + elog(ERROR, "rtpg_union_unionarg_process: Invalid argument for unionarg"); + return 0; + } + + /* first element, bandnum */ + tupv = GetAttributeByName(tup, "nband", &isnull); + if (isnull) { + nband = i + 1; + elog(NOTICE, "First argument (nband) of unionarg is NULL. Assuming nband = %d", nband); + } + else + nband = DatumGetInt32(tupv); + + if (nband < 1) { + elog(ERROR, "rtpg_union_unionarg_process: Band number must be greater than zero (1-based)"); + return 0; + } + + /* second element, uniontype */ + tupv = GetAttributeByName(tup, "uniontype", &isnull); + if (isnull) { + elog(NOTICE, "Second argument (uniontype) of unionarg is NULL. Assuming uniontype = LAST"); + utype = UT_LAST; + } + else { + utypename = text_to_cstring((text *) DatumGetPointer(tupv)); + utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); + } + + arg->bandarg[i].uniontype = utype; + arg->bandarg[i].nband = nband - 1; + arg->bandarg[i].raster = NULL; + + if ( + utype != UT_MEAN && + utype != UT_RANGE + ) { + arg->bandarg[i].numraster = 1; + } + else + arg->bandarg[i].numraster = 2; + } + + if (arg->numband < n) { + arg->bandarg = repalloc(arg->bandarg, sizeof(struct rtpg_union_band_arg_t) * arg->numband); + if (arg->bandarg == NULL) { + elog(ERROR, "rtpg_union_unionarg_process: Could not reallocate memory for band information"); + return 0; + } + } + + return 1; +} + +/* called for ST_Union(raster) */ +static int rtpg_union_noarg(rtpg_union_arg arg, rt_raster raster) { + int numbands; + int i; + + if (rt_raster_is_empty(raster)) + return 1; + + numbands = rt_raster_get_num_bands(raster); + if (numbands <= arg->numband) + return 1; + + /* more bands to process */ + POSTGIS_RT_DEBUG(4, "input raster has more bands, adding more bandargs"); + if (arg->numband) + arg->bandarg = repalloc(arg->bandarg, sizeof(struct rtpg_union_band_arg_t) * numbands); + else + arg->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * numbands); + if (arg->bandarg == NULL) { + elog(ERROR, "rtpg_union_noarg: Could not reallocate memory for band information"); + return 0; + } + + i = arg->numband; + arg->numband = numbands; + for (; i < arg->numband; i++) { + POSTGIS_RT_DEBUGF(4, "Adding bandarg for band at index %d", i); + arg->bandarg[i].uniontype = UT_LAST; + arg->bandarg[i].nband = i; + arg->bandarg[i].numraster = 1; + + arg->bandarg[i].raster = (rt_raster *) palloc(sizeof(rt_raster) * arg->bandarg[i].numraster); + if (arg->bandarg[i].raster == NULL) { + elog(ERROR, "rtpg_union_noarg: Could not allocate memory for working rasters"); + return 0; + } + memset(arg->bandarg[i].raster, 0, sizeof(rt_raster) * arg->bandarg[i].numraster); + + /* add new working rt_raster but only if working raster already exists */ + if (!rt_raster_is_empty(arg->bandarg[0].raster[0])) { + arg->bandarg[i].raster[0] = rt_raster_clone(arg->bandarg[0].raster[0], 0); /* shallow clone */ + if (arg->bandarg[i].raster[0] == NULL) { + elog(ERROR, "rtpg_union_noarg: Could not create working raster"); + return 0; + } + } + } + + return 1; +} + +/* UNION aggregate transition function */ +PG_FUNCTION_INFO_V1(RASTER_union_transfn); +Datum RASTER_union_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext aggcontext; + MemoryContext oldcontext; + rtpg_union_arg iwr = NULL; + int skiparg = 0; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_raster _raster = NULL; + rt_band _band = NULL; + int nband = 1; + int noerr = 1; + int isempty[2] = {0}; + int hasband[2] = {0}; + int nargs = 0; + double _offset[4] = {0.}; + int nbnodata = 0; /* 1 if adding bands */ + + int i = 0; + int j = 0; + int k = 0; + + rt_iterator itrset; + char *utypename = NULL; + rtpg_union_type utype = UT_LAST; + rt_pixtype pixtype = PT_END; + int hasnodata = 1; + double nodataval = 0; + + rt_raster iraster = NULL; + rt_band iband = NULL; + int reuserast = 0; + int y = 0; + uint16_t _dim[2] = {0}; + void *vals = NULL; + uint16_t nvals = 0; + + POSTGIS_RT_DEBUG(3, "Starting..."); + + /* cannot be called directly as this is exclusive aggregate function */ + if (!AggCheckCallContext(fcinfo, &aggcontext)) { + elog(ERROR, "RASTER_union_transfn: Cannot be called in a non-aggregate context"); + PG_RETURN_NULL(); + } + + /* switch to aggcontext */ + oldcontext = MemoryContextSwitchTo(aggcontext); + + if (PG_ARGISNULL(0)) { + POSTGIS_RT_DEBUG(3, "Creating state variable"); + /* allocate container in aggcontext */ + iwr = palloc(sizeof(struct rtpg_union_arg_t)); + if (iwr == NULL) { + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not allocate memory for state variable"); + PG_RETURN_NULL(); + } + + iwr->numband = 0; + iwr->bandarg = NULL; + + skiparg = 0; + } + else { + POSTGIS_RT_DEBUG(3, "State variable already exists"); + iwr = (rtpg_union_arg) PG_GETARG_POINTER(0); + skiparg = 1; + } + + /* raster arg is NOT NULL */ + if (!PG_ARGISNULL(1)) { + /* deserialize raster */ + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + + /* Get raster object */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (raster == NULL) { + + rtpg_union_arg_destroy(iwr); + PG_FREE_IF_COPY(pgraster, 1); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not deserialize raster"); + PG_RETURN_NULL(); + } + } + + /* process additional args if needed */ + nargs = PG_NARGS(); + POSTGIS_RT_DEBUGF(4, "nargs = %d", nargs); + if (nargs > 2) { + POSTGIS_RT_DEBUG(4, "processing additional arguments"); + + /* if more than 2 arguments, determine the type of argument 3 */ + /* band number, UNION type or unionarg */ + if (!PG_ARGISNULL(2)) { + Oid calltype = get_fn_expr_argtype(fcinfo->flinfo, 2); + + switch (calltype) { + /* UNION type */ + case TEXTOID: { + int idx = 0; + int numband = 0; + + POSTGIS_RT_DEBUG(4, "Processing arg 3 as UNION type"); + nbnodata = 1; + + utypename = text_to_cstring(PG_GETARG_TEXT_P(2)); + utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); + POSTGIS_RT_DEBUGF(4, "Union type: %s", utypename); + + POSTGIS_RT_DEBUGF(4, "iwr->numband: %d", iwr->numband); + /* see if we need to append new bands */ + if (raster) { + idx = iwr->numband; + numband = rt_raster_get_num_bands(raster); + POSTGIS_RT_DEBUGF(4, "numband: %d", numband); + + /* only worry about appended bands */ + if (numband > iwr->numband) + iwr->numband = numband; + } + + if (!iwr->numband) + iwr->numband = 1; + POSTGIS_RT_DEBUGF(4, "iwr->numband: %d", iwr->numband); + POSTGIS_RT_DEBUGF(4, "numband, idx: %d, %d", numband, idx); + + /* bandarg set. only possible after the first call to function */ + if (iwr->bandarg) { + /* only reallocate if new bands need to be added */ + if (numband > idx) { + POSTGIS_RT_DEBUG(4, "Reallocating iwr->bandarg"); + iwr->bandarg = repalloc(iwr->bandarg, sizeof(struct rtpg_union_band_arg_t) * iwr->numband); + } + /* prevent initial values step happening below */ + else + idx = iwr->numband; + } + /* bandarg not set, first call to function */ + else { + POSTGIS_RT_DEBUG(4, "Allocating iwr->bandarg"); + iwr->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * iwr->numband); + } + if (iwr->bandarg == NULL) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not allocate memory for band information"); + PG_RETURN_NULL(); + } + + /* set initial values for bands that are "new" */ + for (i = idx; i < iwr->numband; i++) { + iwr->bandarg[i].uniontype = utype; + iwr->bandarg[i].nband = i; + + if ( + utype == UT_MEAN || + utype == UT_RANGE + ) { + iwr->bandarg[i].numraster = 2; + } + else + iwr->bandarg[i].numraster = 1; + iwr->bandarg[i].raster = NULL; + } + + break; + } + /* band number */ + case INT2OID: + case INT4OID: + if (skiparg) + break; + + POSTGIS_RT_DEBUG(4, "Processing arg 3 as band number"); + nband = PG_GETARG_INT32(2); + if (nband < 1) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Band number must be greater than zero (1-based)"); + PG_RETURN_NULL(); + } + + iwr->numband = 1; + iwr->bandarg = palloc(sizeof(struct rtpg_union_band_arg_t) * iwr->numband); + if (iwr->bandarg == NULL) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not allocate memory for band information"); + PG_RETURN_NULL(); + } + + iwr->bandarg[0].uniontype = UT_LAST; + iwr->bandarg[0].nband = nband - 1; + + iwr->bandarg[0].numraster = 1; + iwr->bandarg[0].raster = NULL; + break; + /* only other type allowed is unionarg */ + default: + if (skiparg) + break; + + POSTGIS_RT_DEBUG(4, "Processing arg 3 as unionarg"); + if (!rtpg_union_unionarg_process(iwr, PG_GETARG_ARRAYTYPE_P(2))) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not process unionarg"); + PG_RETURN_NULL(); + } + + break; + } + } + + /* UNION type */ + if (nargs > 3 && !PG_ARGISNULL(3)) { + utypename = text_to_cstring(PG_GETARG_TEXT_P(3)); + utype = rtpg_uniontype_index_from_name(rtpg_strtoupper(utypename)); + iwr->bandarg[0].uniontype = utype; + POSTGIS_RT_DEBUGF(4, "Union type: %s", utypename); + + if ( + utype == UT_MEAN || + utype == UT_RANGE + ) { + iwr->bandarg[0].numraster = 2; + } + } + + /* allocate space for pointers to rt_raster */ + for (i = 0; i < iwr->numband; i++) { + POSTGIS_RT_DEBUGF(4, "iwr->bandarg[%d].raster @ %p", i, iwr->bandarg[i].raster); + + /* no need to allocate */ + if (iwr->bandarg[i].raster != NULL) + continue; + + POSTGIS_RT_DEBUGF(4, "Allocating space for working rasters of band %d", i); + + iwr->bandarg[i].raster = (rt_raster *) palloc(sizeof(rt_raster) * iwr->bandarg[i].numraster); + if (iwr->bandarg[i].raster == NULL) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not allocate memory for working raster(s)"); + PG_RETURN_NULL(); + } + + memset(iwr->bandarg[i].raster, 0, sizeof(rt_raster) * iwr->bandarg[i].numraster); + + /* add new working rt_raster but only if working raster already exists */ + if (i > 0 && !rt_raster_is_empty(iwr->bandarg[0].raster[0])) { + for (j = 0; j < iwr->bandarg[i].numraster; j++) { + iwr->bandarg[i].raster[j] = rt_raster_clone(iwr->bandarg[0].raster[0], 0); /* shallow clone */ + if (iwr->bandarg[i].raster[j] == NULL) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not create working raster"); + PG_RETURN_NULL(); + } + } + } + } + } + /* only raster, no additional args */ + /* only do this if raster isn't empty */ + else { + POSTGIS_RT_DEBUG(4, "no additional args, checking input raster"); + nbnodata = 1; + if (!rtpg_union_noarg(iwr, raster)) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not check and balance number of bands"); + PG_RETURN_NULL(); + } + } + + /* init itrset */ + itrset = palloc(sizeof(struct rt_iterator_t) * 2); + if (itrset == NULL) { + + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not allocate memory for iterator arguments"); + PG_RETURN_NULL(); + } + + /* by band to UNION */ + for (i = 0; i < iwr->numband; i++) { + + /* by raster */ + for (j = 0; j < iwr->bandarg[i].numraster; j++) { + reuserast = 0; + + /* type of union */ + utype = iwr->bandarg[i].uniontype; + + /* raster flags */ + isempty[0] = rt_raster_is_empty(iwr->bandarg[i].raster[j]); + isempty[1] = rt_raster_is_empty(raster); + + if (!isempty[0]) + hasband[0] = rt_raster_has_band(iwr->bandarg[i].raster[j], 0); + if (!isempty[1]) + hasband[1] = rt_raster_has_band(raster, iwr->bandarg[i].nband); + + /* determine pixtype, hasnodata and nodataval */ + _band = NULL; + if (!isempty[0] && hasband[0]) + _band = rt_raster_get_band(iwr->bandarg[i].raster[j], 0); + else if (!isempty[1] && hasband[1]) + _band = rt_raster_get_band(raster, iwr->bandarg[i].nband); + else { + pixtype = PT_64BF; + hasnodata = 1; + nodataval = rt_pixtype_get_min_value(pixtype); + } + if (_band != NULL) { + pixtype = rt_band_get_pixtype(_band); + hasnodata = 1; + if (rt_band_get_hasnodata_flag(_band)) + rt_band_get_nodata(_band, &nodataval); + else + nodataval = rt_band_get_min_value(_band); + } + + /* UT_MEAN and UT_RANGE require two passes */ + /* UT_MEAN: first for UT_COUNT and second for UT_SUM */ + if (iwr->bandarg[i].uniontype == UT_MEAN) { + /* first pass, UT_COUNT */ + if (j < 1) + utype = UT_COUNT; + else + utype = UT_SUM; + } + /* UT_RANGE: first for UT_MIN and second for UT_MAX */ + else if (iwr->bandarg[i].uniontype == UT_RANGE) { + /* first pass, UT_MIN */ + if (j < 1) + utype = UT_MIN; + else + utype = UT_MAX; + } + + /* force band settings for UT_COUNT */ + if (utype == UT_COUNT) { + pixtype = PT_32BUI; + hasnodata = 0; + nodataval = 0; + } + + POSTGIS_RT_DEBUGF(4, "(pixtype, hasnodata, nodataval) = (%s, %d, %f)", rt_pixtype_name(pixtype), hasnodata, nodataval); + + /* set itrset */ + itrset[0].raster = iwr->bandarg[i].raster[j]; + itrset[0].nband = 0; + itrset[1].raster = raster; + itrset[1].nband = iwr->bandarg[i].nband; + + /* allow use NODATA to replace missing bands */ + if (nbnodata) { + itrset[0].nbnodata = 1; + itrset[1].nbnodata = 1; + } + /* missing bands are not ignored */ + else { + itrset[0].nbnodata = 0; + itrset[1].nbnodata = 0; + } + + /* if rasters AND bands are present, use copy approach */ + if (!isempty[0] && !isempty[1] && hasband[0] && hasband[1]) { + POSTGIS_RT_DEBUG(3, "using line method"); + + /* generate empty out raster */ + if (rt_raster_from_two_rasters( + iwr->bandarg[i].raster[j], raster, + ET_UNION, + &iraster, _offset + ) != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not create internal raster"); + PG_RETURN_NULL(); + } + POSTGIS_RT_DEBUGF(4, "_offset = %f, %f, %f, %f", + _offset[0], _offset[1], _offset[2], _offset[3]); + + /* rasters are spatially the same? */ + if ( + rt_raster_get_width(iwr->bandarg[i].raster[j]) == rt_raster_get_width(iraster) && + rt_raster_get_height(iwr->bandarg[i].raster[j]) == rt_raster_get_height(iraster) + ) { + double igt[6] = {0}; + double gt[6] = {0}; + + rt_raster_get_geotransform_matrix(iwr->bandarg[i].raster[j], gt); + rt_raster_get_geotransform_matrix(iraster, igt); + + reuserast = rt_util_same_geotransform_matrix(gt, igt); + } + + /* use internal raster */ + if (!reuserast) { + /* create band of same type */ + if (rt_raster_generate_new_band( + iraster, + pixtype, + nodataval, + hasnodata, nodataval, + 0 + ) == -1) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + rt_raster_destroy(iraster); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not add new band to internal raster"); + PG_RETURN_NULL(); + } + iband = rt_raster_get_band(iraster, 0); + + /* copy working raster to output raster */ + _dim[0] = rt_raster_get_width(iwr->bandarg[i].raster[j]); + _dim[1] = rt_raster_get_height(iwr->bandarg[i].raster[j]); + for (y = 0; y < _dim[1]; y++) { + POSTGIS_RT_DEBUGF(4, "Getting pixel line of working raster at (x, y, length) = (0, %d, %d)", y, _dim[0]); + if (rt_band_get_pixel_line( + _band, + 0, y, + _dim[0], + &vals, &nvals + ) != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + rt_band_destroy(iband); + rt_raster_destroy(iraster); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not get pixel line from band of working raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(4, "Setting pixel line at (x, y, length) = (%d, %d, %d)", (int) _offset[0], (int) _offset[1] + y, nvals); + if (rt_band_set_pixel_line( + iband, + (int) _offset[0], (int) _offset[1] + y, + vals, nvals + ) != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + rt_band_destroy(iband); + rt_raster_destroy(iraster); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not set pixel line to band of internal raster"); + PG_RETURN_NULL(); + } + } + } + else { + rt_raster_destroy(iraster); + iraster = iwr->bandarg[i].raster[j]; + iband = rt_raster_get_band(iraster, 0); + } + + /* run iterator for extent of input raster */ + noerr = rt_raster_iterator( + itrset, 2, + ET_LAST, NULL, + pixtype, + hasnodata, nodataval, + 0, 0, + &utype, + rtpg_union_callback, + &_raster + ); + if (noerr != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (!reuserast) { + rt_band_destroy(iband); + rt_raster_destroy(iraster); + } + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + + /* with iterator raster, copy data to output raster */ + _band = rt_raster_get_band(_raster, 0); + _dim[0] = rt_raster_get_width(_raster); + _dim[1] = rt_raster_get_height(_raster); + for (y = 0; y < _dim[1]; y++) { + POSTGIS_RT_DEBUGF(4, "Getting pixel line of iterator raster at (x, y, length) = (0, %d, %d)", y, _dim[0]); + if (rt_band_get_pixel_line( + _band, + 0, y, + _dim[0], + &vals, &nvals + ) != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (!reuserast) { + rt_band_destroy(iband); + rt_raster_destroy(iraster); + } + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not get pixel line from band of working raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(4, "Setting pixel line at (x, y, length) = (%d, %d, %d)", (int) _offset[2], (int) _offset[3] + y, nvals); + if (rt_band_set_pixel_line( + iband, + (int) _offset[2], (int) _offset[3] + y, + vals, nvals + ) != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (!reuserast) { + rt_band_destroy(iband); + rt_raster_destroy(iraster); + } + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not set pixel line to band of internal raster"); + PG_RETURN_NULL(); + } + } + + /* free _raster */ + rt_band_destroy(_band); + rt_raster_destroy(_raster); + + /* replace working raster with output raster */ + _raster = iraster; + } + else { + POSTGIS_RT_DEBUG(3, "using pixel method"); + + /* pass everything to iterator */ + noerr = rt_raster_iterator( + itrset, 2, + ET_UNION, NULL, + pixtype, + hasnodata, nodataval, + 0, 0, + &utype, + rtpg_union_callback, + &_raster + ); + + if (noerr != ES_NONE) { + + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_union_transfn: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + } + + /* replace working raster */ + if (iwr->bandarg[i].raster[j] != NULL && !reuserast) { + for (k = rt_raster_get_num_bands(iwr->bandarg[i].raster[j]) - 1; k >= 0; k--) + rt_band_destroy(rt_raster_get_band(iwr->bandarg[i].raster[j], k)); + rt_raster_destroy(iwr->bandarg[i].raster[j]); + } + iwr->bandarg[i].raster[j] = _raster; + } + + } + + pfree(itrset); + if (raster != NULL) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 1); + } + + /* switch back to local context */ + MemoryContextSwitchTo(oldcontext); + + POSTGIS_RT_DEBUG(3, "Finished"); + + PG_RETURN_POINTER(iwr); +} + +/* UNION aggregate final function */ +PG_FUNCTION_INFO_V1(RASTER_union_finalfn); +Datum RASTER_union_finalfn(PG_FUNCTION_ARGS) +{ + rtpg_union_arg iwr; + rt_raster _rtn = NULL; + rt_raster _raster = NULL; + rt_pgraster *pgraster = NULL; + + int i = 0; + int j = 0; + rt_iterator itrset = NULL; + rt_band _band = NULL; + int noerr = 1; + int status = 0; + rt_pixtype pixtype = PT_END; + int hasnodata = 0; + double nodataval = 0; + + POSTGIS_RT_DEBUG(3, "Starting..."); + + /* cannot be called directly as this is exclusive aggregate function */ + if (!AggCheckCallContext(fcinfo, NULL)) { + elog(ERROR, "RASTER_union_finalfn: Cannot be called in a non-aggregate context"); + PG_RETURN_NULL(); + } + + /* NULL, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + iwr = (rtpg_union_arg) PG_GETARG_POINTER(0); + + /* init itrset */ + itrset = palloc(sizeof(struct rt_iterator_t) * 2); + if (itrset == NULL) { + rtpg_union_arg_destroy(iwr); + elog(ERROR, "RASTER_union_finalfn: Could not allocate memory for iterator arguments"); + PG_RETURN_NULL(); + } + + for (i = 0; i < iwr->numband; i++) { + if ( + iwr->bandarg[i].uniontype == UT_MEAN || + iwr->bandarg[i].uniontype == UT_RANGE + ) { + /* raster containing the SUM or MAX is at index 1 */ + _band = rt_raster_get_band(iwr->bandarg[i].raster[1], 0); + + pixtype = rt_band_get_pixtype(_band); + hasnodata = rt_band_get_hasnodata_flag(_band); + if (hasnodata) + rt_band_get_nodata(_band, &nodataval); + POSTGIS_RT_DEBUGF(4, "(pixtype, hasnodata, nodataval) = (%s, %d, %f)", rt_pixtype_name(pixtype), hasnodata, nodataval); + + itrset[0].raster = iwr->bandarg[i].raster[0]; + itrset[0].nband = 0; + itrset[1].raster = iwr->bandarg[i].raster[1]; + itrset[1].nband = 0; + + /* pass everything to iterator */ + if (iwr->bandarg[i].uniontype == UT_MEAN) { + noerr = rt_raster_iterator( + itrset, 2, + ET_UNION, NULL, + pixtype, + hasnodata, nodataval, + 0, 0, + NULL, + rtpg_union_mean_callback, + &_raster + ); + } + else if (iwr->bandarg[i].uniontype == UT_RANGE) { + noerr = rt_raster_iterator( + itrset, 2, + ET_UNION, NULL, + pixtype, + hasnodata, nodataval, + 0, 0, + NULL, + rtpg_union_range_callback, + &_raster + ); + } + + if (noerr != ES_NONE) { + pfree(itrset); + rtpg_union_arg_destroy(iwr); + if (_rtn != NULL) + rt_raster_destroy(_rtn); + elog(ERROR, "RASTER_union_finalfn: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + } + else + _raster = iwr->bandarg[i].raster[0]; + + /* first band, _rtn doesn't exist */ + if (i < 1) { + uint32_t bandNums[1] = {0}; + _rtn = rt_raster_from_band(_raster, bandNums, 1); + status = (_rtn == NULL) ? -1 : 0; + } + else + status = rt_raster_copy_band(_rtn, _raster, 0, i); + + POSTGIS_RT_DEBUG(4, "destroying source rasters"); + + /* destroy source rasters */ + if ( + iwr->bandarg[i].uniontype == UT_MEAN || + iwr->bandarg[i].uniontype == UT_RANGE + ) { + rt_raster_destroy(_raster); + } + + for (j = 0; j < iwr->bandarg[i].numraster; j++) { + if (iwr->bandarg[i].raster[j] == NULL) + continue; + rt_raster_destroy(iwr->bandarg[i].raster[j]); + iwr->bandarg[i].raster[j] = NULL; + } + + if (status < 0) { + rtpg_union_arg_destroy(iwr); + rt_raster_destroy(_rtn); + elog(ERROR, "RASTER_union_finalfn: Could not add band to final raster"); + PG_RETURN_NULL(); + } + } + + /* cleanup */ + pfree(itrset); + rtpg_union_arg_destroy(iwr); + + pgraster = rt_raster_serialize(_rtn); + rt_raster_destroy(_rtn); + + POSTGIS_RT_DEBUG(3, "Finished"); + + if (!pgraster) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, pgraster->size); + PG_RETURN_POINTER(pgraster); +} + +/* ---------------------------------------------------------------- */ +/* Clip raster with geometry */ +/* ---------------------------------------------------------------- */ + +typedef struct rtpg_clip_band_t *rtpg_clip_band; +struct rtpg_clip_band_t { + int nband; /* band index */ + int hasnodata; /* is there a user-specified NODATA? */ + double nodataval; /* user-specified NODATA */ +}; + +typedef struct rtpg_clip_arg_t *rtpg_clip_arg; +struct rtpg_clip_arg_t { + rt_extenttype extenttype; + rt_raster raster; + rt_raster mask; + + int numbands; /* number of bandargs */ + rtpg_clip_band band; +}; + +static rtpg_clip_arg rtpg_clip_arg_init() { + rtpg_clip_arg arg = NULL; + + arg = palloc(sizeof(struct rtpg_clip_arg_t)); + if (arg == NULL) { + elog(ERROR, "rtpg_clip_arg_init: Could not allocate memory for function arguments"); + return NULL; + } + + arg->extenttype = ET_INTERSECTION; + arg->raster = NULL; + arg->mask = NULL; + arg->numbands = 0; + arg->band = NULL; + + return arg; +} + +static void rtpg_clip_arg_destroy(rtpg_clip_arg arg) { + if (arg->band != NULL) + pfree(arg->band); + + if (arg->raster != NULL) + rt_raster_destroy(arg->raster); + if (arg->mask != NULL) + rt_raster_destroy(arg->mask); + + pfree(arg); +} + +static int rtpg_clip_callback( + rt_iterator_arg arg, void *userarg, + double *value, int *nodata +) { + *value = 0; + *nodata = 0; + + /* either is NODATA, output is NODATA */ + if (arg->nodata[0][0][0] || arg->nodata[1][0][0]) + *nodata = 1; + /* set to value */ + else + *value = arg->values[0][0][0]; + + return 1; +} + +PG_FUNCTION_INFO_V1(RASTER_clip); +Datum RASTER_clip(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + LWGEOM *rastgeom = NULL; + double gt[6] = {0}; + int srid = SRID_UNKNOWN; + + rt_pgraster *pgrtn = NULL; + rt_raster rtn = NULL; + + GSERIALIZED *gser = NULL; + LWGEOM *geom = NULL; + unsigned char *wkb = NULL; + size_t wkb_len; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + + int16 typlen; + bool typbyval; + char typalign; + + int i = 0; + int j = 0; + int k = 0; + rtpg_clip_arg arg = NULL; + LWGEOM *tmpgeom = NULL; + rt_iterator itrset; + + rt_raster _raster = NULL; + rt_band band = NULL; + rt_pixtype pixtype; + int hasnodata; + double nodataval; + int noerr = 0; + + POSTGIS_RT_DEBUG(3, "Starting..."); + + /* raster or geometry is NULL, return NULL */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(2)) + PG_RETURN_NULL(); + + /* init arg */ + arg = rtpg_clip_arg_init(); + if (arg == NULL) { + elog(ERROR, "RASTER_clip: Could not initialize argument structure"); + PG_RETURN_NULL(); + } + + /* raster (0) */ + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* Get raster object */ + arg->raster = rt_raster_deserialize(pgraster, FALSE); + if (arg->raster == NULL) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* raster is empty, return empty raster */ + if (rt_raster_is_empty(arg->raster)) { + elog(NOTICE, "Input raster is empty. Returning empty raster"); + + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + + rtn = rt_raster_new(0, 0); + if (rtn == NULL) { + elog(ERROR, "RASTER_clip: Could not create empty raster"); + PG_RETURN_NULL(); + } + + pgrtn = rt_raster_serialize(rtn); + rt_raster_destroy(rtn); + if (NULL == pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* metadata */ + rt_raster_get_geotransform_matrix(arg->raster, gt); + srid = clamp_srid(rt_raster_get_srid(arg->raster)); + + /* geometry (2) */ + gser = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(2)); + geom = lwgeom_from_gserialized(gser); + + /* Get a 2D version of the geometry if necessary */ + if (lwgeom_ndims(geom) > 2) { + LWGEOM *geom2d = lwgeom_force_2d(geom); + lwgeom_free(geom); + geom = geom2d; + } + + /* check that SRIDs match */ + if (srid != clamp_srid(gserialized_get_srid(gser))) { + elog(NOTICE, "Geometry provided does not have the same SRID as the raster. Returning NULL"); + + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 2); + + PG_RETURN_NULL(); + } + + /* crop (4) */ + if (!PG_ARGISNULL(4) && !PG_GETARG_BOOL(4)) + arg->extenttype = ET_FIRST; + + /* get intersection geometry of input raster and input geometry */ + if (rt_raster_get_convex_hull(arg->raster, &rastgeom) != ES_NONE) { + + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 2); + + elog(ERROR, "RASTER_clip: Could not get convex hull of raster"); + PG_RETURN_NULL(); + } + + tmpgeom = lwgeom_intersection(rastgeom, geom); + lwgeom_free(rastgeom); + lwgeom_free(geom); + PG_FREE_IF_COPY(gser, 2); + geom = tmpgeom; + + /* intersection is empty AND extent type is INTERSECTION, return empty */ + if (lwgeom_is_empty(geom) && arg->extenttype == ET_INTERSECTION) { + elog(NOTICE, "The input raster and input geometry do not intersect. Returning empty raster"); + + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + + rtn = rt_raster_new(0, 0); + if (rtn == NULL) { + elog(ERROR, "RASTER_clip: Could not create empty raster"); + PG_RETURN_NULL(); + } + + pgrtn = rt_raster_serialize(rtn); + rt_raster_destroy(rtn); + if (NULL == pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* nband (1) */ + 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_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + elog(ERROR, "RASTER_clip: Invalid data type for band indexes"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array( + array, etype, + typlen, typbyval, typalign, + &e, &nulls, &(arg->numbands) + ); + + arg->band = palloc(sizeof(struct rtpg_clip_band_t) * arg->numbands); + if (arg->band == NULL) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + elog(ERROR, "RASTER_clip: Could not allocate memory for band arguments"); + PG_RETURN_NULL(); + } + + for (i = 0, j = 0; i < arg->numbands; i++) { + if (nulls[i]) continue; + + switch (etype) { + case INT2OID: + arg->band[j].nband = DatumGetInt16(e[i]) - 1; + break; + case INT4OID: + arg->band[j].nband = DatumGetInt32(e[i]) - 1; + break; + } + + j++; + } + + if (j < arg->numbands) { + arg->band = repalloc(arg->band, sizeof(struct rtpg_clip_band_t) * j); + if (arg->band == NULL) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + elog(ERROR, "RASTER_clip: Could not reallocate memory for band arguments"); + PG_RETURN_NULL(); + } + + arg->numbands = j; + } + + /* validate band */ + for (i = 0; i < arg->numbands; i++) { + if (!rt_raster_has_band(arg->raster, arg->band[i].nband)) { + elog(NOTICE, "Band at index %d not found in raster", arg->band[i].nband + 1); + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + PG_RETURN_NULL(); + } + + arg->band[i].hasnodata = 0; + arg->band[i].nodataval = 0; + } + } + else { + arg->numbands = rt_raster_get_num_bands(arg->raster); + + /* raster may have no bands */ + if (arg->numbands) { + arg->band = palloc(sizeof(struct rtpg_clip_band_t) * arg->numbands); + if (arg->band == NULL) { + + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + + elog(ERROR, "RASTER_clip: Could not allocate memory for band arguments"); + PG_RETURN_NULL(); + } + + for (i = 0; i < arg->numbands; i++) { + arg->band[i].nband = i; + arg->band[i].hasnodata = 0; + arg->band[i].nodataval = 0; + } + } + } + + /* nodataval (3) */ + if (!PG_ARGISNULL(3)) { + array = PG_GETARG_ARRAYTYPE_P(3); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + lwgeom_free(geom); + elog(ERROR, "RASTER_clip: Invalid data type for NODATA values"); + PG_RETURN_NULL(); + break; + } + + deconstruct_array( + array, etype, + typlen, typbyval, typalign, + &e, &nulls, &k + ); + + /* it doesn't matter if there are more nodataval */ + for (i = 0, j = 0; i < arg->numbands; i++, j++) { + /* cap j to the last nodataval element */ + if (j >= k) + j = k - 1; + + if (nulls[j]) + continue; + + arg->band[i].hasnodata = 1; + switch (etype) { + case FLOAT4OID: + arg->band[i].nodataval = DatumGetFloat4(e[j]); + break; + case FLOAT8OID: + arg->band[i].nodataval = DatumGetFloat8(e[j]); + break; + } + } + } + + /* get wkb of geometry */ + POSTGIS_RT_DEBUG(3, "getting wkb of geometry"); + wkb = lwgeom_to_wkb(geom, WKB_SFSQL, &wkb_len); + lwgeom_free(geom); + + /* rasterize geometry */ + arg->mask = rt_raster_gdal_rasterize( + wkb, wkb_len, + 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 (arg->mask == NULL) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not rasterize intersection geometry"); + PG_RETURN_NULL(); + } + + /* set SRID */ + rt_raster_set_srid(arg->mask, srid); + + /* run iterator */ + + /* init itrset */ + itrset = palloc(sizeof(struct rt_iterator_t) * 2); + if (itrset == NULL) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not allocate memory for iterator arguments"); + PG_RETURN_NULL(); + } + + /* one band at a time */ + for (i = 0; i < arg->numbands; i++) { + POSTGIS_RT_DEBUGF(4, "band arg %d (nband, hasnodata, nodataval) = (%d, %d, %f)", + i, arg->band[i].nband, arg->band[i].hasnodata, arg->band[i].nodataval); + + band = rt_raster_get_band(arg->raster, arg->band[i].nband); + + /* band metadata */ + pixtype = rt_band_get_pixtype(band); + + if (arg->band[i].hasnodata) { + hasnodata = 1; + nodataval = arg->band[i].nodataval; + } + else if (rt_band_get_hasnodata_flag(band)) { + hasnodata = 1; + rt_band_get_nodata(band, &nodataval); + } + else { + hasnodata = 0; + nodataval = rt_band_get_min_value(band); + } + + /* band is NODATA, create NODATA band and continue */ + if (rt_band_get_isnodata_flag(band)) { + /* create raster */ + if (rtn == NULL) { + noerr = rt_raster_from_two_rasters(arg->raster, arg->mask, arg->extenttype, &rtn, NULL); + if (noerr != ES_NONE) { + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not create output raster"); + PG_RETURN_NULL(); + } + } + + /* create NODATA band */ + if (rt_raster_generate_new_band(rtn, pixtype, nodataval, hasnodata, nodataval, i) < 0) { + rt_raster_destroy(rtn); + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not add NODATA band to output raster"); + PG_RETURN_NULL(); + } + + continue; + } + + /* raster */ + itrset[0].raster = arg->raster; + itrset[0].nband = arg->band[i].nband; + itrset[0].nbnodata = 1; + + /* mask */ + itrset[1].raster = arg->mask; + itrset[1].nband = 0; + itrset[1].nbnodata = 1; + + /* pass to iterator */ + noerr = rt_raster_iterator( + itrset, 2, + arg->extenttype, NULL, + pixtype, + hasnodata, nodataval, + 0, 0, + NULL, + rtpg_clip_callback, + &_raster + ); + + if (noerr != ES_NONE) { + pfree(itrset); + rtpg_clip_arg_destroy(arg); + if (rtn != NULL) rt_raster_destroy(rtn); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not run raster iterator function"); + PG_RETURN_NULL(); + } + + /* new raster */ + if (rtn == NULL) + rtn = _raster; + /* copy band */ + else { + band = rt_raster_get_band(_raster, 0); + if (band == NULL) { + pfree(itrset); + rtpg_clip_arg_destroy(arg); + rt_raster_destroy(_raster); + rt_raster_destroy(rtn); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not get band from working raster"); + PG_RETURN_NULL(); + } + + if (rt_raster_add_band(rtn, band, i) < 0) { + pfree(itrset); + rtpg_clip_arg_destroy(arg); + rt_raster_destroy(_raster); + rt_raster_destroy(rtn); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_clip: Could not add new band to output raster"); + PG_RETURN_NULL(); + } + + rt_raster_destroy(_raster); + } + } + + pfree(itrset); + rtpg_clip_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(rtn); + rt_raster_destroy(rtn); + + POSTGIS_RT_DEBUG(3, "Finished"); + + if (!pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/** + * Reclassify the specified bands of the raster + */ +PG_FUNCTION_INFO_V1(RASTER_reclass); +Datum RASTER_reclass(PG_FUNCTION_ARGS) { + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + rt_band newband = NULL; + uint32_t numBands = 0; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + int n = 0; + + int i = 0; + int j = 0; + int k = 0; + + int a = 0; + int b = 0; + int c = 0; + + rt_reclassexpr *exprset = NULL; + HeapTupleHeader tup; + bool isnull; + Datum tupv; + uint32_t nband = 0; + char *expr = NULL; + text *exprtext = NULL; + double val = 0; + char *junk = NULL; + int inc_val = 0; + int exc_val = 0; + char *pixeltype = NULL; + text *pixeltypetext = NULL; + rt_pixtype pixtype = PT_END; + double nodataval = 0; + bool hasnodata = FALSE; + + char **comma_set = NULL; + int comma_n = 0; + char **colon_set = NULL; + int colon_n = 0; + char **dash_set = NULL; + int dash_n = 0; + + POSTGIS_RT_DEBUG(3, "RASTER_reclass: Starting"); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_reclass: Could not deserialize raster"); + PG_RETURN_NULL(); + } + numBands = rt_raster_get_num_bands(raster); + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: %d possible bands to be reclassified", n); + + /* process set of reclassarg */ + POSTGIS_RT_DEBUG(3, "RASTER_reclass: Processing Arg 1 (reclassargset)"); + array = PG_GETARG_ARRAYTYPE_P(1); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + if (!n) { + elog(NOTICE, "Invalid argument for reclassargset. Returning original 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); + } + + /* + process each element of reclassarg + each element is the index of the band to process, the set + of reclass ranges and the output band's pixeltype + */ + for (i = 0; i < n; i++) { + if (nulls[i]) continue; + + /* each element is a tuple */ + tup = (HeapTupleHeader) DatumGetPointer(e[i]); + if (NULL == tup) { + elog(NOTICE, "Invalid argument for reclassargset. Returning original 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); + } + + /* band index (1-based) */ + tupv = GetAttributeByName(tup, "nband", &isnull); + if (isnull) { + elog(NOTICE, "Invalid argument for reclassargset. Missing value of nband for reclassarg of index %d . Returning original raster", i); + + 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); + } + nband = DatumGetInt32(tupv); + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: expression for band %d", nband); + + /* valid band index? */ + if (nband < 1 || nband > numBands) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid band index (must use 1-based) for reclassarg of index %d . Returning original raster", i); + + 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); + } + + /* reclass expr */ + tupv = GetAttributeByName(tup, "reclassexpr", &isnull); + if (isnull) { + elog(NOTICE, "Invalid argument for reclassargset. Missing value of reclassexpr for reclassarg of index %d . Returning original raster", i); + + 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); + } + exprtext = (text *) DatumGetPointer(tupv); + if (NULL == exprtext) { + elog(NOTICE, "Invalid argument for reclassargset. Missing value of reclassexpr for reclassarg of index %d . Returning original raster", i); + + 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); + } + expr = text_to_cstring(exprtext); + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: expr (raw) %s", expr); + expr = rtpg_removespaces(expr); + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: expr (clean) %s", expr); + + /* split string to its components */ + /* comma (,) separating rangesets */ + comma_set = rtpg_strsplit(expr, ",", &comma_n); + if (comma_n < 1) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); + + 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 of reclass expressions */ + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: %d possible expressions", comma_n); + exprset = palloc(comma_n * sizeof(rt_reclassexpr)); + + for (a = 0, j = 0; a < comma_n; a++) { + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: map %s", comma_set[a]); + + /* colon (:) separating range "src" and "dst" */ + colon_set = rtpg_strsplit(comma_set[a], ":", &colon_n); + if (colon_n != 2) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + 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); + } + + /* allocate mem for reclass expression */ + exprset[j] = palloc(sizeof(struct rt_reclassexpr_t)); + + for (b = 0; b < colon_n; b++) { + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: range %s", colon_set[b]); + + /* dash (-) separating "min" and "max" */ + dash_set = rtpg_strsplit(colon_set[b], "-", &dash_n); + if (dash_n < 1 || dash_n > 3) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + 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); + } + + for (c = 0; c < dash_n; c++) { + /* need to handle: (-9999-100 -> "(", "9999", "100" */ + if ( + c < 1 && + strlen(dash_set[c]) == 1 && ( + strchr(dash_set[c], '(') != NULL || + strchr(dash_set[c], '[') != NULL || + strchr(dash_set[c], ')') != NULL || + strchr(dash_set[c], ']') != NULL + ) + ) { + junk = palloc(sizeof(char) * (strlen(dash_set[c + 1]) + 2)); + if (NULL == junk) { + for (k = 0; k <= j; k++) pfree(exprset[k]); + pfree(exprset); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + elog(ERROR, "RASTER_reclass: Could not allocate memory"); + PG_RETURN_NULL(); + } + + sprintf(junk, "%s%s", dash_set[c], dash_set[c + 1]); + c++; + dash_set[c] = repalloc(dash_set[c], sizeof(char) * (strlen(junk) + 1)); + strcpy(dash_set[c], junk); + pfree(junk); + + /* rebuild dash_set */ + for (k = 1; k < dash_n; k++) { + dash_set[k - 1] = repalloc(dash_set[k - 1], (strlen(dash_set[k]) + 1) * sizeof(char)); + strcpy(dash_set[k - 1], dash_set[k]); + } + dash_n--; + c--; + pfree(dash_set[dash_n]); + dash_set = repalloc(dash_set, sizeof(char *) * dash_n); + } + + /* there shouldn't be more than two in dash_n */ + if (c < 1 && dash_n > 2) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + 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); + } + + /* check interval flags */ + exc_val = 0; + inc_val = 1; + /* range */ + if (dash_n != 1) { + /* min */ + if (c < 1) { + if ( + strchr(dash_set[c], ')') != NULL || + strchr(dash_set[c], ']') != NULL + ) { + exc_val = 1; + inc_val = 1; + } + else if (strchr(dash_set[c], '(') != NULL){ + inc_val = 0; + } + else { + inc_val = 1; + } + } + /* max */ + else { + if ( + strrchr(dash_set[c], '(') != NULL || + strrchr(dash_set[c], '[') != NULL + ) { + exc_val = 1; + inc_val = 0; + } + else if (strrchr(dash_set[c], ']') != NULL) { + inc_val = 1; + } + else { + inc_val = 0; + } + } + } + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: exc_val %d inc_val %d", exc_val, inc_val); + + /* remove interval flags */ + dash_set[c] = rtpg_chartrim(dash_set[c], "()[]"); + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (char) %s", dash_set[c]); + + /* value from string to double */ + errno = 0; + val = strtod(dash_set[c], &junk); + if (errno != 0 || dash_set[c] == junk) { + elog(NOTICE, "Invalid argument for reclassargset. Invalid expression of reclassexpr for reclassarg of index %d . Returning original raster", i); + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + 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); + } + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (double) %f", val); + + /* strsplit removes dash (a.k.a. negative sign), compare now to restore */ + junk = strstr(colon_set[b], dash_set[c]); + /* not beginning of string */ + if (junk != colon_set[b]) { + /* prior is a dash */ + if (*(junk - 1) == '-') { + /* prior is beginning of string or prior - 1 char is dash, negative number */ + if ( + ((junk - 1) == colon_set[b]) || + (*(junk - 2) == '-') || + (*(junk - 2) == '[') || + (*(junk - 2) == '(') + ) { + val *= -1.; + } + } + } + POSTGIS_RT_DEBUGF(5, "RASTER_reclass: min/max (double) %f", val); + + /* src */ + if (b < 1) { + /* singular value */ + if (dash_n == 1) { + exprset[j]->src.exc_min = exprset[j]->src.exc_max = exc_val; + exprset[j]->src.inc_min = exprset[j]->src.inc_max = inc_val; + exprset[j]->src.min = exprset[j]->src.max = val; + } + /* min */ + else if (c < 1) { + exprset[j]->src.exc_min = exc_val; + exprset[j]->src.inc_min = inc_val; + exprset[j]->src.min = val; + } + /* max */ + else { + exprset[j]->src.exc_max = exc_val; + exprset[j]->src.inc_max = inc_val; + exprset[j]->src.max = val; + } + } + /* dst */ + else { + /* singular value */ + if (dash_n == 1) + exprset[j]->dst.min = exprset[j]->dst.max = val; + /* min */ + else if (c < 1) + exprset[j]->dst.min = val; + /* max */ + else + exprset[j]->dst.max = val; + } + } + pfree(dash_set); + } + pfree(colon_set); + + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: or: %f - %f nr: %f - %f" + , exprset[j]->src.min + , exprset[j]->src.max + , exprset[j]->dst.min + , exprset[j]->dst.max + ); + j++; + } + pfree(comma_set); + + /* pixel type */ + tupv = GetAttributeByName(tup, "pixeltype", &isnull); + if (isnull) { + elog(NOTICE, "Invalid argument for reclassargset. Missing value of pixeltype for reclassarg of index %d . Returning original raster", i); + + 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); + } + pixeltypetext = (text *) DatumGetPointer(tupv); + if (NULL == pixeltypetext) { + elog(NOTICE, "Invalid argument for reclassargset. Missing value of pixeltype for reclassarg of index %d . Returning original raster", i); + + 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); + } + pixeltype = text_to_cstring(pixeltypetext); + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: pixeltype %s", pixeltype); + pixtype = rt_pixtype_index_from_name(pixeltype); + + /* nodata */ + tupv = GetAttributeByName(tup, "nodataval", &isnull); + if (isnull) { + nodataval = 0; + hasnodata = FALSE; + } + else { + nodataval = DatumGetFloat8(tupv); + hasnodata = TRUE; + } + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: nodataval %f", nodataval); + POSTGIS_RT_DEBUGF(3, "RASTER_reclass: hasnodata %d", hasnodata); + + /* do reclass */ + band = rt_raster_get_band(raster, nband - 1); + if (!band) { + elog(NOTICE, "Could not find raster band of index %d. Returning original raster", nband); + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + 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); + } + newband = rt_band_reclass(band, pixtype, hasnodata, nodataval, exprset, j); + if (!newband) { + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + elog(ERROR, "RASTER_reclass: Could not reclassify raster band of index %d", nband); + PG_RETURN_NULL(); + } + + /* replace old band with new band */ + if (rt_raster_replace_band(raster, newband, nband - 1) == NULL) { + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + + rt_band_destroy(newband); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + elog(ERROR, "RASTER_reclass: Could not replace raster band of index %d with reclassified band", nband); + PG_RETURN_NULL(); + } + + /* old band is in the variable band */ + rt_band_destroy(band); + + /* free exprset */ + for (k = 0; k < j; k++) pfree(exprset[k]); + pfree(exprset); + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (!pgrtn) + PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "RASTER_reclass: Finished"); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/* ---------------------------------------------------------------- */ +/* apply colormap to specified band of a raster */ +/* ---------------------------------------------------------------- */ + +typedef struct rtpg_colormap_arg_t *rtpg_colormap_arg; +struct rtpg_colormap_arg_t { + rt_raster raster; + int nband; /* 1-based */ + rt_band band; + rt_bandstats bandstats; + + rt_colormap colormap; + int nodataentry; + + char **entry; + int nentry; + char **element; + int nelement; +}; + +static rtpg_colormap_arg +rtpg_colormap_arg_init() { + rtpg_colormap_arg arg = NULL; + + arg = palloc(sizeof(struct rtpg_colormap_arg_t)); + if (arg == NULL) { + elog(ERROR, "rtpg_colormap_arg: Could not allocate memory for function arguments"); + return NULL; + } + + arg->raster = NULL; + arg->nband = 1; + arg->band = NULL; + arg->bandstats = NULL; + + arg->colormap = palloc(sizeof(struct rt_colormap_t)); + if (arg->colormap == NULL) { + elog(ERROR, "rtpg_colormap_arg: Could not allocate memory for function arguments"); + return NULL; + } + arg->colormap->nentry = 0; + arg->colormap->entry = NULL; + arg->colormap->ncolor = 4; /* assume RGBA */ + arg->colormap->method = CM_INTERPOLATE; + arg->nodataentry = -1; + + arg->entry = NULL; + arg->nentry = 0; + arg->element = NULL; + arg->nelement = 0; + + return arg; +} + +static void +rtpg_colormap_arg_destroy(rtpg_colormap_arg arg) { + int i = 0; + if (arg->raster != NULL) + rt_raster_destroy(arg->raster); + + if (arg->bandstats != NULL) + pfree(arg->bandstats); + + if (arg->colormap != NULL) { + if (arg->colormap->entry != NULL) + pfree(arg->colormap->entry); + pfree(arg->colormap); + } + + if (arg->nentry) { + for (i = 0; i < arg->nentry; i++) { + if (arg->entry[i] != NULL) + pfree(arg->entry[i]); + } + pfree(arg->entry); + } + + if (arg->nelement) { + for (i = 0; i < arg->nelement; i++) + pfree(arg->element[i]); + pfree(arg->element); + } + + pfree(arg); + arg = NULL; +} + +PG_FUNCTION_INFO_V1(RASTER_colorMap); +Datum RASTER_colorMap(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rtpg_colormap_arg arg = NULL; + char *junk = NULL; + rt_raster raster = NULL; + + POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Starting"); + + /* pgraster is NULL, return NULL */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + /* init arg */ + arg = rtpg_colormap_arg_init(); + if (arg == NULL) { + elog(ERROR, "RASTER_colorMap: Could not initialize argument structure"); + PG_RETURN_NULL(); + } + + /* raster (0) */ + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + + /* raster object */ + arg->raster = rt_raster_deserialize(pgraster, FALSE); + if (!arg->raster) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* nband (1) */ + if (!PG_ARGISNULL(1)) + arg->nband = PG_GETARG_INT32(1); + POSTGIS_RT_DEBUGF(4, "nband = %d", arg->nband); + + /* check that band works */ + if (!rt_raster_has_band(arg->raster, arg->nband - 1)) { + elog(NOTICE, "Raster does not have band at index %d. Returning empty raster", arg->nband); + + raster = rt_raster_clone(arg->raster, 0); + if (raster == NULL) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not create empty raster"); + PG_RETURN_NULL(); + } + + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (pgraster == NULL) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size); + PG_RETURN_POINTER(pgraster); + } + + /* get band */ + arg->band = rt_raster_get_band(arg->raster, arg->nband - 1); + if (arg->band == NULL) { + int nband = arg->nband; + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not get band at index %d", nband); + PG_RETURN_NULL(); + } + + /* method (3) */ + if (!PG_ARGISNULL(3)) { + char *method = NULL; + char *tmp = text_to_cstring(PG_GETARG_TEXT_P(3)); + POSTGIS_RT_DEBUGF(4, "raw method = %s", tmp); + + method = rtpg_trim(tmp); + pfree(tmp); + method = rtpg_strtoupper(method); + + if (strcmp(method, "INTERPOLATE") == 0) + arg->colormap->method = CM_INTERPOLATE; + else if (strcmp(method, "EXACT") == 0) + arg->colormap->method = CM_EXACT; + else if (strcmp(method, "NEAREST") == 0) + arg->colormap->method = CM_NEAREST; + else { + elog(NOTICE, "Unknown value provided for method. Defaulting to INTERPOLATE"); + arg->colormap->method = CM_INTERPOLATE; + } + } + /* default to INTERPOLATE */ + else + arg->colormap->method = CM_INTERPOLATE; + POSTGIS_RT_DEBUGF(4, "method = %d", arg->colormap->method); + + /* colormap (2) */ + if (PG_ARGISNULL(2)) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Value must be provided for colormap"); + PG_RETURN_NULL(); + } + else { + char *tmp = NULL; + char *colormap = text_to_cstring(PG_GETARG_TEXT_P(2)); + char *_entry; + char *_element; + int i = 0; + int j = 0; + + POSTGIS_RT_DEBUGF(4, "colormap = %s", colormap); + + /* empty string */ + if (!strlen(colormap)) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Value must be provided for colormap"); + PG_RETURN_NULL(); + } + + arg->entry = rtpg_strsplit(colormap, "\n", &(arg->nentry)); + pfree(colormap); + if (arg->nentry < 1) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not process the value provided for colormap"); + PG_RETURN_NULL(); + } + + /* allocate the max # of colormap entries */ + arg->colormap->entry = palloc(sizeof(struct rt_colormap_entry_t) * arg->nentry); + if (arg->colormap->entry == NULL) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not allocate memory for colormap entries"); + PG_RETURN_NULL(); + } + memset(arg->colormap->entry, 0, sizeof(struct rt_colormap_entry_t) * arg->nentry); + + /* each entry */ + for (i = 0; i < arg->nentry; i++) { + /* substitute space for other delimiters */ + tmp = rtpg_strreplace(arg->entry[i], ":", " ", NULL); + _entry = rtpg_strreplace(tmp, ",", " ", NULL); + pfree(tmp); + tmp = rtpg_strreplace(_entry, "\t", " ", NULL); + pfree(_entry); + _entry = rtpg_trim(tmp); + pfree(tmp); + + POSTGIS_RT_DEBUGF(4, "Processing entry[%d] = %s", i, arg->entry[i]); + POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d] = %s", i, _entry); + + /* empty entry, continue */ + if (!strlen(_entry)) { + POSTGIS_RT_DEBUGF(3, "Skipping empty entry[%d]", i); + pfree(_entry); + continue; + } + + arg->element = rtpg_strsplit(_entry, " ", &(arg->nelement)); + pfree(_entry); + if (arg->nelement < 2) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not process colormap entry %d", i + 1); + PG_RETURN_NULL(); + } + else if (arg->nelement > 5) { + elog(NOTICE, "More than five elements in colormap entry %d. Using at most five elements", i + 1); + arg->nelement = 5; + } + + /* smallest # of colors */ + if ((arg->nelement - 1) < arg->colormap->ncolor) + arg->colormap->ncolor = arg->nelement - 1; + + /* each element of entry */ + for (j = 0; j < arg->nelement; j++) { + + _element = rtpg_trim(arg->element[j]); + _element = rtpg_strtoupper(_element); + POSTGIS_RT_DEBUGF(4, "Processing entry[%d][%d] = %s", i, j, arg->element[j]); + POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d][%d] = %s", i, j, _element); + + /* first element is ALWAYS a band value, percentage OR "nv" string */ + if (j == 0) { + char *percent = NULL; + + /* NODATA */ + if ( + strcmp(_element, "NV") == 0 || + strcmp(_element, "NULL") == 0 || + strcmp(_element, "NODATA") == 0 + ) { + POSTGIS_RT_DEBUG(4, "Processing NODATA string"); + + if (arg->nodataentry > -1) { + elog(NOTICE, "More than one NODATA entry found. Using only the first one"); + } + else { + arg->colormap->entry[arg->colormap->nentry].isnodata = 1; + /* no need to set value as value comes from band's NODATA */ + arg->colormap->entry[arg->colormap->nentry].value = 0; + } + } + /* percent value */ + else if ((percent = strchr(_element, '%')) != NULL) { + double value; + POSTGIS_RT_DEBUG(4, "Processing percent string"); + + /* get the band stats */ + if (arg->bandstats == NULL) { + POSTGIS_RT_DEBUG(4, "Getting band stats"); + + arg->bandstats = rt_band_get_summary_stats(arg->band, 1, 1, 0, NULL, NULL, NULL); + if (arg->bandstats == NULL) { + pfree(_element); + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not get band's summary stats to process percentages"); + PG_RETURN_NULL(); + } + } + + /* get the string before the percent char */ + tmp = palloc(sizeof(char) * (percent - _element + 1)); + if (tmp == NULL) { + pfree(_element); + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not allocate memory for value of percentage"); + PG_RETURN_NULL(); + } + + memcpy(tmp, _element, percent - _element); + tmp[percent - _element] = '\0'; + POSTGIS_RT_DEBUGF(4, "Percent value = %s", tmp); + + /* get percentage value */ + errno = 0; + value = strtod(tmp, NULL); + pfree(tmp); + if (errno != 0 || _element == junk) { + pfree(_element); + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not process percent string to value"); + PG_RETURN_NULL(); + } + + /* check percentage */ + if (value < 0.) { + elog(NOTICE, "Percentage values cannot be less than zero. Defaulting to zero"); + value = 0.; + } + else if (value > 100.) { + elog(NOTICE, "Percentage values cannot be greater than 100. Defaulting to 100"); + value = 100.; + } + + /* get the true pixel value */ + /* TODO: should the percentage be quantile based? */ + arg->colormap->entry[arg->colormap->nentry].value = ((value / 100.) * (arg->bandstats->max - arg->bandstats->min)) + arg->bandstats->min; + } + /* straight value */ + else { + errno = 0; + arg->colormap->entry[arg->colormap->nentry].value = strtod(_element, &junk); + if (errno != 0 || _element == junk) { + pfree(_element); + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not process string to value"); + PG_RETURN_NULL(); + } + } + + } + /* RGB values (0 - 255) */ + else { + int value = 0; + + errno = 0; + value = (int) strtod(_element, &junk); + if (errno != 0 || _element == junk) { + pfree(_element); + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not process string to value"); + PG_RETURN_NULL(); + } + + if (value > 255) { + elog(NOTICE, "RGBA value cannot be greater than 255. Defaulting to 255"); + value = 255; + } + else if (value < 0) { + elog(NOTICE, "RGBA value cannot be less than zero. Defaulting to zero"); + value = 0; + } + arg->colormap->entry[arg->colormap->nentry].color[j - 1] = value; + } + + pfree(_element); + } + + POSTGIS_RT_DEBUGF(4, "colormap->entry[%d] (isnodata, value, R, G, B, A) = (%d, %f, %d, %d, %d, %d)", + arg->colormap->nentry, + arg->colormap->entry[arg->colormap->nentry].isnodata, + arg->colormap->entry[arg->colormap->nentry].value, + arg->colormap->entry[arg->colormap->nentry].color[0], + arg->colormap->entry[arg->colormap->nentry].color[1], + arg->colormap->entry[arg->colormap->nentry].color[2], + arg->colormap->entry[arg->colormap->nentry].color[3] + ); + + arg->colormap->nentry++; + } + + POSTGIS_RT_DEBUGF(4, "colormap->nentry = %d", arg->colormap->nentry); + POSTGIS_RT_DEBUGF(4, "colormap->ncolor = %d", arg->colormap->ncolor); + } + + /* call colormap */ + raster = rt_raster_colormap(arg->raster, arg->nband - 1, arg->colormap); + if (raster == NULL) { + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_colorMap: Could not create new raster with applied colormap"); + PG_RETURN_NULL(); + } + + rtpg_colormap_arg_destroy(arg); + PG_FREE_IF_COPY(pgraster, 0); + pgraster = rt_raster_serialize(raster); + rt_raster_destroy(raster); + + POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Done"); + + if (pgraster == NULL) + PG_RETURN_NULL(); + + SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size); + PG_RETURN_POINTER(pgraster); +} + +PG_FUNCTION_INFO_V1(RASTER_mapAlgebraExpr); +Datum RASTER_mapAlgebraExpr(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_raster newrast = NULL; + rt_band band = NULL; + rt_band newband = NULL; + int x, y, nband, width, height; + double r; + double newnodatavalue = 0.0; + double newinitialvalue = 0.0; + double newval = 0.0; + char *newexpr = NULL; + char *initexpr = NULL; + char *expression = NULL; + int hasnodataval = 0; + double nodataval = 0.; + rt_pixtype newpixeltype; + int skipcomputation = 0; + int len = 0; + const int argkwcount = 3; + enum KEYWORDS { kVAL=0, kX=1, kY=2 }; + char *argkw[] = {"[rast]", "[rast.x]", "[rast.y]"}; + Oid argkwtypes[] = { FLOAT8OID, INT4OID, INT4OID }; + int argcount = 0; + Oid argtype[] = { FLOAT8OID, INT4OID, INT4OID }; + uint8_t argpos[3] = {0}; + char place[5]; + int idx = 0; + int ret = -1; + TupleDesc tupdesc; + SPIPlanPtr spi_plan = NULL; + SPITupleTable * tuptable = NULL; + HeapTuple tuple; + char * strFromText = NULL; + Datum *values = NULL; + Datum datum = (Datum)NULL; + char *nulls = NULL; + bool isnull = FALSE; + int i = 0; + int j = 0; + + POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraExpr: Starting..."); + + /* Check raster */ + if (PG_ARGISNULL(0)) { + elog(NOTICE, "Raster is NULL. Returning NULL"); + PG_RETURN_NULL(); + } + + + /* Deserialize raster */ + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + raster = rt_raster_deserialize(pgraster, FALSE); + if (NULL == raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraExpr: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Getting arguments..."); + + if (PG_ARGISNULL(1)) + nband = 1; + else + nband = PG_GETARG_INT32(1); + + if (nband < 1) + nband = 1; + + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Creating new empty raster..."); + + /** + * Create a new empty raster with having the same georeference as the + * provided raster + **/ + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + + newrast = rt_raster_new(width, height); + + if ( NULL == newrast ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraExpr: Could not create a new raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(newrast, + rt_raster_get_x_scale(raster), + rt_raster_get_y_scale(raster)); + + rt_raster_set_offsets(newrast, + rt_raster_get_x_offset(raster), + rt_raster_get_y_offset(raster)); + + rt_raster_set_skews(newrast, + rt_raster_get_x_skew(raster), + rt_raster_get_y_skew(raster)); + + rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); + + + /** + * If this new raster is empty (width = 0 OR height = 0) then there is + * nothing to compute and we return it right now + **/ + if (rt_raster_is_empty(newrast)) + { + elog(NOTICE, "Raster is empty. Returning an empty raster"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Getting raster band %d...", nband); + + /** + * Check if the raster has the required band. Otherwise, return a raster + * without band + **/ + if (!rt_raster_has_band(raster, nband - 1)) { + elog(NOTICE, "Raster does not have the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* Get the raster band */ + band = rt_raster_get_band(raster, nband - 1); + if ( NULL == band ) { + elog(NOTICE, "Could not get the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* + * Get NODATA value + */ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Getting NODATA value for band..."); + + if (rt_band_get_hasnodata_flag(band)) { + rt_band_get_nodata(band, &newnodatavalue); + } + + else { + newnodatavalue = rt_band_get_min_value(band); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: NODATA value for band: = %f", + newnodatavalue); + + /** + * We set the initial value of the future band to nodata value. If nodata + * value is null, then the raster will be initialized to + * rt_band_get_min_value but all the values should be recomputed anyway + **/ + newinitialvalue = newnodatavalue; + + /** + * Set the new pixeltype + **/ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Setting pixeltype..."); + + if (PG_ARGISNULL(2)) { + newpixeltype = rt_band_get_pixtype(band); + } + + else { + strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); + newpixeltype = rt_pixtype_index_from_name(strFromText); + pfree(strFromText); + if (newpixeltype == PT_END) + newpixeltype = rt_band_get_pixtype(band); + } + + if (newpixeltype == PT_END) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraExpr: Invalid pixeltype"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Pixeltype set to %s", + rt_pixtype_name(newpixeltype)); + + + /* Construct expression for raster values */ + if (!PG_ARGISNULL(3)) { + expression = text_to_cstring(PG_GETARG_TEXT_P(3)); + len = strlen("SELECT (") + strlen(expression) + strlen(")::double precision"); + initexpr = (char *)palloc(len + 1); + + strncpy(initexpr, "SELECT (", strlen("SELECT (")); + strncpy(initexpr + strlen("SELECT ("), expression, strlen(expression)); + strncpy(initexpr + strlen("SELECT (") + strlen(expression), ")::double precision", strlen(")::double precision")); + initexpr[len] = '\0'; + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Expression is %s", initexpr); + + /* We don't need this memory */ + /* + pfree(expression); + expression = NULL; + */ + } + + + + /** + * Optimization: If a nodataval is provided, use it for newinitialvalue. + * Then, we can initialize the raster with this value and skip the + * computation of nodata values one by one in the main computing loop + **/ + if (!PG_ARGISNULL(4)) { + hasnodataval = 1; + nodataval = PG_GETARG_FLOAT8(4); + newinitialvalue = nodataval; + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: new initial value = %f", + newinitialvalue); + } + else + hasnodataval = 0; + + + + /** + * Optimization: If the raster is only filled with nodata values return + * right now a raster filled with the newinitialvalue + * TODO: Call rt_band_check_isnodata instead? + **/ + if (rt_band_get_isnodata_flag(band)) { + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: Band is a nodata band, returning " + "a raster filled with nodata"); + + ret = rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + /* Free memory */ + if (initexpr) + pfree(initexpr); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + + /** + * Optimization: If expression resume to 'RAST' and hasnodataval is zero, + * we can just return the band from the original raster + **/ + if (initexpr != NULL && ( !strcmp(initexpr, "SELECT [rast]") || !strcmp(initexpr, "SELECT [rast.val]") ) && !hasnodataval) { + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Expression resumes to RAST. " + "Returning raster with band %d from original raster", nband); + + POSTGIS_RT_DEBUGF(4, "RASTER_mapAlgebraExpr: New raster has %d bands", + rt_raster_get_num_bands(newrast)); + + rt_raster_copy_band(newrast, raster, nband - 1, 0); + + POSTGIS_RT_DEBUGF(4, "RASTER_mapAlgebraExpr: New raster now has %d bands", + rt_raster_get_num_bands(newrast)); + + if (initexpr) + pfree(initexpr); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /** + * Optimization: If expression resume to a constant (it does not contain + * [rast) + **/ + if (initexpr != NULL && strstr(initexpr, "[rast") == NULL) { + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraExpr: Could not connect to the SPI manager"); + PG_RETURN_NULL(); + }; + + /* Execute the expresion into newval */ + ret = SPI_execute(initexpr, FALSE, 0); + + if (ret != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + + /* Free memory allocated out of the current context */ + if (SPI_tuptable) + SPI_freetuptable(tuptable); + PG_FREE_IF_COPY(pgraster, 0); + + SPI_finish(); + elog(ERROR, "RASTER_mapAlgebraExpr: Invalid construction for expression"); + PG_RETURN_NULL(); + } + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + + tuple = tuptable->vals[0]; + newexpr = SPI_getvalue(tuple, tupdesc, 1); + if ( ! newexpr ) { + POSTGIS_RT_DEBUG(3, "Constant expression evaluated to NULL, keeping initvalue"); + newval = newinitialvalue; + } else { + newval = atof(newexpr); + } + + SPI_freetuptable(tuptable); + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: New raster value = %f", + newval); + + SPI_finish(); + + skipcomputation = 1; + + /** + * Compute the new value, set it and we will return after creating the + * new raster + **/ + if (!hasnodataval) { + newinitialvalue = newval; + skipcomputation = 2; + } + + /* Return the new raster as it will be before computing pixel by pixel */ + else if (FLT_NEQ(newval, newinitialvalue)) { + skipcomputation = 2; + } + } + + /** + * Create the raster receiving all the computed values. Initialize it to the + * new initial value + **/ + ret = rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + /** + * Optimization: If expression is NULL, or all the pixels could be set in + * one step, return the initialized raster now + **/ + /*if (initexpr == NULL || skipcomputation == 2) {*/ + if (expression == NULL || skipcomputation == 2) { + + /* Free memory */ + if (initexpr) + pfree(initexpr); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + RASTER_DEBUG(3, "RASTER_mapAlgebraExpr: Creating new raster band..."); + + /* Get the new raster band */ + newband = rt_raster_get_band(newrast, 0); + if ( NULL == newband ) { + elog(NOTICE, "Could not modify band for new raster. Returning new " + "raster with the original band"); + + if (initexpr) + pfree(initexpr); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraExpr: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: Main computing loop (%d x %d)", + width, height); + + if (initexpr != NULL) { + /* Convert [rast.val] to [rast] */ + newexpr = rtpg_strreplace(initexpr, "[rast.val]", "[rast]", NULL); + pfree(initexpr); initexpr=newexpr; + + sprintf(place,"$1"); + for (i = 0, j = 1; i < argkwcount; i++) { + len = 0; + newexpr = rtpg_strreplace(initexpr, argkw[i], place, &len); + pfree(initexpr); initexpr=newexpr; + if (len > 0) { + argtype[argcount] = argkwtypes[i]; + argcount++; + argpos[i] = j++; + + sprintf(place, "$%d", j); + } + else { + argpos[i] = 0; + } + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: initexpr = %s", initexpr); + + /* define values */ + values = (Datum *) palloc(sizeof(Datum) * argcount); + if (values == NULL) { + + SPI_finish(); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraExpr: Could not allocate memory for value parameters of prepared statement"); + PG_RETURN_NULL(); + } + + /* define nulls */ + nulls = (char *)palloc(argcount); + if (nulls == NULL) { + + SPI_finish(); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraExpr: Could not allocate memory for null parameters of prepared statement"); + PG_RETURN_NULL(); + } + + /* Connect to SPI and prepare the expression */ + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) { + + if (initexpr) + pfree(initexpr); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraExpr: Could not connect to the SPI manager"); + PG_RETURN_NULL(); + }; + + /* Type of all arguments is FLOAT8OID */ + spi_plan = SPI_prepare(initexpr, argcount, argtype); + + if (spi_plan == NULL) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + SPI_finish(); + + pfree(initexpr); + + elog(ERROR, "RASTER_mapAlgebraExpr: Could not prepare expression"); + PG_RETURN_NULL(); + } + } + + for (x = 0; x < width; x++) { + for(y = 0; y < height; y++) { + ret = rt_band_get_pixel(band, x, y, &r, NULL); + + /** + * We compute a value only for the withdata value pixel since the + * nodata value has already been set by the first optimization + **/ + if (ret == ES_NONE && FLT_NEQ(r, newnodatavalue)) { + if (skipcomputation == 0) { + if (initexpr != NULL) { + /* Reset the null arg flags. */ + memset(nulls, 'n', argcount); + + for (i = 0; i < argkwcount; i++) { + idx = argpos[i]; + if (idx < 1) continue; + idx--; + + if (i == kX ) { + /* x is 0 based index, but SQL expects 1 based index */ + values[idx] = Int32GetDatum(x+1); + nulls[idx] = ' '; + } + else if (i == kY) { + /* y is 0 based index, but SQL expects 1 based index */ + values[idx] = Int32GetDatum(y+1); + nulls[idx] = ' '; + } + else if (i == kVAL ) { + values[idx] = Float8GetDatum(r); + nulls[idx] = ' '; + } + + } + + ret = SPI_execute_plan(spi_plan, values, nulls, FALSE, 0); + if (ret != SPI_OK_SELECT || SPI_tuptable == NULL || + SPI_processed != 1) { + if (SPI_tuptable) + SPI_freetuptable(tuptable); + + SPI_freeplan(spi_plan); + SPI_finish(); + + pfree(values); + pfree(nulls); + pfree(initexpr); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraExpr: Error executing prepared plan"); + + PG_RETURN_NULL(); + } + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + + tuple = tuptable->vals[0]; + datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + if ( SPI_result == SPI_ERROR_NOATTRIBUTE ) { + POSTGIS_RT_DEBUGF(3, "Expression for pixel %d,%d (value %g) errored, skip setting", x+1,y+1,r); + newval = newinitialvalue; + } + else if ( isnull ) { + POSTGIS_RT_DEBUGF(3, "Expression for pixel %d,%d (value %g) evaluated to NULL, skip setting", x+1,y+1,r); + newval = newinitialvalue; + } else { + newval = DatumGetFloat8(datum); + } + + SPI_freetuptable(tuptable); + } + + else + newval = newinitialvalue; + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraExpr: new value = %f", + newval); + } + + + rt_band_set_pixel(newband, x, y, newval, NULL); + } + + } + } + + if (initexpr != NULL) { + SPI_freeplan(spi_plan); + SPI_finish(); + + pfree(values); + pfree(nulls); + pfree(initexpr); + } + else { + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: no SPI cleanup"); + } + + + /* The newrast band has been modified */ + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: raster modified, serializing it."); + /* Serialize created raster */ + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraExpr: raster serialized"); + + + POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraExpr: returning raster"); + + + PG_RETURN_POINTER(pgrtn); +} + +/* + * One raster user function MapAlgebra. + */ +PG_FUNCTION_INFO_V1(RASTER_mapAlgebraFct); +Datum RASTER_mapAlgebraFct(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_raster newrast = NULL; + rt_band band = NULL; + rt_band newband = NULL; + int x, y, nband, width, height; + double r; + double newnodatavalue = 0.0; + double newinitialvalue = 0.0; + double newval = 0.0; + rt_pixtype newpixeltype; + int ret = -1; + Oid oid; + FmgrInfo cbinfo; + FunctionCallInfoData cbdata; + Datum tmpnewval; + char * strFromText = NULL; + int k = 0; + + POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraFct: STARTING..."); + + /* Check raster */ + if (PG_ARGISNULL(0)) { + elog(WARNING, "Raster is NULL. Returning NULL"); + PG_RETURN_NULL(); + } + + + /* Deserialize raster */ + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + raster = rt_raster_deserialize(pgraster, FALSE); + if (NULL == raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraFct: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Getting arguments..."); + + /* Get the rest of the arguments */ + + if (PG_ARGISNULL(1)) + nband = 1; + else + nband = PG_GETARG_INT32(1); + + if (nband < 1) + nband = 1; + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Creating new empty raster..."); + + /** + * Create a new empty raster with having the same georeference as the + * provided raster + **/ + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + + newrast = rt_raster_new(width, height); + + if ( NULL == newrast ) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + elog(ERROR, "RASTER_mapAlgebraFct: Could not create a new raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(newrast, + rt_raster_get_x_scale(raster), + rt_raster_get_y_scale(raster)); + + rt_raster_set_offsets(newrast, + rt_raster_get_x_offset(raster), + rt_raster_get_y_offset(raster)); + + rt_raster_set_skews(newrast, + rt_raster_get_x_skew(raster), + rt_raster_get_y_skew(raster)); + + rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); + + + /** + * If this new raster is empty (width = 0 OR height = 0) then there is + * nothing to compute and we return it right now + **/ + if (rt_raster_is_empty(newrast)) + { + elog(NOTICE, "Raster is empty. Returning an empty raster"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Getting raster band %d...", nband); + + /** + * Check if the raster has the required band. Otherwise, return a raster + * without band + **/ + if (!rt_raster_has_band(raster, nband - 1)) { + elog(NOTICE, "Raster does not have the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* Get the raster band */ + band = rt_raster_get_band(raster, nband - 1); + if ( NULL == band ) { + elog(NOTICE, "Could not get the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* + * Get NODATA value + */ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Getting NODATA value for band..."); + + if (rt_band_get_hasnodata_flag(band)) { + rt_band_get_nodata(band, &newnodatavalue); + } + + else { + newnodatavalue = rt_band_get_min_value(band); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: NODATA value for band: %f", + newnodatavalue); + /** + * We set the initial value of the future band to nodata value. If nodata + * value is null, then the raster will be initialized to + * rt_band_get_min_value but all the values should be recomputed anyway + **/ + newinitialvalue = newnodatavalue; + + /** + * Set the new pixeltype + **/ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Setting pixeltype..."); + + if (PG_ARGISNULL(2)) { + newpixeltype = rt_band_get_pixtype(band); + } + + else { + strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); + newpixeltype = rt_pixtype_index_from_name(strFromText); + pfree(strFromText); + if (newpixeltype == PT_END) + newpixeltype = rt_band_get_pixtype(band); + } + + if (newpixeltype == PT_END) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Invalid pixeltype"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Pixeltype set to %s", + rt_pixtype_name(newpixeltype)); + + /* Get the name of the callback user function for raster values */ + if (PG_ARGISNULL(3)) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Required function is missing. Returning NULL"); + PG_RETURN_NULL(); + } + + oid = PG_GETARG_OID(3); + if (oid == InvalidOid) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Got invalid function object id. Returning NULL"); + PG_RETURN_NULL(); + } + + fmgr_info(oid, &cbinfo); + + /* function cannot return set */ + if (cbinfo.fn_retset) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Function provided must return double precision not resultset"); + PG_RETURN_NULL(); + } + /* function should have correct # of args */ + else if (cbinfo.fn_nargs < 2 || cbinfo.fn_nargs > 3) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Function does not have two or three input parameters"); + PG_RETURN_NULL(); + } + + if (cbinfo.fn_nargs == 2) + k = 1; + else + k = 2; + + if (func_volatile(oid) == 'v') { + elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); + } + + /* prep function call data */ +#if POSTGIS_PGSQL_VERSION <= 90 + InitFunctionCallInfoData(cbdata, &cbinfo, 2, InvalidOid, NULL); +#else + InitFunctionCallInfoData(cbdata, &cbinfo, 2, InvalidOid, NULL, NULL); +#endif + memset(cbdata.argnull, FALSE, sizeof(bool) * cbinfo.fn_nargs); + + /* check that the function isn't strict if the args are null. */ + if (PG_ARGISNULL(4)) { + if (cbinfo.fn_strict) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFct: Strict callback functions cannot have null parameters"); + PG_RETURN_NULL(); + } + + cbdata.arg[k] = (Datum)NULL; + cbdata.argnull[k] = TRUE; + } + else { + cbdata.arg[k] = PG_GETARG_DATUM(4); + } + + /** + * Optimization: If the raster is only filled with nodata values return + * right now a raster filled with the nodatavalueexpr + * TODO: Call rt_band_check_isnodata instead? + **/ + if (rt_band_get_isnodata_flag(band)) { + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Band is a nodata band, returning " + "a raster filled with nodata"); + + ret = rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + + /** + * Create the raster receiving all the computed values. Initialize it to the + * new initial value + **/ + ret = rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + /* Get the new raster band */ + newband = rt_raster_get_band(newrast, 0); + if ( NULL == newband ) { + elog(NOTICE, "Could not modify band for new raster. Returning new " + "raster with the original band"); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFct: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: Main computing loop (%d x %d)", + width, height); + + for (x = 0; x < width; x++) { + for(y = 0; y < height; y++) { + ret = rt_band_get_pixel(band, x, y, &r, NULL); + + /** + * We compute a value only for the withdata value pixel since the + * nodata value has already been set by the first optimization + **/ + if (ret == ES_NONE) { + if (FLT_EQ(r, newnodatavalue)) { + if (cbinfo.fn_strict) { + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: Strict callbacks cannot accept NULL arguments, skipping NODATA cell."); + continue; + } + cbdata.argnull[0] = TRUE; + cbdata.arg[0] = (Datum)NULL; + } + else { + cbdata.argnull[0] = FALSE; + cbdata.arg[0] = Float8GetDatum(r); + } + + /* Add pixel positions if callback has proper # of args */ + if (cbinfo.fn_nargs == 3) { + Datum d[2]; + ArrayType *a; + + d[0] = Int32GetDatum(x+1); + d[1] = Int32GetDatum(y+1); + + a = construct_array(d, 2, INT4OID, sizeof(int32), true, 'i'); + + cbdata.argnull[1] = FALSE; + cbdata.arg[1] = PointerGetDatum(a); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: (%dx%d), r = %f", + x, y, r); + + tmpnewval = FunctionCallInvoke(&cbdata); + + if (cbdata.isnull) { + newval = newnodatavalue; + } + else { + newval = DatumGetFloat8(tmpnewval); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFct: new value = %f", + newval); + + rt_band_set_pixel(newband, x, y, newval, NULL); + } + + } + } + + /* The newrast band has been modified */ + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: raster modified, serializing it."); + /* Serialize created raster */ + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) + PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFct: raster serialized"); + + POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraFct: returning raster"); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/** + * One raster neighborhood MapAlgebra + */ +PG_FUNCTION_INFO_V1(RASTER_mapAlgebraFctNgb); +Datum RASTER_mapAlgebraFctNgb(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster = NULL; + rt_raster newrast = NULL; + rt_band band = NULL; + rt_band newband = NULL; + int x, y, nband, width, height, ngbwidth, ngbheight, winwidth, winheight, u, v, nIndex, nNullItems; + double r, rpix; + double newnodatavalue = 0.0; + double newinitialvalue = 0.0; + double newval = 0.0; + rt_pixtype newpixeltype; + int ret = -1; + Oid oid; + FmgrInfo cbinfo; + FunctionCallInfoData cbdata; + Datum tmpnewval; + ArrayType * neighborDatum; + char * strFromText = NULL; + text * txtNodataMode = NULL; + text * txtCallbackParam = NULL; + int intReplace = 0; + float fltReplace = 0; + bool valuereplace = false, pixelreplace, nNodataOnly = true, nNullSkip = false; + Datum * neighborData = NULL; + bool * neighborNulls = NULL; + int neighborDims[2]; + int neighborLbs[2]; + int16 typlen; + bool typbyval; + char typalign; + + POSTGIS_RT_DEBUG(2, "RASTER_mapAlgebraFctNgb: STARTING..."); + + /* Check raster */ + if (PG_ARGISNULL(0)) { + elog(WARNING, "Raster is NULL. Returning NULL"); + PG_RETURN_NULL(); + } + + + /* Deserialize raster */ + pgraster = (rt_pgraster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + raster = rt_raster_deserialize(pgraster, FALSE); + if (NULL == raster) + { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Getting arguments..."); + + /* Get the rest of the arguments */ + + if (PG_ARGISNULL(1)) + nband = 1; + else + nband = PG_GETARG_INT32(1); + + if (nband < 1) + nband = 1; + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Creating new empty raster..."); + + /** + * Create a new empty raster with having the same georeference as the + * provided raster + **/ + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + + newrast = rt_raster_new(width, height); + + if ( NULL == newrast ) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not create a new raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(newrast, + rt_raster_get_x_scale(raster), + rt_raster_get_y_scale(raster)); + + rt_raster_set_offsets(newrast, + rt_raster_get_x_offset(raster), + rt_raster_get_y_offset(raster)); + + rt_raster_set_skews(newrast, + rt_raster_get_x_skew(raster), + rt_raster_get_y_skew(raster)); + + rt_raster_set_srid(newrast, rt_raster_get_srid(raster)); + + + /** + * If this new raster is empty (width = 0 OR height = 0) then there is + * nothing to compute and we return it right now + **/ + if (rt_raster_is_empty(newrast)) + { + elog(NOTICE, "Raster is empty. Returning an empty raster"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Getting raster band %d...", nband); + + /** + * Check if the raster has the required band. Otherwise, return a raster + * without band + **/ + if (!rt_raster_has_band(raster, nband - 1)) { + elog(NOTICE, "Raster does not have the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* Get the raster band */ + band = rt_raster_get_band(raster, nband - 1); + if ( NULL == band ) { + elog(NOTICE, "Could not get the required band. Returning a raster " + "without a band"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* + * Get NODATA value + */ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Getting NODATA value for band..."); + + if (rt_band_get_hasnodata_flag(band)) { + rt_band_get_nodata(band, &newnodatavalue); + } + + else { + newnodatavalue = rt_band_get_min_value(band); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: NODATA value for band: %f", + newnodatavalue); + /** + * We set the initial value of the future band to nodata value. If nodata + * value is null, then the raster will be initialized to + * rt_band_get_min_value but all the values should be recomputed anyway + **/ + newinitialvalue = newnodatavalue; + + /** + * Set the new pixeltype + **/ + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Setting pixeltype..."); + + if (PG_ARGISNULL(2)) { + newpixeltype = rt_band_get_pixtype(band); + } + + else { + strFromText = text_to_cstring(PG_GETARG_TEXT_P(2)); + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Pixeltype parameter: %s", strFromText); + newpixeltype = rt_pixtype_index_from_name(strFromText); + pfree(strFromText); + if (newpixeltype == PT_END) + newpixeltype = rt_band_get_pixtype(band); + } + + if (newpixeltype == PT_END) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Invalid pixeltype"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Pixeltype set to %s (%d)", + rt_pixtype_name(newpixeltype), newpixeltype); + + /* Get the name of the callback userfunction */ + if (PG_ARGISNULL(5)) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Required function is missing"); + PG_RETURN_NULL(); + } + + oid = PG_GETARG_OID(5); + if (oid == InvalidOid) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Got invalid function object id"); + PG_RETURN_NULL(); + } + + fmgr_info(oid, &cbinfo); + + /* function cannot return set */ + if (cbinfo.fn_retset) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Function provided must return double precision not resultset"); + PG_RETURN_NULL(); + } + /* function should have correct # of args */ + else if (cbinfo.fn_nargs != 3) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Function does not have three input parameters"); + PG_RETURN_NULL(); + } + + if (func_volatile(oid) == 'v') { + elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); + } + + /* prep function call data */ +#if POSTGIS_PGSQL_VERSION <= 90 + InitFunctionCallInfoData(cbdata, &cbinfo, 3, InvalidOid, NULL); +#else + InitFunctionCallInfoData(cbdata, &cbinfo, 3, InvalidOid, NULL, NULL); +#endif + memset(cbdata.argnull, FALSE, sizeof(bool) * 3); + + /* check that the function isn't strict if the args are null. */ + if (PG_ARGISNULL(7)) { + if (cbinfo.fn_strict) { + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + rt_raster_destroy(newrast); + + elog(ERROR, "RASTER_mapAlgebraFctNgb: Strict callback functions cannot have NULL parameters"); + PG_RETURN_NULL(); + } + + cbdata.arg[2] = (Datum)NULL; + cbdata.argnull[2] = TRUE; + } + else { + cbdata.arg[2] = PG_GETARG_DATUM(7); + } + + /** + * Optimization: If the raster is only filled with nodata values return + * right now a raster filled with the nodatavalueexpr + * TODO: Call rt_band_check_isnodata instead? + **/ + if (rt_band_get_isnodata_flag(band)) { + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: Band is a nodata band, returning " + "a raster filled with nodata"); + + rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + + /** + * Create the raster receiving all the computed values. Initialize it to the + * new initial value + **/ + rt_raster_generate_new_band(newrast, newpixeltype, + newinitialvalue, TRUE, newnodatavalue, 0); + + /* Get the new raster band */ + newband = rt_raster_get_band(newrast, 0); + if ( NULL == newband ) { + elog(NOTICE, "Could not modify band for new raster. Returning new " + "raster with the original band"); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* Get the width of the neighborhood */ + if (PG_ARGISNULL(3) || PG_GETARG_INT32(3) <= 0) { + elog(NOTICE, "Neighborhood width is NULL or <= 0. Returning new " + "raster with the original band"); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + ngbwidth = PG_GETARG_INT32(3); + winwidth = ngbwidth * 2 + 1; + + /* Get the height of the neighborhood */ + if (PG_ARGISNULL(4) || PG_GETARG_INT32(4) <= 0) { + elog(NOTICE, "Neighborhood height is NULL or <= 0. Returning new " + "raster with the original band"); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + ngbheight = PG_GETARG_INT32(4); + winheight = ngbheight * 2 + 1; + + /* Get the type of NODATA behavior for the neighborhoods. */ + if (PG_ARGISNULL(6)) { + elog(NOTICE, "Neighborhood NODATA behavior defaulting to 'ignore'"); + txtNodataMode = cstring_to_text("ignore"); + } + else { + txtNodataMode = PG_GETARG_TEXT_P(6); + } + + txtCallbackParam = (text*)palloc(VARSIZE(txtNodataMode)); + SET_VARSIZE(txtCallbackParam, VARSIZE(txtNodataMode)); + memcpy((void *)VARDATA(txtCallbackParam), (void *)VARDATA(txtNodataMode), VARSIZE(txtNodataMode) - VARHDRSZ); + + /* pass the nodata mode into the user function */ + cbdata.arg[1] = CStringGetDatum(txtCallbackParam); + + strFromText = text_to_cstring(txtNodataMode); + strFromText = rtpg_strtoupper(strFromText); + + if (strcmp(strFromText, "VALUE") == 0) + valuereplace = true; + else if (strcmp(strFromText, "IGNORE") != 0 && strcmp(strFromText, "NULL") != 0) { + /* if the text is not "IGNORE" or "NULL", it may be a numerical value */ + if (sscanf(strFromText, "%d", &intReplace) <= 0 && sscanf(strFromText, "%f", &fltReplace) <= 0) { + /* the value is NOT an integer NOR a floating point */ + elog(NOTICE, "Neighborhood NODATA mode is not recognized. Must be one of 'value', 'ignore', " + "'NULL', or a numeric value. Returning new raster with the original band"); + + /* clean up the nodatamode string */ + pfree(txtCallbackParam); + pfree(strFromText); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* Serialize created raster */ + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) { + elog(ERROR, "RASTER_mapAlgebraFctNgb: Could not serialize raster"); + PG_RETURN_NULL(); + } + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + } + else if (strcmp(strFromText, "NULL") == 0) { + /* this setting means that the neighborhood should be skipped if any of the values are null */ + nNullSkip = true; + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: Main computing loop (%d x %d)", + width, height); + + /* Allocate room for the neighborhood. */ + neighborData = (Datum *)palloc(winwidth * winheight * sizeof(Datum)); + neighborNulls = (bool *)palloc(winwidth * winheight * sizeof(bool)); + + /* The dimensions of the neighborhood array, for creating a multi-dimensional array. */ + neighborDims[0] = winwidth; + neighborDims[1] = winheight; + + /* The lower bounds for the new multi-dimensional array. */ + neighborLbs[0] = 1; + neighborLbs[1] = 1; + + /* Get information about the type of item in the multi-dimensional array (float8). */ + get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign); + + for (x = 0 + ngbwidth; x < width - ngbwidth; x++) { + for(y = 0 + ngbheight; y < height - ngbheight; y++) { + /* populate an array with the pixel values in the neighborhood */ + nIndex = 0; + nNullItems = 0; + nNodataOnly = true; + pixelreplace = false; + if (valuereplace) { + ret = rt_band_get_pixel(band, x, y, &rpix, NULL); + if (ret == ES_NONE && FLT_NEQ(rpix, newnodatavalue)) { + pixelreplace = true; + } + } + for (u = x - ngbwidth; u <= x + ngbwidth; u++) { + for (v = y - ngbheight; v <= y + ngbheight; v++) { + ret = rt_band_get_pixel(band, u, v, &r, NULL); + if (ret == ES_NONE) { + if (FLT_NEQ(r, newnodatavalue)) { + /* If the pixel value for this neighbor cell is not NODATA */ + neighborData[nIndex] = Float8GetDatum((double)r); + neighborNulls[nIndex] = false; + nNodataOnly = false; + } + else { + /* If the pixel value for this neighbor cell is NODATA */ + if (valuereplace && pixelreplace) { + /* Replace the NODATA value with the currently processing pixel. */ + neighborData[nIndex] = Float8GetDatum((double)rpix); + neighborNulls[nIndex] = false; + /* do not increment nNullItems, since the user requested that the */ + /* neighborhood replace NODATA values with the central pixel value */ + } + else { + neighborData[nIndex] = PointerGetDatum(NULL); + neighborNulls[nIndex] = true; + nNullItems++; + } + } + } + else { + /* Fill this will NULL if we can't read the raster pixel. */ + neighborData[nIndex] = PointerGetDatum(NULL); + neighborNulls[nIndex] = true; + nNullItems++; + } + /* Next neighbor position */ + nIndex++; + } + } + + /** + * We compute a value only for the withdata value neighborhood since the + * nodata value has already been set by the first optimization + **/ + if (!(nNodataOnly || /* neighborhood only contains NODATA -- OR -- */ + (nNullSkip && nNullItems > 0) || /* neighborhood should skip any NODATA cells, and a NODATA cell was detected -- OR -- */ + (valuereplace && nNullItems > 0))) { /* neighborhood should replace NODATA cells with the central pixel value, and a NODATA cell was detected */ + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: (%dx%d), %dx%d neighborhood", + x, y, winwidth, winheight); + + neighborDatum = construct_md_array((void *)neighborData, neighborNulls, 2, neighborDims, neighborLbs, + FLOAT8OID, typlen, typbyval, typalign); + + /* Assign the neighbor matrix as the first argument to the user function */ + cbdata.arg[0] = PointerGetDatum(neighborDatum); + + /* Invoke the user function */ + tmpnewval = FunctionCallInvoke(&cbdata); + + /* Get the return value of the user function */ + if (cbdata.isnull) { + newval = newnodatavalue; + } + else { + newval = DatumGetFloat8(tmpnewval); + } + + POSTGIS_RT_DEBUGF(3, "RASTER_mapAlgebraFctNgb: new value = %f", + newval); + + rt_band_set_pixel(newband, x, y, newval, NULL); + } + + /* reset the number of null items in the neighborhood */ + nNullItems = 0; + } + } + + + /* clean up */ + pfree(neighborNulls); + pfree(neighborData); + pfree(strFromText); + pfree(txtCallbackParam); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* The newrast band has been modified */ + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: raster modified, serializing it."); + /* Serialize created raster */ + + pgrtn = rt_raster_serialize(newrast); + rt_raster_destroy(newrast); + if (NULL == pgrtn) + PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "RASTER_mapAlgebraFctNgb: raster serialized"); + POSTGIS_RT_DEBUG(4, "RASTER_mapAlgebraFctNgb: returning raster"); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} + +/** + * Two raster MapAlgebra + */ +PG_FUNCTION_INFO_V1(RASTER_mapAlgebra2); +Datum RASTER_mapAlgebra2(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_pgraster *pgrtn; + rt_raster rast[2] = {NULL}; + int _isempty[2] = {0}; + uint32_t bandindex[2] = {0}; + rt_raster _rast[2] = {NULL}; + rt_band _band[2] = {NULL}; + int _hasnodata[2] = {0}; + double _nodataval[2] = {0}; + double _offset[4] = {0.}; + double _rastoffset[2][4] = {{0.}}; + int _haspixel[2] = {0}; + double _pixel[2] = {0}; + int _pos[2][2] = {{0}}; + uint16_t _dim[2][2] = {{0}}; + + char *pixtypename = NULL; + rt_pixtype pixtype = PT_END; + char *extenttypename = NULL; + rt_extenttype extenttype = ET_INTERSECTION; + + rt_raster raster = NULL; + rt_band band = NULL; + uint16_t dim[2] = {0}; + int haspixel = 0; + double pixel = 0.; + double nodataval = 0; + double gt[6] = {0.}; + + Oid calltype = InvalidOid; + + const int spi_count = 3; + uint16_t spi_exprpos[3] = {4, 7, 8}; + uint32_t spi_argcount[3] = {0}; + char *expr = NULL; + char *sql = NULL; + SPIPlanPtr spi_plan[3] = {NULL}; + uint16_t spi_empty = 0; + Oid *argtype = NULL; + const int argkwcount = 8; + uint8_t argpos[3][8] = {{0}}; + char *argkw[] = {"[rast1.x]", "[rast1.y]", "[rast1.val]", "[rast1]", "[rast2.x]", "[rast2.y]", "[rast2.val]", "[rast2]"}; + Datum values[argkwcount]; + bool nulls[argkwcount]; + TupleDesc tupdesc; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isnull = FALSE; + int hasargval[3] = {0}; + double argval[3] = {0.}; + int hasnodatanodataval = 0; + double nodatanodataval = 0; + int isnodata = 0; + + Oid ufc_noid = InvalidOid; + FmgrInfo ufl_info; + FunctionCallInfoData ufc_info; + int ufc_nullcount = 0; + + int idx = 0; + uint32_t i = 0; + uint32_t j = 0; + uint32_t k = 0; + uint32_t x = 0; + uint32_t y = 0; + int _x = 0; + int _y = 0; + int err; + int aligned = 0; + int len = 0; + + POSTGIS_RT_DEBUG(3, "Starting RASTER_mapAlgebra2"); + + for (i = 0, j = 0; i < set_count; i++) { + if (!PG_ARGISNULL(j)) { + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i && rast[k] != NULL) + rt_raster_destroy(rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* empty */ + _isempty[i] = rt_raster_is_empty(rast[i]); + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + } + j++; + } + else { + _isempty[i] = 1; + j += 2; + } + + POSTGIS_RT_DEBUGF(3, "_isempty[%d] = %d", i, _isempty[i]); + } + + /* both rasters are NULL */ + if (rast[0] == NULL && rast[1] == NULL) { + elog(NOTICE, "The two rasters provided are NULL. Returning NULL"); + for (k = 0; k < set_count; k++) { + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* both rasters are empty */ + if (_isempty[0] && _isempty[1]) { + elog(NOTICE, "The two rasters provided are empty. Returning empty raster"); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + for (k = 0; k < set_count; k++) { + if (rast[k] != NULL) + rt_raster_destroy(rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not create empty raster"); + PG_RETURN_NULL(); + } + rt_raster_set_scale(raster, 0, 0); + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgrtn) + PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* replace the empty or NULL raster with one matching the other */ + if ( + (rast[0] == NULL || _isempty[0]) || + (rast[1] == NULL || _isempty[1]) + ) { + /* first raster is empty */ + if (rast[0] == NULL || _isempty[0]) { + i = 0; + j = 1; + } + /* second raster is empty */ + else { + i = 1; + j = 0; + } + + _rast[j] = rast[j]; + + /* raster is empty, destroy it */ + if (_rast[i] != NULL) + rt_raster_destroy(_rast[i]); + + _dim[i][0] = rt_raster_get_width(_rast[j]); + _dim[i][1] = rt_raster_get_height(_rast[j]); + _dim[j][0] = rt_raster_get_width(_rast[j]); + _dim[j][1] = rt_raster_get_height(_rast[j]); + + _rast[i] = rt_raster_new( + _dim[j][0], + _dim[j][1] + ); + if (_rast[i] == NULL) { + rt_raster_destroy(_rast[j]); + for (k = 0; k < set_count; k++) { + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not create NODATA raster"); + PG_RETURN_NULL(); + } + rt_raster_set_srid(_rast[i], rt_raster_get_srid(_rast[j])); + + rt_raster_get_geotransform_matrix(_rast[j], gt); + rt_raster_set_geotransform_matrix(_rast[i], gt); + } + else { + _rast[0] = rast[0]; + _dim[0][0] = rt_raster_get_width(_rast[0]); + _dim[0][1] = rt_raster_get_height(_rast[0]); + + _rast[1] = rast[1]; + _dim[1][0] = rt_raster_get_width(_rast[1]); + _dim[1][1] = rt_raster_get_height(_rast[1]); + } + + /* SRID must match */ + /* + if (rt_raster_get_srid(_rast[0]) != rt_raster_get_srid(_rast[1])) { + elog(NOTICE, "The two rasters provided have different SRIDs. Returning NULL"); + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + */ + + /* same alignment */ + if (rt_raster_same_alignment(_rast[0], _rast[1], &aligned, NULL) != ES_NONE) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not test for alignment on the two rasters"); + PG_RETURN_NULL(); + } + if (!aligned) { + elog(NOTICE, "The two rasters provided do not have the same alignment. Returning NULL"); + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* pixel type */ + if (!PG_ARGISNULL(5)) { + pixtypename = text_to_cstring(PG_GETARG_TEXT_P(5)); + /* Get the pixel type index */ + pixtype = rt_pixtype_index_from_name(pixtypename); + if (pixtype == PT_END ) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Invalid pixel type: %s", pixtypename); + PG_RETURN_NULL(); + } + } + + /* extent type */ + if (!PG_ARGISNULL(6)) { + extenttypename = rtpg_strtoupper(rtpg_trim(text_to_cstring(PG_GETARG_TEXT_P(6)))); + extenttype = rt_util_extent_type(extenttypename); + } + POSTGIS_RT_DEBUGF(3, "extenttype: %d %s", extenttype, extenttypename); + + /* computed raster from extent type */ + err = rt_raster_from_two_rasters( + _rast[0], _rast[1], + extenttype, + &raster, _offset + ); + if (err != ES_NONE) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not get output raster of correct extent"); + PG_RETURN_NULL(); + } + + /* copy offsets */ + _rastoffset[0][0] = _offset[0]; + _rastoffset[0][1] = _offset[1]; + _rastoffset[1][0] = _offset[2]; + _rastoffset[1][1] = _offset[3]; + + /* get output raster dimensions */ + dim[0] = rt_raster_get_width(raster); + dim[1] = rt_raster_get_height(raster); + + i = 2; + /* handle special cases for extent */ + switch (extenttype) { + case ET_FIRST: + i = 0; + case ET_SECOND: + if (i > 1) + i = 1; + + if ( + _isempty[i] && ( + (extenttype == ET_FIRST && i == 0) || + (extenttype == ET_SECOND && i == 1) + ) + ) { + elog(NOTICE, "The %s raster is NULL. Returning NULL", (i != 1 ? "FIRST" : "SECOND")); + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + PG_RETURN_NULL(); + } + + /* specified band not found */ + if (!rt_raster_has_band(_rast[i], bandindex[i] - 1)) { + elog(NOTICE, "The %s raster does not have the band at index %d. Returning no band raster of correct extent", + (i != 1 ? "FIRST" : "SECOND"), bandindex[i] + ); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgrtn) PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + break; + case ET_UNION: + break; + case ET_INTERSECTION: + /* no intersection */ + if ( + _isempty[0] || _isempty[1] || + !dim[0] || !dim[1] + ) { + elog(NOTICE, "The two rasters provided have no intersection. Returning no band raster"); + + /* raster has dimension, replace with no band raster */ + if (dim[0] || dim[1]) { + rt_raster_destroy(raster); + + raster = rt_raster_new(0, 0); + if (raster == NULL) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: Could not create no band raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(raster, 0, 0); + rt_raster_set_srid(raster, rt_raster_get_srid(_rast[0])); + } + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgrtn) PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + break; + case ET_LAST: + case ET_CUSTOM: + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_mapAlgebra2: ET_LAST and ET_CUSTOM are not implemented"); + PG_RETURN_NULL(); + break; + } + + /* both rasters do not have specified bands */ + if ( + (!_isempty[0] && !rt_raster_has_band(_rast[0], bandindex[0] - 1)) && + (!_isempty[1] && !rt_raster_has_band(_rast[1], bandindex[1] - 1)) + ) { + elog(NOTICE, "The two rasters provided do not have the respectively specified band indices. Returning no band raster of correct extent"); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgrtn) PG_RETURN_NULL(); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); + } + + /* get bands */ + for (i = 0; i < set_count; i++) { + if (_isempty[i] || !rt_raster_has_band(_rast[i], bandindex[i] - 1)) { + _hasnodata[i] = 1; + _nodataval[i] = 0; + + continue; + } + + _band[i] = rt_raster_get_band(_rast[i], bandindex[i] - 1); + if (_band[i] == NULL) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + elog(ERROR, "RASTER_mapAlgebra2: Could not get band %d of the %s raster", + bandindex[i], + (i < 1 ? "FIRST" : "SECOND") + ); + PG_RETURN_NULL(); + } + + _hasnodata[i] = rt_band_get_hasnodata_flag(_band[i]); + if (_hasnodata[i]) + rt_band_get_nodata(_band[i], &(_nodataval[i])); + } + + /* pixtype is PT_END, get pixtype based upon extent */ + if (pixtype == PT_END) { + if ((extenttype == ET_SECOND && !_isempty[1]) || _isempty[0]) + pixtype = rt_band_get_pixtype(_band[1]); + else + pixtype = rt_band_get_pixtype(_band[0]); + } + + /* nodata value for new band */ + if (extenttype == ET_SECOND && !_isempty[1] && _hasnodata[1]) { + nodataval = _nodataval[1]; + } + else if (!_isempty[0] && _hasnodata[0]) { + nodataval = _nodataval[0]; + } + else if (!_isempty[1] && _hasnodata[1]) { + nodataval = _nodataval[1]; + } + else { + elog(NOTICE, "Neither raster provided has a NODATA value for the specified band indices. NODATA value set to minimum possible for %s", rt_pixtype_name(pixtype)); + nodataval = rt_pixtype_get_min_value(pixtype); + } + + /* add band to output raster */ + if (rt_raster_generate_new_band( + raster, + pixtype, + nodataval, + 1, nodataval, + 0 + ) < 0) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + elog(ERROR, "RASTER_mapAlgebra2: Could not add new band to output raster"); + PG_RETURN_NULL(); + } + + /* get output band */ + band = rt_raster_get_band(raster, 0); + if (band == NULL) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + elog(ERROR, "RASTER_mapAlgebra2: Could not get newly added band of output raster"); + PG_RETURN_NULL(); + } + + POSTGIS_RT_DEBUGF(4, "offsets = (%d, %d, %d, %d)", + (int) _rastoffset[0][0], + (int) _rastoffset[0][1], + (int) _rastoffset[1][0], + (int) _rastoffset[1][1] + ); + + POSTGIS_RT_DEBUGF(4, "metadata = (%f, %f, %d, %d, %f, %f, %f, %f, %d)", + rt_raster_get_x_offset(raster), + rt_raster_get_y_offset(raster), + rt_raster_get_width(raster), + rt_raster_get_height(raster), + rt_raster_get_x_scale(raster), + rt_raster_get_y_scale(raster), + rt_raster_get_x_skew(raster), + rt_raster_get_y_skew(raster), + rt_raster_get_srid(raster) + ); + + /* + determine who called this function + Arg 4 will either be text or regprocedure + */ + POSTGIS_RT_DEBUG(3, "checking parameter type for arg 4"); + calltype = get_fn_expr_argtype(fcinfo->flinfo, 4); + + switch(calltype) { + case TEXTOID: { + POSTGIS_RT_DEBUG(3, "arg 4 is \"expression\"!"); + + /* connect SPI */ + if (SPI_connect() != SPI_OK_CONNECT) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + elog(ERROR, "RASTER_mapAlgebra2: Could not connect to the SPI manager"); + PG_RETURN_NULL(); + } + + /* reset hasargval */ + memset(hasargval, 0, sizeof(int) * spi_count); + + /* + process expressions + + spi_exprpos elements are: + 4 - expression => spi_plan[0] + 7 - nodata1expr => spi_plan[1] + 8 - nodata2expr => spi_plan[2] + */ + for (i = 0; i < spi_count; i++) { + if (!PG_ARGISNULL(spi_exprpos[i])) { + char *tmp = NULL; + char place[5] = "$1"; + expr = text_to_cstring(PG_GETARG_TEXT_P(spi_exprpos[i])); + POSTGIS_RT_DEBUGF(3, "raw expr #%d: %s", i, expr); + + for (j = 0, k = 1; j < argkwcount; j++) { + /* attempt to replace keyword with placeholder */ + len = 0; + tmp = rtpg_strreplace(expr, argkw[j], place, &len); + pfree(expr); + expr = tmp; + + if (len) { + spi_argcount[i]++; + argpos[i][j] = k++; + + sprintf(place, "$%d", k); + } + else + argpos[i][j] = 0; + } + + len = strlen("SELECT (") + strlen(expr) + strlen(")::double precision"); + sql = (char *) palloc(len + 1); + if (sql == NULL) { + + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not allocate memory for expression parameter %d", spi_exprpos[i]); + PG_RETURN_NULL(); + } + + strncpy(sql, "SELECT (", strlen("SELECT (")); + strncpy(sql + strlen("SELECT ("), expr, strlen(expr)); + strncpy(sql + strlen("SELECT (") + strlen(expr), ")::double precision", strlen(")::double precision")); + sql[len] = '\0'; + + POSTGIS_RT_DEBUGF(3, "sql #%d: %s", i, sql); + + /* create prepared plan */ + if (spi_argcount[i]) { + argtype = (Oid *) palloc(spi_argcount[i] * sizeof(Oid)); + if (argtype == NULL) { + + pfree(sql); + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not allocate memory for prepared plan argtypes of expression parameter %d", spi_exprpos[i]); + PG_RETURN_NULL(); + } + + /* specify datatypes of parameters */ + for (j = 0, k = 0; j < argkwcount; j++) { + if (argpos[i][j] < 1) continue; + + /* positions are INT4 */ + if ( + (strstr(argkw[j], "[rast1.x]") != NULL) || + (strstr(argkw[j], "[rast1.y]") != NULL) || + (strstr(argkw[j], "[rast2.x]") != NULL) || + (strstr(argkw[j], "[rast2.y]") != NULL) + ) { + argtype[k] = INT4OID; + } + /* everything else is FLOAT8 */ + else { + argtype[k] = FLOAT8OID; + } + + k++; + } + + spi_plan[i] = SPI_prepare(sql, spi_argcount[i], argtype); + pfree(argtype); + + if (spi_plan[i] == NULL) { + + pfree(sql); + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not create prepared plan of expression parameter %d", spi_exprpos[i]); + PG_RETURN_NULL(); + } + } + /* no args, just execute query */ + else { + err = SPI_execute(sql, TRUE, 0); + if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + + pfree(sql); + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not evaluate expression parameter %d", spi_exprpos[i]); + PG_RETURN_NULL(); + } + + /* get output of prepared plan */ + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + pfree(sql); + if (SPI_tuptable) SPI_freetuptable(tuptable); + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not get result of expression parameter %d", spi_exprpos[i]); + PG_RETURN_NULL(); + } + + if (!isnull) { + hasargval[i] = 1; + argval[i] = DatumGetFloat8(datum); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + } + + pfree(sql); + } + else + spi_empty++; + } + + /* nodatanodataval */ + if (!PG_ARGISNULL(9)) { + hasnodatanodataval = 1; + nodatanodataval = PG_GETARG_FLOAT8(9); + } + else + hasnodatanodataval = 0; + break; + } + case REGPROCEDUREOID: { + POSTGIS_RT_DEBUG(3, "arg 4 is \"userfunction\"!"); + if (!PG_ARGISNULL(4)) { + + ufc_nullcount = 0; + ufc_noid = PG_GETARG_OID(4); + + /* get function info */ + fmgr_info(ufc_noid, &ufl_info); + + /* function cannot return set */ + err = 0; + if (ufl_info.fn_retset) { + err = 1; + } + /* function should have correct # of args */ + else if (ufl_info.fn_nargs < 3 || ufl_info.fn_nargs > 4) { + err = 2; + } + + /* + TODO: consider adding checks of the userfunction parameters + should be able to use get_fn_expr_argtype() of fmgr.c + */ + + if (err > 0) { + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + if (err > 1) + elog(ERROR, "RASTER_mapAlgebra2: Function provided must have three or four input parameters"); + else + elog(ERROR, "RASTER_mapAlgebra2: Function provided must return double precision not resultset"); + PG_RETURN_NULL(); + } + + if (func_volatile(ufc_noid) == 'v') { + elog(NOTICE, "Function provided is VOLATILE. Unless required and for best performance, function should be IMMUTABLE or STABLE"); + } + + /* prep function call data */ +#if POSTGIS_PGSQL_VERSION <= 90 + InitFunctionCallInfoData(ufc_info, &ufl_info, ufl_info.fn_nargs, InvalidOid, NULL); +#else + InitFunctionCallInfoData(ufc_info, &ufl_info, ufl_info.fn_nargs, InvalidOid, NULL, NULL); +#endif + memset(ufc_info.argnull, FALSE, sizeof(bool) * ufl_info.fn_nargs); + + if (ufl_info.fn_nargs != 4) + k = 2; + else + k = 3; + if (!PG_ARGISNULL(7)) { + ufc_info.arg[k] = PG_GETARG_DATUM(7); + } + else { + ufc_info.arg[k] = (Datum) NULL; + ufc_info.argnull[k] = TRUE; + ufc_nullcount++; + } + } + break; + } + default: + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + elog(ERROR, "RASTER_mapAlgebra2: Invalid data type for expression or userfunction"); + PG_RETURN_NULL(); + break; + } + + /* loop over pixels */ + /* if any expression present, run */ + if (( + (calltype == TEXTOID) && ( + (spi_empty != spi_count) || hasnodatanodataval + ) + ) || ( + (calltype == REGPROCEDUREOID) && (ufc_noid != InvalidOid) + )) { + for (x = 0; x < dim[0]; x++) { + for (y = 0; y < dim[1]; y++) { + + /* get pixel from each raster */ + for (i = 0; i < set_count; i++) { + _haspixel[i] = 0; + _pixel[i] = 0; + + /* row/column */ + _x = x - (int) _rastoffset[i][0]; + _y = y - (int) _rastoffset[i][1]; + + /* store _x and _y in 1-based */ + _pos[i][0] = _x + 1; + _pos[i][1] = _y + 1; + + /* get pixel value */ + if (_band[i] == NULL) { + if (!_hasnodata[i]) { + _haspixel[i] = 1; + _pixel[i] = _nodataval[i]; + } + } + else if ( + !_isempty[i] && + (_x >= 0 && _x < _dim[i][0]) && + (_y >= 0 && _y < _dim[i][1]) + ) { + err = rt_band_get_pixel(_band[i], _x, _y, &(_pixel[i]), &isnodata); + if (err != ES_NONE) { + + if (calltype == TEXTOID) { + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + } + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not get pixel of %s raster", (i < 1 ? "FIRST" : "SECOND")); + PG_RETURN_NULL(); + } + + if (!_hasnodata[i] || !isnodata) + _haspixel[i] = 1; + } + + POSTGIS_RT_DEBUGF(5, "pixel r%d(%d, %d) = %d, %f", + i, + _x, _y, + _haspixel[i], + _pixel[i] + ); + } + + haspixel = 0; + + switch (calltype) { + case TEXTOID: { + /* which prepared plan to use? */ + /* !pixel0 && !pixel1 */ + /* use nodatanodataval */ + if (!_haspixel[0] && !_haspixel[1]) + i = 3; + /* pixel0 && !pixel1 */ + /* run spi_plan[2] (nodata2expr) */ + else if (_haspixel[0] && !_haspixel[1]) + i = 2; + /* !pixel0 && pixel1 */ + /* run spi_plan[1] (nodata1expr) */ + else if (!_haspixel[0] && _haspixel[1]) + i = 1; + /* pixel0 && pixel1 */ + /* run spi_plan[0] (expression) */ + else + i = 0; + + /* process values */ + if (i == 3) { + if (hasnodatanodataval) { + haspixel = 1; + pixel = nodatanodataval; + } + } + /* has an evaluated value */ + else if (hasargval[i]) { + haspixel = 1; + pixel = argval[i]; + } + /* prepared plan exists */ + else if (spi_plan[i] != NULL) { + POSTGIS_RT_DEBUGF(4, "Using prepared plan: %d", i); + + /* expression has argument(s) */ + if (spi_argcount[i]) { + /* reset values to (Datum) NULL */ + memset(values, (Datum) NULL, sizeof(Datum) * argkwcount); + /* reset nulls to FALSE */ + memset(nulls, FALSE, sizeof(bool) * argkwcount); + + /* set values and nulls */ + for (j = 0; j < argkwcount; j++) { + idx = argpos[i][j]; + if (idx < 1) continue; + idx--; /* 1-based becomes 0-based */ + + if (strstr(argkw[j], "[rast1.x]") != NULL) { + values[idx] = _pos[0][0]; + } + else if (strstr(argkw[j], "[rast1.y]") != NULL) { + values[idx] = _pos[0][1]; + } + else if ( + (strstr(argkw[j], "[rast1.val]") != NULL) || + (strstr(argkw[j], "[rast1]") != NULL) + ) { + if (_isempty[0] || !_haspixel[0]) + nulls[idx] = TRUE; + else + values[idx] = Float8GetDatum(_pixel[0]); + } + else if (strstr(argkw[j], "[rast2.x]") != NULL) { + values[idx] = _pos[1][0]; + } + else if (strstr(argkw[j], "[rast2.y]") != NULL) { + values[idx] = _pos[1][1]; + } + else if ( + (strstr(argkw[j], "[rast2.val]") != NULL) || + (strstr(argkw[j], "[rast2]") != NULL) + ) { + if (_isempty[1] || !_haspixel[1]) + nulls[idx] = TRUE; + else + values[idx] = Float8GetDatum(_pixel[1]); + } + } + } + + /* run prepared plan */ + err = SPI_execute_plan(spi_plan[i], values, nulls, TRUE, 1); + if (err != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Unexpected error when running prepared statement %d", i); + PG_RETURN_NULL(); + } + + /* get output of prepared plan */ + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isnull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not get result of prepared statement %d", i); + PG_RETURN_NULL(); + } + + if (!isnull) { + haspixel = 1; + pixel = DatumGetFloat8(datum); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + } + } break; + case REGPROCEDUREOID: { + Datum d[4]; + ArrayType *a; + + /* build fcnarg */ + for (i = 0; i < set_count; i++) { + ufc_info.arg[i] = Float8GetDatum(_pixel[i]); + + if (_haspixel[i]) { + ufc_info.argnull[i] = FALSE; + ufc_nullcount--; + } + else { + ufc_info.argnull[i] = TRUE; + ufc_nullcount++; + } + } + + /* function is strict and null parameter is passed */ + /* http://archives.postgresql.org/pgsql-general/2011-11/msg00424.php */ + if (ufl_info.fn_strict && ufc_nullcount) + break; + + /* 4 parameters, add position */ + if (ufl_info.fn_nargs == 4) { + /* Datum of 4 element array */ + /* array is (x1, y1, x2, y2) */ + for (i = 0; i < set_count; i++) { + if (i < 1) { + d[0] = Int32GetDatum(_pos[i][0]); + d[1] = Int32GetDatum(_pos[i][1]); + } + else { + d[2] = Int32GetDatum(_pos[i][0]); + d[3] = Int32GetDatum(_pos[i][1]); + } + } + + a = construct_array(d, 4, INT4OID, sizeof(int32), true, 'i'); + ufc_info.arg[2] = PointerGetDatum(a); + ufc_info.argnull[2] = FALSE; + } + + datum = FunctionCallInvoke(&ufc_info); + + /* result is not null*/ + if (!ufc_info.isnull) { + haspixel = 1; + pixel = DatumGetFloat8(datum); + } + } break; + } + + /* burn pixel if haspixel != 0 */ + if (haspixel) { + if (rt_band_set_pixel(band, x, y, pixel, NULL) != ES_NONE) { + + if (calltype == TEXTOID) { + for (k = 0; k < spi_count; k++) SPI_freeplan(spi_plan[k]); + SPI_finish(); + } + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + rt_raster_destroy(raster); + + elog(ERROR, "RASTER_mapAlgebra2: Could not set pixel value of output raster"); + PG_RETURN_NULL(); + } + } + + POSTGIS_RT_DEBUGF(5, "(x, y, val) = (%d, %d, %f)", x, y, haspixel ? pixel : nodataval); + + } /* y: height */ + } /* x: width */ + } + + /* CLEANUP */ + if (calltype == TEXTOID) { + for (i = 0; i < spi_count; i++) { + if (spi_plan[i] != NULL) SPI_freeplan(spi_plan[i]); + } + SPI_finish(); + } + + for (k = 0; k < set_count; k++) { + if (_rast[k] != NULL) + rt_raster_destroy(_rast[k]); + if (pgrastpos[k] != -1) + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + pgrtn = rt_raster_serialize(raster); + rt_raster_destroy(raster); + if (!pgrtn) PG_RETURN_NULL(); + + POSTGIS_RT_DEBUG(3, "Finished RASTER_mapAlgebra2"); + + SET_VARSIZE(pgrtn, pgrtn->size); + PG_RETURN_POINTER(pgrtn); +} diff --git a/raster/rt_pg/rtpg_pixel.c b/raster/rt_pg/rtpg_pixel.c new file mode 100644 index 000000000..7b93ba20c --- /dev/null +++ b/raster/rt_pg/rtpg_pixel.c @@ -0,0 +1,2278 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ +#include +#include "utils/array.h" /* for ArrayType */ +#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ + +#include "rtpostgis.h" + +/* Get pixel value */ +Datum RASTER_getPixelValue(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); + +/* 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); +} + +/* ---------------------------------------------------------------- */ +/* 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) { + if (arg->nbands != NULL) + pfree(arg->nbands); + + 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]); + } + + if (arg->values != NULL) + pfree(arg->values); + if (arg->nodata != NULL) + pfree(arg->nodata); + } + + pfree(arg); +} + +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); + } + } + + } + 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++) { + 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) { + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + ArrayType *mdValues = NULL; + 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); + + /* assemble 3-dimension array of values */ + mdValues = construct_md_array( + arg2->values[call_cntr], arg2->nodata[call_cntr], + 2, 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; + int 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; + unsigned char *wkb = NULL; + size_t wkb_len; + + int i = 0; + int 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 intialize 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(arg->gv[arg->ngv].geom, WKB_SFSQL, &wkb_len); + + /* rasterize geometry */ + arg->gv[arg->ngv].mask = rt_raster_gdal_rasterize( + wkb, wkb_len, + 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, + 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); +} + +/** + * 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) { + int values_length = 3; + 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 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(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, + _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); +} + diff --git a/raster/rt_pg/rtpg_raster_properties.c b/raster/rt_pg/rtpg_raster_properties.c new file mode 100644 index 000000000..368b04070 --- /dev/null +++ b/raster/rt_pg/rtpg_raster_properties.c @@ -0,0 +1,1172 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#if POSTGIS_PGSQL_VERSION > 92 +#include "access/htup_details.h" /* for heap_form_tuple() */ +#endif + +#include "rtpostgis.h" + +/* Get all the properties of a raster */ +Datum RASTER_getSRID(PG_FUNCTION_ARGS); +Datum RASTER_getWidth(PG_FUNCTION_ARGS); +Datum RASTER_getHeight(PG_FUNCTION_ARGS); +Datum RASTER_getNumBands(PG_FUNCTION_ARGS); +Datum RASTER_getXScale(PG_FUNCTION_ARGS); +Datum RASTER_getYScale(PG_FUNCTION_ARGS); +Datum RASTER_getXSkew(PG_FUNCTION_ARGS); +Datum RASTER_getYSkew(PG_FUNCTION_ARGS); +Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS); +Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS); +Datum RASTER_getPixelWidth(PG_FUNCTION_ARGS); +Datum RASTER_getPixelHeight(PG_FUNCTION_ARGS); +Datum RASTER_getGeotransform(PG_FUNCTION_ARGS); +Datum RASTER_isEmpty(PG_FUNCTION_ARGS); +Datum RASTER_hasNoBand(PG_FUNCTION_ARGS); + +/* get raster's meta data */ +Datum RASTER_metadata(PG_FUNCTION_ARGS); + +/* convert pixel/line to spatial coordinates */ +Datum RASTER_rasterToWorldCoord(PG_FUNCTION_ARGS); + +/* convert spatial coordinates to pixel/line*/ +Datum RASTER_worldToRasterCoord(PG_FUNCTION_ARGS); + +/* Set all the properties of a raster */ +Datum RASTER_setSRID(PG_FUNCTION_ARGS); +Datum RASTER_setScale(PG_FUNCTION_ARGS); +Datum RASTER_setScaleXY(PG_FUNCTION_ARGS); +Datum RASTER_setSkew(PG_FUNCTION_ARGS); +Datum RASTER_setSkewXY(PG_FUNCTION_ARGS); +Datum RASTER_setUpperLeftXY(PG_FUNCTION_ARGS); +Datum RASTER_setRotation(PG_FUNCTION_ARGS); +Datum RASTER_setGeotransform(PG_FUNCTION_ARGS); + +/** + * Return the SRID associated with the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getSRID); +Datum RASTER_getSRID(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + int32_t srid; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getSRID: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + srid = rt_raster_get_srid(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_INT32(srid); +} + +/** + * Return the width of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getWidth); +Datum RASTER_getWidth(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + uint16_t width; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getWidth: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + width = rt_raster_get_width(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_INT32(width); +} + +/** + * Return the height of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getHeight); +Datum RASTER_getHeight(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + uint16_t height; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getHeight: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + height = rt_raster_get_height(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_INT32(height); +} + +/** + * Return the number of bands included in the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getNumBands); +Datum RASTER_getNumBands(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + int32_t num_bands; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getNumBands: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + num_bands = rt_raster_get_num_bands(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_INT32(num_bands); +} + +/** + * Return X scale from georeference of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getXScale); +Datum RASTER_getXScale(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double xsize; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getXScale: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + xsize = rt_raster_get_x_scale(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(xsize); +} + +/** + * Return Y scale from georeference of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_getYScale); +Datum RASTER_getYScale(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double ysize; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getYScale: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + ysize = rt_raster_get_y_scale(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(ysize); +} + +/** + * Return value of the raster skew about the X axis. + */ +PG_FUNCTION_INFO_V1(RASTER_getXSkew); +Datum RASTER_getXSkew(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double xskew; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getXSkew: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + xskew = rt_raster_get_x_skew(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(xskew); +} + +/** + * Return value of the raster skew about the Y axis. + */ +PG_FUNCTION_INFO_V1(RASTER_getYSkew); +Datum RASTER_getYSkew(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double yskew; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getYSkew: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + yskew = rt_raster_get_y_skew(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(yskew); +} + +/** + * Return value of the raster offset in the X dimension. + */ +PG_FUNCTION_INFO_V1(RASTER_getXUpperLeft); +Datum RASTER_getXUpperLeft(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double xul; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getXUpperLeft: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + xul = rt_raster_get_x_offset(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(xul); +} + +/** + * Return value of the raster offset in the Y dimension. + */ +PG_FUNCTION_INFO_V1(RASTER_getYUpperLeft); +Datum RASTER_getYUpperLeft(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double yul; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getYUpperLeft: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + yul = rt_raster_get_y_offset(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(yul); +} + +/** + * Return the pixel width of the raster. The pixel width is + * a read-only, dynamically computed value derived from the + * X Scale and the Y Skew. + * + * Pixel Width = sqrt( X Scale * X Scale + Y Skew * Y Skew ) + */ +PG_FUNCTION_INFO_V1(RASTER_getPixelWidth); +Datum RASTER_getPixelWidth(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double xscale; + double yskew; + double pwidth; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getPixelWidth: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + xscale = rt_raster_get_x_scale(raster); + yskew = rt_raster_get_y_skew(raster); + pwidth = sqrt(xscale*xscale + yskew*yskew); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(pwidth); +} + +/** + * Return the pixel height of the raster. The pixel height is + * a read-only, dynamically computed value derived from the + * Y Scale and the X Skew. + * + * Pixel Height = sqrt( Y Scale * Y Scale + X Skew * X Skew ) + */ +PG_FUNCTION_INFO_V1(RASTER_getPixelHeight); +Datum RASTER_getPixelHeight(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster; + rt_raster raster; + double yscale; + double xskew; + double pheight; + + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getPixelHeight: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + yscale = rt_raster_get_y_scale(raster); + xskew = rt_raster_get_x_skew(raster); + pheight = sqrt(yscale*yscale + xskew*xskew); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_FLOAT8(pheight); +} + +/** + * Calculates the physically relevant parameters of the supplied raster's + * geotransform. Returns them as a set. + */ +PG_FUNCTION_INFO_V1(RASTER_getGeotransform); +Datum RASTER_getGeotransform(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + + double imag; + double jmag; + double theta_i; + double theta_ij; + /* + double xoffset; + double yoffset; + */ + + TupleDesc result_tuple; /* for returning a composite */ + int values_length = 6; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple heap_tuple ; /* instance of the tuple to return */ + Datum result; + + POSTGIS_RT_DEBUG(3, "RASTER_getGeotransform: Starting"); + + /* get argument */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_getGeotransform: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* do the calculation */ + rt_raster_calc_phys_params( + rt_raster_get_x_scale(raster), + rt_raster_get_x_skew(raster), + rt_raster_get_y_skew(raster), + rt_raster_get_y_scale(raster), + &imag, &jmag, &theta_i, &theta_ij) ; + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* setup the return value infrastructure */ + if (get_call_result_type(fcinfo, NULL, &result_tuple) != TYPEFUNC_COMPOSITE) { + ereport(ERROR, ( + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RASTER_getGeotransform(): function returning record called in context that cannot accept type record" + ) + )); + PG_RETURN_NULL(); + } + + BlessTupleDesc(result_tuple); + + /* get argument */ + /* prep the composite return value */ + /* construct datum array */ + values[0] = Float8GetDatum(imag); + values[1] = Float8GetDatum(jmag); + values[2] = Float8GetDatum(theta_i); + values[3] = Float8GetDatum(theta_ij); + values[4] = Float8GetDatum(rt_raster_get_x_offset(raster)); + values[5] = Float8GetDatum(rt_raster_get_y_offset(raster)); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* stick em on the heap */ + heap_tuple = heap_form_tuple(result_tuple, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(heap_tuple); + + PG_RETURN_DATUM(result); +} + +/** + * Check if raster is empty or not + */ +PG_FUNCTION_INFO_V1(RASTER_isEmpty); +Datum RASTER_isEmpty(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + bool isempty = FALSE; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) + { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("RASTER_isEmpty: Could not deserialize raster"))); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + isempty = rt_raster_is_empty(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_BOOL(isempty); +} + +/** + * Check if the raster has a given band or not + */ +PG_FUNCTION_INFO_V1(RASTER_hasNoBand); +Datum RASTER_hasNoBand(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + int bandindex = 0; + bool hasnoband = FALSE; + + /* Deserialize raster */ + if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + raster = rt_raster_deserialize(pgraster, TRUE); + if ( ! raster ) + { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("RASTER_hasNoBand: Could not deserialize raster"))); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + /* Get band number */ + bandindex = PG_GETARG_INT32(1); + hasnoband = !rt_raster_has_band(raster, bandindex - 1); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + PG_RETURN_BOOL(hasnoband); +} + +/** + * Get raster's meta data + */ +PG_FUNCTION_INFO_V1(RASTER_metadata); +Datum RASTER_metadata(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + + uint32_t numBands; + double scaleX; + double scaleY; + double ipX; + double ipY; + double skewX; + double skewY; + int32_t srid; + uint32_t width; + uint32_t height; + + TupleDesc tupdesc; + int values_length = 10; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUG(3, "RASTER_metadata: Starting"); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_metadata; Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* upper left x, y */ + ipX = rt_raster_get_x_offset(raster); + ipY = rt_raster_get_y_offset(raster); + + /* width, height */ + width = rt_raster_get_width(raster); + height = rt_raster_get_height(raster); + + /* scale x, y */ + scaleX = rt_raster_get_x_scale(raster); + scaleY = rt_raster_get_y_scale(raster); + + /* skew x, y */ + skewX = rt_raster_get_x_skew(raster); + skewY = rt_raster_get_y_skew(raster); + + /* srid */ + srid = rt_raster_get_srid(raster); + + /* numbands */ + numBands = rt_raster_get_num_bands(raster); + + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* 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); + + values[0] = Float8GetDatum(ipX); + values[1] = Float8GetDatum(ipY); + values[2] = UInt32GetDatum(width); + values[3] = UInt32GetDatum(height); + values[4] = Float8GetDatum(scaleX); + values[5] = Float8GetDatum(scaleY); + values[6] = Float8GetDatum(skewX); + values[7] = Float8GetDatum(skewY); + values[8] = Int32GetDatum(srid); + values[9] = UInt32GetDatum(numBands); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(RASTER_rasterToWorldCoord); +Datum RASTER_rasterToWorldCoord(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + int i = 0; + int cr[2] = {0}; + bool skewed[2] = {false, false}; + double cw[2] = {0}; + + TupleDesc tupdesc; + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUG(3, "RASTER_rasterToWorldCoord: Starting"); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_rasterToWorldCoord: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* raster skewed? */ + skewed[0] = FLT_NEQ(rt_raster_get_x_skew(raster), 0) ? true : false; + skewed[1] = FLT_NEQ(rt_raster_get_y_skew(raster), 0) ? true : false; + + /* column and row */ + for (i = 1; i <= 2; i++) { + if (PG_ARGISNULL(i)) { + /* if skewed on same axis, parameter is required */ + if (skewed[i - 1]) { + elog(NOTICE, "Pixel row and column required for computing longitude and latitude of a rotated raster"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + continue; + } + + cr[i - 1] = PG_GETARG_INT32(i); + } + + /* user-provided value is 1-based but needs to be 0-based */ + if (rt_raster_cell_to_geopoint( + raster, + (double) cr[0] - 1, (double) cr[1] - 1, + &(cw[0]), &(cw[1]), + NULL + ) != ES_NONE) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_rasterToWorldCoord: Could not compute longitude and latitude from pixel row and column"); + PG_RETURN_NULL(); + } + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* 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); + + values[0] = Float8GetDatum(cw[0]); + values[1] = Float8GetDatum(cw[1]); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(RASTER_worldToRasterCoord); +Datum RASTER_worldToRasterCoord(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + int i = 0; + double cw[2] = {0}; + double _cr[2] = {0}; + int cr[2] = {0}; + bool skewed = false; + + TupleDesc tupdesc; + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUG(3, "RASTER_worldToRasterCoord: Starting"); + + /* pgraster is null, return null */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, sizeof(struct rt_raster_serialized_t)); + + /* raster */ + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_worldToRasterCoord: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* raster skewed? */ + skewed = FLT_NEQ(rt_raster_get_x_skew(raster), 0) ? true : false; + if (!skewed) + skewed = FLT_NEQ(rt_raster_get_y_skew(raster), 0) ? true : false; + + /* longitude and latitude */ + for (i = 1; i <= 2; i++) { + if (PG_ARGISNULL(i)) { + /* if skewed, parameter is required */ + if (skewed) { + elog(NOTICE, "Latitude and longitude required for computing pixel row and column of a rotated raster"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + + continue; + } + + cw[i - 1] = PG_GETARG_FLOAT8(i); + } + + /* return pixel row and column values are 0-based */ + if (rt_raster_geopoint_to_cell( + raster, + cw[0], cw[1], + &(_cr[0]), &(_cr[1]), + NULL + ) != ES_NONE) { + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_worldToRasterCoord: Could not compute pixel row and column from longitude and latitude"); + PG_RETURN_NULL(); + } + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + + /* force to integer and add one to make 1-based */ + cr[0] = ((int) _cr[0]) + 1; + cr[1] = ((int) _cr[1]) + 1; + + /* 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); + + values[0] = Int32GetDatum(cr[0]); + values[1] = Int32GetDatum(cr[1]); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +/** + * Set the SRID associated with the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setSRID); +Datum RASTER_setSRID(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + int32_t newSRID = PG_GETARG_INT32(1); + + 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_setSRID: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_srid(raster, newSRID); + + 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 the scale of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setScale); +Datum RASTER_setScale(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double size = PG_GETARG_FLOAT8(1); + + 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_setScale: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(raster, size, size); + + 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 the pixel size of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setScaleXY); +Datum RASTER_setScaleXY(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double xscale = PG_GETARG_FLOAT8(1); + double yscale = PG_GETARG_FLOAT8(2); + + 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_setScaleXY: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_scale(raster, xscale, yscale); + 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 the skew of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setSkew); +Datum RASTER_setSkew(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double skew = PG_GETARG_FLOAT8(1); + + 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_setSkew: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_skews(raster, skew, skew); + + 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 the skew of the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setSkewXY); +Datum RASTER_setSkewXY(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double xskew = PG_GETARG_FLOAT8(1); + double yskew = PG_GETARG_FLOAT8(2); + + 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_setSkewXY: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_skews(raster, xskew, yskew); + + 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 the raster offset in the X and Y dimension. + */ +PG_FUNCTION_INFO_V1(RASTER_setUpperLeftXY); +Datum RASTER_setUpperLeftXY(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double xoffset = PG_GETARG_FLOAT8(1); + double yoffset = PG_GETARG_FLOAT8(2); + + 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_setUpperLeftXY: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + rt_raster_set_offsets(raster, xoffset, yoffset); + + 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 the geotransform of the supplied raster. Returns the raster. + */ +PG_FUNCTION_INFO_V1(RASTER_setGeotransform); +Datum RASTER_setGeotransform(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + float8 imag, jmag, theta_i, theta_ij, xoffset, yoffset; + + if ( + PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || + PG_ARGISNULL(3) || PG_ARGISNULL(4) || + PG_ARGISNULL(5) || PG_ARGISNULL(6) + ) { + PG_RETURN_NULL(); + } + + /* get the inputs */ + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + imag = PG_GETARG_FLOAT8(1); + jmag = PG_GETARG_FLOAT8(2); + theta_i = PG_GETARG_FLOAT8(3); + theta_ij = PG_GETARG_FLOAT8(4); + xoffset = PG_GETARG_FLOAT8(5); + yoffset = PG_GETARG_FLOAT8(6); + + raster = rt_raster_deserialize(pgraster, TRUE); + if (!raster) { + PG_FREE_IF_COPY(pgraster, 0); + elog(ERROR, "RASTER_setGeotransform: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* store the new geotransform */ + rt_raster_set_phys_params(raster, imag,jmag,theta_i,theta_ij); + rt_raster_set_offsets(raster, xoffset, yoffset); + + /* prep the return value */ + 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 the rotation of the raster. This method will change the X Scale, + * Y Scale, X Skew and Y Skew properties all at once to keep the rotations + * about the X and Y axis uniform. + * + * This method will set the rotation about the X axis and Y axis based on + * the pixel size. This pixel size may not be uniform if rasters have different + * skew values (the raster cells are diamond-shaped). If a raster has different + * skew values has a rotation set upon it, this method will remove the + * diamond distortions of the cells, as each axis will have the same rotation. + */ +PG_FUNCTION_INFO_V1(RASTER_setRotation); +Datum RASTER_setRotation(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_pgraster *pgrtn = NULL; + rt_raster raster; + double rotation = PG_GETARG_FLOAT8(1); + double imag, jmag, theta_i, theta_ij; + + 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_setRotation: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* preserve all defining characteristics of the grid except for rotation */ + rt_raster_get_phys_params(raster, &imag, &jmag, &theta_i, &theta_ij); + rt_raster_set_phys_params(raster, imag, jmag, rotation, theta_ij); + + 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); +} diff --git a/raster/rt_pg/rtpg_spatial_relationship.c b/raster/rt_pg/rtpg_spatial_relationship.c new file mode 100644 index 000000000..85053a0ca --- /dev/null +++ b/raster/rt_pg/rtpg_spatial_relationship.c @@ -0,0 +1,1315 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include /* for palloc */ +#include + +#include "../../postgis_config.h" + +#include "lwgeom_pg.h" +#include "rtpostgis.h" + +/* determine if two rasters intersect */ +Datum RASTER_intersects(PG_FUNCTION_ARGS); + +/* determine if two rasters overlap */ +Datum RASTER_overlaps(PG_FUNCTION_ARGS); + +/* determine if two rasters touch */ +Datum RASTER_touches(PG_FUNCTION_ARGS); + +/* determine if the first raster contains the second raster */ +Datum RASTER_contains(PG_FUNCTION_ARGS); + +/* determine if the first raster contains properly the second raster */ +Datum RASTER_containsProperly(PG_FUNCTION_ARGS); + +/* determine if the first raster covers the second raster */ +Datum RASTER_covers(PG_FUNCTION_ARGS); + +/* determine if the first raster is covered by the second raster */ +Datum RASTER_coveredby(PG_FUNCTION_ARGS); + +/* determine if the two rasters are within the specified distance of each other */ +Datum RASTER_dwithin(PG_FUNCTION_ARGS); + +/* determine if the two rasters are fully within the specified distance of each other */ +Datum RASTER_dfullywithin(PG_FUNCTION_ARGS); + +/* determine if two rasters are aligned */ +Datum RASTER_sameAlignment(PG_FUNCTION_ARGS); +Datum RASTER_notSameAlignmentReason(PG_FUNCTION_ARGS); + +/** + * See if two rasters intersect + */ +PG_FUNCTION_INFO_V1(RASTER_intersects); +Datum RASTER_intersects(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_intersects: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_intersects( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_intersects: Could not test for intersection on the two rasters"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if two rasters overlap + */ +PG_FUNCTION_INFO_V1(RASTER_overlaps); +Datum RASTER_overlaps(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_overlaps: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_overlaps( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_overlaps: Could not test for overlap on the two rasters"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if two rasters touch + */ +PG_FUNCTION_INFO_V1(RASTER_touches); +Datum RASTER_touches(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_touches: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_touches( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_touches: Could not test for touch on the two rasters"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the first raster contains the second raster + */ +PG_FUNCTION_INFO_V1(RASTER_contains); +Datum RASTER_contains(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_contains: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_contains( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_contains: Could not test that the first raster contains the second raster"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the first raster contains properly the second raster + */ +PG_FUNCTION_INFO_V1(RASTER_containsProperly); +Datum RASTER_containsProperly(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_containsProperly: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_contains_properly( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_containsProperly: Could not test that the first raster contains properly the second raster"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the first raster covers the second raster + */ +PG_FUNCTION_INFO_V1(RASTER_covers); +Datum RASTER_covers(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_covers: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_covers( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_covers: Could not test that the first raster covers the second raster"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the first raster is covered by the second raster + */ +PG_FUNCTION_INFO_V1(RASTER_coveredby); +Datum RASTER_coveredby(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_coveredby: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_coveredby( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_coveredby: Could not test that the first raster is covered by the second raster"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the two rasters are within the specified distance of each other + */ +PG_FUNCTION_INFO_V1(RASTER_dwithin); +Datum RASTER_dwithin(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + double distance = 0; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_dwithin: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* distance */ + if (PG_ARGISNULL(4)) { + elog(NOTICE, "Distance cannot be NULL. Returning NULL"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + distance = PG_GETARG_FLOAT8(4); + if (distance < 0) { + elog(NOTICE, "Distance cannot be less than zero. Returning NULL"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_within_distance( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + distance, + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_dwithin: Could not test that the two rasters are within the specified distance of each other"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if the two rasters are fully within the specified distance of each other + */ +PG_FUNCTION_INFO_V1(RASTER_dfullywithin); +Datum RASTER_dfullywithin(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + uint32_t bandindex[2] = {0}; + uint32_t hasbandindex[2] = {0}; + double distance = 0; + + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t numBands; + int rtn; + int result; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(j)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], FALSE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_dfullywithin: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + + /* numbands */ + numBands = rt_raster_get_num_bands(rast[i]); + if (numBands < 1) { + elog(NOTICE, "The %s raster provided has no bands", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* band index */ + if (!PG_ARGISNULL(j)) { + bandindex[i] = PG_GETARG_INT32(j); + if (bandindex[i] < 1 || bandindex[i] > numBands) { + elog(NOTICE, "Invalid band index (must use 1-based) for the %s raster. Returning NULL", i < 1 ? "first" : "second"); + if (i > 0) i++; + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + hasbandindex[i] = 1; + } + else + hasbandindex[i] = 0; + POSTGIS_RT_DEBUGF(4, "hasbandindex[%d] = %d", i, hasbandindex[i]); + POSTGIS_RT_DEBUGF(4, "bandindex[%d] = %d", i, bandindex[i]); + j++; + } + + /* distance */ + if (PG_ARGISNULL(4)) { + elog(NOTICE, "Distance cannot be NULL. Returning NULL"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + distance = PG_GETARG_FLOAT8(4); + if (distance < 0) { + elog(NOTICE, "Distance cannot be less than zero. Returning NULL"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* hasbandindex must be balanced */ + if ( + (hasbandindex[0] && !hasbandindex[1]) || + (!hasbandindex[0] && hasbandindex[1]) + ) { + elog(NOTICE, "Missing band index. Band indices must be provided for both rasters if any one is provided"); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + + /* SRID must match */ + if (rt_raster_get_srid(rast[0]) != rt_raster_get_srid(rast[1])) { + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "The two rasters provided have different SRIDs"); + PG_RETURN_NULL(); + } + + rtn = rt_raster_fully_within_distance( + rast[0], (hasbandindex[0] ? bandindex[0] - 1 : -1), + rast[1], (hasbandindex[1] ? bandindex[1] - 1 : -1), + distance, + &result + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_dfullywithin: Could not test that the two rasters are fully within the specified distance of each other"); + PG_RETURN_NULL(); + } + + PG_RETURN_BOOL(result); +} + +/** + * See if two rasters are aligned + */ +PG_FUNCTION_INFO_V1(RASTER_sameAlignment); +Datum RASTER_sameAlignment(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + + uint32_t i; + uint32_t j; + uint32_t k; + int rtn; + int aligned = 0; + char *reason = NULL; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(j), 0, sizeof(struct rt_raster_serialized_t)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], TRUE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_sameAlignment: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + } + + rtn = rt_raster_same_alignment( + rast[0], + rast[1], + &aligned, + &reason + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_sameAlignment: Could not test for alignment on the two rasters"); + PG_RETURN_NULL(); + } + + /* only output reason if not aligned */ + if (reason != NULL && !aligned) + elog(NOTICE, "%s", reason); + + PG_RETURN_BOOL(aligned); +} + +/** + * Return a reason why two rasters are not aligned + */ +PG_FUNCTION_INFO_V1(RASTER_notSameAlignmentReason); +Datum RASTER_notSameAlignmentReason(PG_FUNCTION_ARGS) +{ + const int set_count = 2; + rt_pgraster *pgrast[2]; + int pgrastpos[2] = {-1, -1}; + rt_raster rast[2] = {NULL}; + + uint32_t i; + uint32_t j; + uint32_t k; + int rtn; + int aligned = 0; + char *reason = NULL; + text *result = NULL; + + for (i = 0, j = 0; i < set_count; i++) { + /* pgrast is null, return null */ + if (PG_ARGISNULL(j)) { + for (k = 0; k < i; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + PG_RETURN_NULL(); + } + pgrast[i] = (rt_pgraster *) PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(j), 0, sizeof(struct rt_raster_serialized_t)); + pgrastpos[i] = j; + j++; + + /* raster */ + rast[i] = rt_raster_deserialize(pgrast[i], TRUE); + if (!rast[i]) { + for (k = 0; k <= i; k++) { + if (k < i) + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + elog(ERROR, "RASTER_notSameAlignmentReason: Could not deserialize the %s raster", i < 1 ? "first" : "second"); + PG_RETURN_NULL(); + } + } + + rtn = rt_raster_same_alignment( + rast[0], + rast[1], + &aligned, + &reason + ); + for (k = 0; k < set_count; k++) { + rt_raster_destroy(rast[k]); + PG_FREE_IF_COPY(pgrast[k], pgrastpos[k]); + } + + if (rtn != ES_NONE) { + elog(ERROR, "RASTER_notSameAlignmentReason: Could not test for alignment on the two rasters"); + PG_RETURN_NULL(); + } + + result = cstring2text(reason); + PG_RETURN_TEXT_P(result); +} diff --git a/raster/rt_pg/rtpg_statistics.c b/raster/rt_pg/rtpg_statistics.c new file mode 100644 index 000000000..453c26a4d --- /dev/null +++ b/raster/rt_pg/rtpg_statistics.c @@ -0,0 +1,2617 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include /* for text_to_cstring() */ +#include "utils/lsyscache.h" /* for get_typlenbyvalalign */ +#include "utils/array.h" /* for ArrayType */ +#include "catalog/pg_type.h" /* for INT2OID, INT4OID, FLOAT4OID, FLOAT8OID and TEXTOID */ +#include +#include /* for SRF */ + +#include "rtpostgis.h" + +/* Get summary stats */ +Datum RASTER_summaryStats(PG_FUNCTION_ARGS); +Datum RASTER_summaryStatsCoverage(PG_FUNCTION_ARGS); + +/* get histogram */ +Datum RASTER_histogram(PG_FUNCTION_ARGS); +Datum RASTER_histogramCoverage(PG_FUNCTION_ARGS); + +/* get quantiles */ +Datum RASTER_quantile(PG_FUNCTION_ARGS); +Datum RASTER_quantileCoverage(PG_FUNCTION_ARGS); + +/* get counts of values */ +Datum RASTER_valueCount(PG_FUNCTION_ARGS); +Datum RASTER_valueCountCoverage(PG_FUNCTION_ARGS); + +/** + * Get summary stats of a band + */ +PG_FUNCTION_INFO_V1(RASTER_summaryStats); +Datum RASTER_summaryStats(PG_FUNCTION_ARGS) +{ + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex = 1; + bool exclude_nodata_value = TRUE; + int num_bands = 0; + double sample = 0; + rt_bandstats stats = NULL; + + TupleDesc tupdesc; + int values_length = 6; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + /* pgraster is null, return null */ + 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_summaryStats: 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(); + } + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(2)) + exclude_nodata_value = PG_GETARG_BOOL(2); + + /* sample % */ + if (!PG_ARGISNULL(3)) { + sample = PG_GETARG_FLOAT8(3); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + PG_RETURN_NULL(); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* 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(); + } + + /* we don't need the raw values, hence the zero parameter */ + stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 0, NULL, NULL, NULL); + rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (NULL == stats) { + elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); + PG_RETURN_NULL(); + } + + /* 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); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Int64GetDatum(stats->count); + if (stats->count > 0) { + values[1] = Float8GetDatum(stats->sum); + values[2] = Float8GetDatum(stats->mean); + values[3] = Float8GetDatum(stats->stddev); + values[4] = Float8GetDatum(stats->min); + values[5] = Float8GetDatum(stats->max); + } + else { + nulls[1] = TRUE; + nulls[2] = TRUE; + nulls[3] = TRUE; + nulls[4] = TRUE; + nulls[5] = TRUE; + } + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + /* clean up */ + pfree(stats); + + PG_RETURN_DATUM(result); +} + +/** + * Get summary stats of a coverage for a specific band + */ +PG_FUNCTION_INFO_V1(RASTER_summaryStatsCoverage); +Datum RASTER_summaryStatsCoverage(PG_FUNCTION_ARGS) +{ + text *tablenametext = NULL; + char *tablename = NULL; + text *colnametext = NULL; + char *colname = NULL; + int32_t bandindex = 1; + bool exclude_nodata_value = TRUE; + double sample = 0; + + int len = 0; + char *sql = NULL; + int spi_result; + Portal portal; + TupleDesc tupdesc; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isNull = FALSE; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int num_bands = 0; + uint64_t cK = 0; + double cM = 0; + double cQ = 0; + rt_bandstats stats = NULL; + rt_bandstats rtn = NULL; + + int values_length = 6; + Datum values[values_length]; + bool nulls[values_length]; + Datum result; + + /* tablename is null, return null */ + if (PG_ARGISNULL(0)) { + elog(NOTICE, "Table name must be provided"); + PG_RETURN_NULL(); + } + tablenametext = PG_GETARG_TEXT_P(0); + tablename = text_to_cstring(tablenametext); + if (!strlen(tablename)) { + elog(NOTICE, "Table name must be provided"); + PG_RETURN_NULL(); + } + + /* column name is null, return null */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Column name must be provided"); + PG_RETURN_NULL(); + } + colnametext = PG_GETARG_TEXT_P(1); + colname = text_to_cstring(colnametext); + if (!strlen(colname)) { + elog(NOTICE, "Column name must be provided"); + PG_RETURN_NULL(); + } + + /* band index is 1-based */ + if (!PG_ARGISNULL(2)) + bandindex = PG_GETARG_INT32(2); + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(3)) + exclude_nodata_value = PG_GETARG_BOOL(3); + + /* sample % */ + if (!PG_ARGISNULL(4)) { + sample = PG_GETARG_FLOAT8(4); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + rt_raster_destroy(raster); + PG_RETURN_NULL(); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* iterate through rasters of coverage */ + /* connect to database */ + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) { + pfree(sql); + elog(ERROR, "RASTER_summaryStatsCoverage: Could not connect to database using SPI"); + PG_RETURN_NULL(); + } + + /* create sql */ + len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + elog(ERROR, "RASTER_summaryStatsCoverage: Could not allocate memory for sql"); + PG_RETURN_NULL(); + } + + /* get cursor */ + snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); + portal = SPI_cursor_open_with_args( + "coverage", + sql, + 0, NULL, + NULL, NULL, + TRUE, 0 + ); + pfree(sql); + + /* process resultset */ + SPI_cursor_fetch(portal, TRUE, 1); + while (SPI_processed == 1 && SPI_tuptable != NULL) { + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != rtn) pfree(rtn); + elog(ERROR, "RASTER_summaryStatsCoverage: Could not get raster of coverage"); + PG_RETURN_NULL(); + } + else if (isNull) { + SPI_cursor_fetch(portal, TRUE, 1); + continue; + } + + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); + + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != rtn) pfree(rtn); + elog(ERROR, "RASTER_summaryStatsCoverage: Could not deserialize raster"); + PG_RETURN_NULL(); + } + + /* inspect number of bands */ + 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != rtn) pfree(rtn); + 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != rtn) pfree(rtn); + PG_RETURN_NULL(); + } + + /* we don't need the raw values, hence the zero parameter */ + stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 0, &cK, &cM, &cQ); + + rt_band_destroy(band); + rt_raster_destroy(raster); + + if (NULL == stats) { + elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != rtn) pfree(rtn); + PG_RETURN_NULL(); + } + + /* initialize rtn */ + if (stats->count > 0) { + if (NULL == rtn) { + rtn = (rt_bandstats) SPI_palloc(sizeof(struct rt_bandstats_t)); + if (NULL == rtn) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + elog(ERROR, "RASTER_summaryStatsCoverage: Could not allocate memory for summary stats of coverage"); + PG_RETURN_NULL(); + } + + rtn->sample = stats->sample; + rtn->count = stats->count; + rtn->min = stats->min; + rtn->max = stats->max; + rtn->sum = stats->sum; + rtn->mean = stats->mean; + rtn->stddev = -1; + + rtn->values = NULL; + rtn->sorted = 0; + } + else { + rtn->count += stats->count; + rtn->sum += stats->sum; + + if (stats->min < rtn->min) + rtn->min = stats->min; + if (stats->max > rtn->max) + rtn->max = stats->max; + } + } + + pfree(stats); + + /* next record */ + SPI_cursor_fetch(portal, TRUE, 1); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL == rtn) { + elog(ERROR, "RASTER_summaryStatsCoverage: Could not compute coverage summary stats"); + PG_RETURN_NULL(); + } + + /* coverage mean and deviation */ + rtn->mean = rtn->sum / rtn->count; + /* sample deviation */ + if (rtn->sample > 0 && rtn->sample < 1) + rtn->stddev = sqrt(cQ / (rtn->count - 1)); + /* standard deviation */ + else + rtn->stddev = sqrt(cQ / rtn->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); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Int64GetDatum(rtn->count); + if (rtn->count > 0) { + values[1] = Float8GetDatum(rtn->sum); + values[2] = Float8GetDatum(rtn->mean); + values[3] = Float8GetDatum(rtn->stddev); + values[4] = Float8GetDatum(rtn->min); + values[5] = Float8GetDatum(rtn->max); + } + else { + nulls[1] = TRUE; + nulls[2] = TRUE; + nulls[3] = TRUE; + nulls[4] = TRUE; + nulls[5] = TRUE; + } + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + /* clean up */ + pfree(rtn); + + PG_RETURN_DATUM(result); +} + +/** + * Returns histogram for a band + */ +PG_FUNCTION_INFO_V1(RASTER_histogram); +Datum RASTER_histogram(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + rt_histogram hist; + rt_histogram hist2; + int call_cntr; + int max_calls; + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex = 1; + int num_bands = 0; + bool exclude_nodata_value = TRUE; + double sample = 0; + uint32_t bin_count = 0; + double *bin_width = NULL; + uint32_t bin_width_count = 0; + double width = 0; + bool right = FALSE; + double min = 0; + double max = 0; + rt_bandstats stats = NULL; + uint32_t count; + + int j; + int n; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + + POSTGIS_RT_DEBUG(3, "RASTER_histogram: Starting"); + + /* 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); + + /* pgraster is null, return nothing */ + 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_histogram: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(2)) + exclude_nodata_value = PG_GETARG_BOOL(2); + + /* sample % */ + if (!PG_ARGISNULL(3)) { + sample = PG_GETARG_FLOAT8(3); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* bin_count */ + if (!PG_ARGISNULL(4)) { + bin_count = PG_GETARG_INT32(4); + if (bin_count < 1) bin_count = 0; + } + + /* bin_width */ + if (!PG_ARGISNULL(5)) { + array = PG_GETARG_ARRAYTYPE_P(5); + 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_histogram: Invalid data type for width"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + bin_width = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + width = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + width = (double) DatumGetFloat8(e[i]); + break; + } + + if (width < 0 || FLT_EQ(width, 0.0)) { + elog(NOTICE, "Invalid value for width (must be greater than 0). Returning NULL"); + pfree(bin_width); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + bin_width[j] = width; + POSTGIS_RT_DEBUGF(5, "bin_width[%d] = %f", j, bin_width[j]); + j++; + } + bin_width_count = j; + + if (j < 1) { + pfree(bin_width); + bin_width = NULL; + } + } + + /* right */ + if (!PG_ARGISNULL(6)) + right = PG_GETARG_BOOL(6); + + /* min */ + if (!PG_ARGISNULL(7)) min = PG_GETARG_FLOAT8(7); + + /* max */ + if (!PG_ARGISNULL(8)) max = PG_GETARG_FLOAT8(8); + + /* 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get stats */ + stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); + rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (NULL == stats || NULL == stats->values) { + elog(NOTICE, "Could not compute summary statistics for band at index %d", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (stats->count < 1) { + elog(NOTICE, "Could not compute histogram for band at index %d as the band has no values", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get histogram */ + hist = rt_band_get_histogram(stats, bin_count, bin_width, bin_width_count, right, min, max, &count); + if (bin_width_count) pfree(bin_width); + pfree(stats); + if (NULL == hist || !count) { + elog(NOTICE, "Could not compute histogram for band at index %d", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d bins returned", count); + + /* Store needed information */ + funcctx->user_fctx = hist; + + /* 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; + hist2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 4; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(hist2[call_cntr].min); + values[1] = Float8GetDatum(hist2[call_cntr].max); + values[2] = Int64GetDatum(hist2[call_cntr].count); + values[3] = Float8GetDatum(hist2[call_cntr].percent); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(hist2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Returns histogram of a coverage for a specified band + */ +PG_FUNCTION_INFO_V1(RASTER_histogramCoverage); +Datum RASTER_histogramCoverage(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + rt_histogram covhist = NULL; + rt_histogram covhist2; + int call_cntr; + int max_calls; + + POSTGIS_RT_DEBUG(3, "RASTER_histogramCoverage: Starting"); + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + text *tablenametext = NULL; + char *tablename = NULL; + text *colnametext = NULL; + char *colname = NULL; + int32_t bandindex = 1; + bool exclude_nodata_value = TRUE; + double sample = 0; + uint32_t bin_count = 0; + double *bin_width = NULL; + uint32_t bin_width_count = 0; + double width = 0; + bool right = FALSE; + uint32_t count; + + int len = 0; + char *sql = NULL; + char *tmp = NULL; + double min = 0; + double max = 0; + int spi_result; + Portal portal; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isNull = FALSE; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int num_bands = 0; + rt_bandstats stats = NULL; + rt_histogram hist; + uint64_t sum = 0; + + int j; + int n; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + + POSTGIS_RT_DEBUG(3, "RASTER_histogramCoverage: first call of function"); + + /* 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); + + /* tablename is null, return null */ + if (PG_ARGISNULL(0)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + tablenametext = PG_GETARG_TEXT_P(0); + tablename = text_to_cstring(tablenametext); + if (!strlen(tablename)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: tablename = %s", tablename); + + /* column name is null, return null */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + colnametext = PG_GETARG_TEXT_P(1); + colname = text_to_cstring(colnametext); + if (!strlen(colname)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: colname = %s", colname); + + /* band index is 1-based */ + if (!PG_ARGISNULL(2)) + bandindex = PG_GETARG_INT32(2); + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(3)) + exclude_nodata_value = PG_GETARG_BOOL(3); + + /* sample % */ + if (!PG_ARGISNULL(4)) { + sample = PG_GETARG_FLOAT8(4); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* bin_count */ + if (!PG_ARGISNULL(5)) { + bin_count = PG_GETARG_INT32(5); + if (bin_count < 1) bin_count = 0; + } + + /* bin_width */ + if (!PG_ARGISNULL(6)) { + array = PG_GETARG_ARRAYTYPE_P(6); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Invalid data type for width"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + bin_width = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + width = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + width = (double) DatumGetFloat8(e[i]); + break; + } + + if (width < 0 || FLT_EQ(width, 0.0)) { + elog(NOTICE, "Invalid value for width (must be greater than 0). Returning NULL"); + pfree(bin_width); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + bin_width[j] = width; + POSTGIS_RT_DEBUGF(5, "bin_width[%d] = %f", j, bin_width[j]); + j++; + } + bin_width_count = j; + + if (j < 1) { + pfree(bin_width); + bin_width = NULL; + } + } + + /* right */ + if (!PG_ARGISNULL(7)) + right = PG_GETARG_BOOL(7); + + /* connect to database */ + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) { + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not connect to database using SPI"); + SRF_RETURN_DONE(funcctx); + } + + /* coverage stats */ + len = sizeof(char) * (strlen("SELECT min, max FROM _st_summarystats('','',,::boolean,)") + strlen(tablename) + strlen(colname) + (MAX_INT_CHARLEN * 2) + MAX_DBL_CHARLEN + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for sql"); + SRF_RETURN_DONE(funcctx); + } + + /* get stats */ + snprintf(sql, len, "SELECT min, max FROM _st_summarystats('%s','%s',%d,%d::boolean,%f)", tablename, colname, bandindex, (exclude_nodata_value ? 1 : 0), sample); + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: %s", sql); + spi_result = SPI_execute(sql, TRUE, 0); + pfree(sql); + if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); + SRF_RETURN_DONE(funcctx); + } + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + tmp = SPI_getvalue(tuple, tupdesc, 1); + if (NULL == tmp || !strlen(tmp)) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); + SRF_RETURN_DONE(funcctx); + } + min = strtod(tmp, NULL); + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: min = %f", min); + pfree(tmp); + + tmp = SPI_getvalue(tuple, tupdesc, 2); + if (NULL == tmp || !strlen(tmp)) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not get summary stats of coverage"); + SRF_RETURN_DONE(funcctx); + } + max = strtod(tmp, NULL); + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: max = %f", max); + pfree(tmp); + + /* iterate through rasters of coverage */ + /* create sql */ + len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for sql"); + SRF_RETURN_DONE(funcctx); + } + + /* get cursor */ + snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); + POSTGIS_RT_DEBUGF(3, "RASTER_histogramCoverage: %s", sql); + portal = SPI_cursor_open_with_args( + "coverage", + sql, + 0, NULL, + NULL, NULL, + TRUE, 0 + ); + pfree(sql); + + /* process resultset */ + SPI_cursor_fetch(portal, TRUE, 1); + while (SPI_processed == 1 && SPI_tuptable != NULL) { + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not get raster of coverage"); + SRF_RETURN_DONE(funcctx); + } + else if (isNull) { + SPI_cursor_fetch(portal, TRUE, 1); + continue; + } + + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); + + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* inspect number of bands*/ + 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* we need the raw values, hence the non-zero parameter */ + stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); + + rt_band_destroy(band); + rt_raster_destroy(raster); + + if (NULL == stats) { + elog(NOTICE, "Could not compute summary statistics for band at index %d. Returning NULL", bandindex); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get histogram */ + if (stats->count > 0) { + hist = rt_band_get_histogram(stats, bin_count, bin_width, bin_width_count, right, min, max, &count); + pfree(stats); + if (NULL == hist || !count) { + elog(NOTICE, "Could not compute histogram for band at index %d", bandindex); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covhist) pfree(covhist); + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d bins returned", count); + + /* coverage histogram */ + if (NULL == covhist) { + covhist = (rt_histogram) SPI_palloc(sizeof(struct rt_histogram_t) * count); + if (NULL == covhist) { + + pfree(hist); + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_histogramCoverage: Could not allocate memory for histogram of coverage"); + SRF_RETURN_DONE(funcctx); + } + + for (i = 0; i < count; i++) { + sum += hist[i].count; + covhist[i].count = hist[i].count; + covhist[i].percent = 0; + covhist[i].min = hist[i].min; + covhist[i].max = hist[i].max; + } + } + else { + for (i = 0; i < count; i++) { + sum += hist[i].count; + covhist[i].count += hist[i].count; + } + } + + pfree(hist); + + /* assuming bin_count wasn't set, force consistency */ + if (bin_count <= 0) bin_count = count; + } + + /* next record */ + SPI_cursor_fetch(portal, TRUE, 1); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (bin_width_count) pfree(bin_width); + + /* finish percent of histogram */ + if (sum > 0) { + for (i = 0; i < count; i++) + covhist[i].percent = covhist[i].count / (double) sum; + } + + /* Store needed information */ + funcctx->user_fctx = covhist; + + /* 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; + covhist2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 4; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(covhist2[call_cntr].min); + values[1] = Float8GetDatum(covhist2[call_cntr].max); + values[2] = Int64GetDatum(covhist2[call_cntr].count); + values[3] = Float8GetDatum(covhist2[call_cntr].percent); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(covhist2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Returns quantiles for a band + */ +PG_FUNCTION_INFO_V1(RASTER_quantile); +Datum RASTER_quantile(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + rt_quantile quant; + rt_quantile quant2; + int call_cntr; + int max_calls; + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex = 0; + int num_bands = 0; + bool exclude_nodata_value = TRUE; + double sample = 0; + double *quantiles = NULL; + uint32_t quantiles_count = 0; + double quantile = 0; + rt_bandstats stats = NULL; + uint32_t count; + + int j; + int n; + + 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); + + /* pgraster is null, return nothing */ + 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_quantile: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* band index is 1-based */ + 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(2)) + exclude_nodata_value = PG_GETARG_BOOL(2); + + /* sample % */ + if (!PG_ARGISNULL(3)) { + sample = PG_GETARG_FLOAT8(3); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* quantiles */ + if (!PG_ARGISNULL(4)) { + 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); + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantile: Invalid data type for quantiles"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + quantiles = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + quantile = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + quantile = (double) DatumGetFloat8(e[i]); + break; + } + + if (quantile < 0 || quantile > 1) { + elog(NOTICE, "Invalid value for quantile (must be between 0 and 1). Returning NULL"); + pfree(quantiles); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + quantiles[j] = quantile; + POSTGIS_RT_DEBUGF(5, "quantiles[%d] = %f", j, quantiles[j]); + j++; + } + quantiles_count = j; + + if (j < 1) { + pfree(quantiles); + quantiles = 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get stats */ + stats = rt_band_get_summary_stats(band, (int) exclude_nodata_value, sample, 1, NULL, NULL, NULL); + rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (NULL == stats || NULL == stats->values) { + elog(NOTICE, "Could not retrieve summary statistics for band at index %d", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (stats->count < 1) { + elog(NOTICE, "Could not compute quantiles for band at index %d as the band has no values", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get quantiles */ + quant = rt_band_get_quantiles(stats, quantiles, quantiles_count, &count); + if (quantiles_count) pfree(quantiles); + pfree(stats); + if (NULL == quant || !count) { + elog(NOTICE, "Could not compute quantiles for band at index %d", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d quantiles returned", count); + + /* Store needed information */ + funcctx->user_fctx = quant; + + /* 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; + quant2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(quant2[call_cntr].quantile); + values[1] = Float8GetDatum(quant2[call_cntr].value); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(quant2); + SRF_RETURN_DONE(funcctx); + } +} + +/** + * Returns selected quantiles of a coverage for a specified band + */ +PG_FUNCTION_INFO_V1(RASTER_quantileCoverage); +Datum RASTER_quantileCoverage(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + rt_quantile covquant = NULL; + rt_quantile covquant2; + int call_cntr; + int max_calls; + + POSTGIS_RT_DEBUG(3, "RASTER_quantileCoverage: Starting"); + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + text *tablenametext = NULL; + char *tablename = NULL; + text *colnametext = NULL; + char *colname = NULL; + int32_t bandindex = 1; + bool exclude_nodata_value = TRUE; + double sample = 0; + double *quantiles = NULL; + uint32_t quantiles_count = 0; + double quantile = 0; + uint32_t count; + + int len = 0; + char *sql = NULL; + char *tmp = NULL; + uint64_t cov_count = 0; + int spi_result; + Portal portal; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isNull = FALSE; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int num_bands = 0; + struct quantile_llist *qlls = NULL; + uint32_t qlls_count; + + int j; + int n; + + ArrayType *array; + Oid etype; + Datum *e; + bool *nulls; + int16 typlen; + bool typbyval; + char typalign; + + POSTGIS_RT_DEBUG(3, "RASTER_quantileCoverage: first call of function"); + + /* 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); + + /* tablename is null, return null */ + if (PG_ARGISNULL(0)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + tablenametext = PG_GETARG_TEXT_P(0); + tablename = text_to_cstring(tablenametext); + if (!strlen(tablename)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "RASTER_quantileCoverage: tablename = %s", tablename); + + /* column name is null, return null */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + colnametext = PG_GETARG_TEXT_P(1); + colname = text_to_cstring(colnametext); + if (!strlen(colname)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "RASTER_quantileCoverage: colname = %s", colname); + + /* band index is 1-based */ + if (!PG_ARGISNULL(2)) + bandindex = PG_GETARG_INT32(2); + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(3)) + exclude_nodata_value = PG_GETARG_BOOL(3); + + /* sample % */ + if (!PG_ARGISNULL(4)) { + sample = PG_GETARG_FLOAT8(4); + if (sample < 0 || sample > 1) { + elog(NOTICE, "Invalid sample percentage (must be between 0 and 1). Returning NULL"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + else if (FLT_EQ(sample, 0.0)) + sample = 1; + } + else + sample = 1; + + /* quantiles */ + if (!PG_ARGISNULL(5)) { + array = PG_GETARG_ARRAYTYPE_P(5); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Invalid data type for quantiles"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + quantiles = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + quantile = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + quantile = (double) DatumGetFloat8(e[i]); + break; + } + + if (quantile < 0 || quantile > 1) { + elog(NOTICE, "Invalid value for quantile (must be between 0 and 1). Returning NULL"); + pfree(quantiles); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + quantiles[j] = quantile; + POSTGIS_RT_DEBUGF(5, "quantiles[%d] = %f", j, quantiles[j]); + j++; + } + quantiles_count = j; + + if (j < 1) { + pfree(quantiles); + quantiles = NULL; + } + } + + /* coverage stats */ + /* connect to database */ + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) { + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not connect to database using SPI"); + SRF_RETURN_DONE(funcctx); + } + + len = sizeof(char) * (strlen("SELECT count FROM _st_summarystats('','',,::boolean,)") + strlen(tablename) + strlen(colname) + (MAX_INT_CHARLEN * 2) + MAX_DBL_CHARLEN + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not allocate memory for sql"); + SRF_RETURN_DONE(funcctx); + } + + /* get stats */ + snprintf(sql, len, "SELECT count FROM _st_summarystats('%s','%s',%d,%d::boolean,%f)", tablename, colname, bandindex, (exclude_nodata_value ? 1 : 0), sample); + POSTGIS_RT_DEBUGF(3, "stats sql: %s", sql); + spi_result = SPI_execute(sql, TRUE, 0); + pfree(sql); + if (spi_result != SPI_OK_SELECT || SPI_tuptable == NULL || SPI_processed != 1) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not get summary stats of coverage"); + SRF_RETURN_DONE(funcctx); + } + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + tmp = SPI_getvalue(tuple, tupdesc, 1); + if (NULL == tmp || !strlen(tmp)) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not get summary stats of coverage"); + SRF_RETURN_DONE(funcctx); + } + cov_count = strtol(tmp, NULL, 10); + POSTGIS_RT_DEBUGF(3, "covcount = %d", (int) cov_count); + pfree(tmp); + + /* iterate through rasters of coverage */ + /* create sql */ + len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not allocate memory for sql"); + SRF_RETURN_DONE(funcctx); + } + + /* get cursor */ + snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); + POSTGIS_RT_DEBUGF(3, "coverage sql: %s", sql); + portal = SPI_cursor_open_with_args( + "coverage", + sql, + 0, NULL, + NULL, NULL, + TRUE, 0 + ); + pfree(sql); + + /* process resultset */ + SPI_cursor_fetch(portal, TRUE, 1); + while (SPI_processed == 1 && SPI_tuptable != NULL) { + if (NULL != covquant) pfree(covquant); + + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not get raster of coverage"); + SRF_RETURN_DONE(funcctx); + } + else if (isNull) { + SPI_cursor_fetch(portal, TRUE, 1); + continue; + } + + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); + + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_quantileCoverage: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* inspect number of bands*/ + 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get band */ + band = rt_raster_get_band(raster, bandindex - 1); + if (!band) { + elog(NOTICE, "Could not find raster band of index %d. Returning NULL", bandindex); + + rt_raster_destroy(raster); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + covquant = rt_band_get_quantiles_stream( + band, + exclude_nodata_value, sample, cov_count, + &qlls, &qlls_count, + quantiles, quantiles_count, + &count + ); + + rt_band_destroy(band); + rt_raster_destroy(raster); + + if (NULL == covquant || !count) { + elog(NOTICE, "Could not compute quantiles for band at index %d", bandindex); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* next record */ + SPI_cursor_fetch(portal, TRUE, 1); + } + + covquant2 = SPI_palloc(sizeof(struct rt_quantile_t) * count); + for (i = 0; i < count; i++) { + covquant2[i].quantile = covquant[i].quantile; + covquant2[i].has_value = covquant[i].has_value; + if (covquant2[i].has_value) + covquant2[i].value = covquant[i].value; + } + + if (NULL != covquant) pfree(covquant); + quantile_llist_destroy(&qlls, qlls_count); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (quantiles_count) pfree(quantiles); + + POSTGIS_RT_DEBUGF(3, "%d quantiles returned", count); + + /* Store needed information */ + funcctx->user_fctx = covquant2; + + /* 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; + covquant2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 2; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(covquant2[call_cntr].quantile); + if (covquant2[call_cntr].has_value) + values[1] = Float8GetDatum(covquant2[call_cntr].value); + else + nulls[1] = TRUE; + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + POSTGIS_RT_DEBUG(3, "done"); + pfree(covquant2); + SRF_RETURN_DONE(funcctx); + } +} + +/* get counts of values */ +PG_FUNCTION_INFO_V1(RASTER_valueCount); +Datum RASTER_valueCount(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + rt_valuecount vcnts; + rt_valuecount vcnts2; + int call_cntr; + int max_calls; + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int32_t bandindex = 0; + int num_bands = 0; + bool exclude_nodata_value = TRUE; + double *search_values = NULL; + uint32_t search_values_count = 0; + double roundto = 0; + uint32_t count; + + int j; + int n; + + 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); + + /* pgraster is null, return nothing */ + 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_valueCount: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* band index is 1-based */ + 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(2)) + exclude_nodata_value = PG_GETARG_BOOL(2); + + /* search values */ + if (!PG_ARGISNULL(3)) { + array = PG_GETARG_ARRAYTYPE_P(3); + 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_valueCount: Invalid data type for values"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + search_values = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + search_values[j] = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + search_values[j] = (double) DatumGetFloat8(e[i]); + break; + } + + POSTGIS_RT_DEBUGF(5, "search_values[%d] = %f", j, search_values[j]); + j++; + } + search_values_count = j; + + if (j < 1) { + pfree(search_values); + search_values = NULL; + } + } + + /* roundto */ + if (!PG_ARGISNULL(4)) { + roundto = PG_GETARG_FLOAT8(4); + if (roundto < 0.) roundto = 0; + } + + /* 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); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get counts of values */ + vcnts = rt_band_get_value_count(band, (int) exclude_nodata_value, search_values, search_values_count, roundto, NULL, &count); + rt_band_destroy(band); + rt_raster_destroy(raster); + PG_FREE_IF_COPY(pgraster, 0); + if (NULL == vcnts || !count) { + elog(NOTICE, "Could not count the values for band at index %d", bandindex); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d value counts returned", count); + + /* Store needed information */ + funcctx->user_fctx = vcnts; + + /* 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; + vcnts2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 3; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(vcnts2[call_cntr].value); + values[1] = UInt32GetDatum(vcnts2[call_cntr].count); + values[2] = Float8GetDatum(vcnts2[call_cntr].percent); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(vcnts2); + SRF_RETURN_DONE(funcctx); + } +} + +/* get counts of values for a coverage */ +PG_FUNCTION_INFO_V1(RASTER_valueCountCoverage); +Datum RASTER_valueCountCoverage(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tupdesc; + + int i; + uint64_t covcount = 0; + uint64_t covtotal = 0; + rt_valuecount covvcnts = NULL; + rt_valuecount covvcnts2; + int call_cntr; + int max_calls; + + POSTGIS_RT_DEBUG(3, "RASTER_valueCountCoverage: Starting"); + + /* first call of function */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + + text *tablenametext = NULL; + char *tablename = NULL; + text *colnametext = NULL; + char *colname = NULL; + int32_t bandindex = 1; + bool exclude_nodata_value = TRUE; + double *search_values = NULL; + uint32_t search_values_count = 0; + double roundto = 0; + + int len = 0; + char *sql = NULL; + int spi_result; + Portal portal; + SPITupleTable *tuptable = NULL; + HeapTuple tuple; + Datum datum; + bool isNull = FALSE; + rt_pgraster *pgraster = NULL; + rt_raster raster = NULL; + rt_band band = NULL; + int num_bands = 0; + uint32_t count; + uint32_t total; + rt_valuecount vcnts = NULL; + int exists = 0; + + int j; + int n; + + 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); + + /* tablename is null, return null */ + if (PG_ARGISNULL(0)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + tablenametext = PG_GETARG_TEXT_P(0); + tablename = text_to_cstring(tablenametext); + if (!strlen(tablename)) { + elog(NOTICE, "Table name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "tablename = %s", tablename); + + /* column name is null, return null */ + if (PG_ARGISNULL(1)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + colnametext = PG_GETARG_TEXT_P(1); + colname = text_to_cstring(colnametext); + if (!strlen(colname)) { + elog(NOTICE, "Column name must be provided"); + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + POSTGIS_RT_DEBUGF(3, "colname = %s", colname); + + /* band index is 1-based */ + if (!PG_ARGISNULL(2)) + bandindex = PG_GETARG_INT32(2); + + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(3)) + exclude_nodata_value = PG_GETARG_BOOL(3); + + /* search values */ + if (!PG_ARGISNULL(4)) { + array = PG_GETARG_ARRAYTYPE_P(4); + etype = ARR_ELEMTYPE(array); + get_typlenbyvalalign(etype, &typlen, &typbyval, &typalign); + + switch (etype) { + case FLOAT4OID: + case FLOAT8OID: + break; + default: + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Invalid data type for values"); + SRF_RETURN_DONE(funcctx); + break; + } + + deconstruct_array(array, etype, typlen, typbyval, typalign, &e, + &nulls, &n); + + search_values = palloc(sizeof(double) * n); + for (i = 0, j = 0; i < n; i++) { + if (nulls[i]) continue; + + switch (etype) { + case FLOAT4OID: + search_values[j] = (double) DatumGetFloat4(e[i]); + break; + case FLOAT8OID: + search_values[j] = (double) DatumGetFloat8(e[i]); + break; + } + + POSTGIS_RT_DEBUGF(5, "search_values[%d] = %f", j, search_values[j]); + j++; + } + search_values_count = j; + + if (j < 1) { + pfree(search_values); + search_values = NULL; + } + } + + /* roundto */ + if (!PG_ARGISNULL(5)) { + roundto = PG_GETARG_FLOAT8(5); + if (roundto < 0.) roundto = 0; + } + + /* iterate through rasters of coverage */ + /* connect to database */ + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) { + + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not connect to database using SPI"); + SRF_RETURN_DONE(funcctx); + } + + /* create sql */ + len = sizeof(char) * (strlen("SELECT \"\" FROM \"\" WHERE \"\" IS NOT NULL") + (strlen(colname) * 2) + strlen(tablename) + 1); + sql = (char *) palloc(len); + if (NULL == sql) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_finish(); + + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not allocate memory for sql"); + SRF_RETURN_DONE(funcctx); + } + + /* get cursor */ + snprintf(sql, len, "SELECT \"%s\" FROM \"%s\" WHERE \"%s\" IS NOT NULL", colname, tablename, colname); + POSTGIS_RT_DEBUGF(3, "RASTER_valueCountCoverage: %s", sql); + portal = SPI_cursor_open_with_args( + "coverage", + sql, + 0, NULL, + NULL, NULL, + TRUE, 0 + ); + pfree(sql); + + /* process resultset */ + SPI_cursor_fetch(portal, TRUE, 1); + while (SPI_processed == 1 && SPI_tuptable != NULL) { + tupdesc = SPI_tuptable->tupdesc; + tuptable = SPI_tuptable; + tuple = tuptable->vals[0]; + + datum = SPI_getbinval(tuple, tupdesc, 1, &isNull); + if (SPI_result == SPI_ERROR_NOATTRIBUTE) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covvcnts) pfree(covvcnts); + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not get raster of coverage"); + SRF_RETURN_DONE(funcctx); + } + else if (isNull) { + SPI_cursor_fetch(portal, TRUE, 1); + continue; + } + + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(datum); + + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covvcnts) pfree(covvcnts); + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not deserialize raster"); + SRF_RETURN_DONE(funcctx); + } + + /* inspect number of bands*/ + 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covvcnts) pfree(covvcnts); + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* 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); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covvcnts) pfree(covvcnts); + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + /* get counts of values */ + vcnts = rt_band_get_value_count(band, (int) exclude_nodata_value, search_values, search_values_count, roundto, &total, &count); + rt_band_destroy(band); + rt_raster_destroy(raster); + if (NULL == vcnts || !count) { + elog(NOTICE, "Could not count the values for band at index %d", bandindex); + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (NULL != covvcnts) free(covvcnts); + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + POSTGIS_RT_DEBUGF(3, "%d value counts returned", count); + + if (NULL == covvcnts) { + covvcnts = (rt_valuecount) SPI_palloc(sizeof(struct rt_valuecount_t) * count); + if (NULL == covvcnts) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (search_values_count) pfree(search_values); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not allocate memory for value counts of coverage"); + SRF_RETURN_DONE(funcctx); + } + + for (i = 0; i < count; i++) { + covvcnts[i].value = vcnts[i].value; + covvcnts[i].count = vcnts[i].count; + covvcnts[i].percent = -1; + } + + covcount = count; + } + else { + for (i = 0; i < count; i++) { + exists = 0; + + for (j = 0; j < covcount; j++) { + if (FLT_EQ(vcnts[i].value, covvcnts[j].value)) { + exists = 1; + break; + } + } + + if (exists) { + covvcnts[j].count += vcnts[i].count; + } + else { + covcount++; + covvcnts = SPI_repalloc(covvcnts, sizeof(struct rt_valuecount_t) * covcount); + if (NULL == covvcnts) { + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (search_values_count) pfree(search_values); + if (NULL != covvcnts) free(covvcnts); + + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "RASTER_valueCountCoverage: Could not change allocated memory for value counts of coverage"); + SRF_RETURN_DONE(funcctx); + } + + covvcnts[covcount - 1].value = vcnts[i].value; + covvcnts[covcount - 1].count = vcnts[i].count; + covvcnts[covcount - 1].percent = -1; + } + } + } + + covtotal += total; + + pfree(vcnts); + + /* next record */ + SPI_cursor_fetch(portal, TRUE, 1); + } + + if (SPI_tuptable) SPI_freetuptable(tuptable); + SPI_cursor_close(portal); + SPI_finish(); + + if (search_values_count) pfree(search_values); + + /* compute percentages */ + for (i = 0; i < covcount; i++) { + covvcnts[i].percent = (double) covvcnts[i].count / covtotal; + } + + /* Store needed information */ + funcctx->user_fctx = covvcnts; + + /* total number of tuples to be returned */ + funcctx->max_calls = covcount; + + /* 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; + covvcnts2 = funcctx->user_fctx; + + /* do when there is more left to send */ + if (call_cntr < max_calls) { + int values_length = 3; + Datum values[values_length]; + bool nulls[values_length]; + HeapTuple tuple; + Datum result; + + POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + + memset(nulls, FALSE, sizeof(bool) * values_length); + + values[0] = Float8GetDatum(covvcnts2[call_cntr].value); + values[1] = UInt32GetDatum(covvcnts2[call_cntr].count); + values[2] = Float8GetDatum(covvcnts2[call_cntr].percent); + + /* build a tuple */ + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + /* do when there is no more left */ + else { + pfree(covvcnts2); + SRF_RETURN_DONE(funcctx); + } +} + diff --git a/raster/rt_pg/rtpg_utility.c b/raster/rt_pg/rtpg_utility.c new file mode 100644 index 000000000..4f4b7c9c3 --- /dev/null +++ b/raster/rt_pg/rtpg_utility.c @@ -0,0 +1,135 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include /* for palloc */ +#include +#include + +#include "../../postgis_config.h" +#include "lwgeom_pg.h" + +#include "rtpostgis.h" + +Datum RASTER_lib_version(PG_FUNCTION_ARGS); +Datum RASTER_lib_build_date(PG_FUNCTION_ARGS); +Datum RASTER_gdal_version(PG_FUNCTION_ARGS); +Datum RASTER_minPossibleValue(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(RASTER_lib_version); +Datum RASTER_lib_version(PG_FUNCTION_ARGS) +{ + char ver[64]; + text *result; + + snprintf(ver, 64, "%s r%d", POSTGIS_LIB_VERSION, POSTGIS_SVN_REVISION); + ver[63] = '\0'; + + result = cstring2text(ver); + PG_RETURN_TEXT_P(result); +} + +PG_FUNCTION_INFO_V1(RASTER_lib_build_date); +Datum RASTER_lib_build_date(PG_FUNCTION_ARGS) +{ + char *ver = POSTGIS_BUILD_DATE; + text *result; + result = palloc(VARHDRSZ + strlen(ver)); + SET_VARSIZE(result, VARHDRSZ + strlen(ver)); + memcpy(VARDATA(result), ver, strlen(ver)); + PG_RETURN_POINTER(result); +} + +PG_FUNCTION_INFO_V1(RASTER_gdal_version); +Datum RASTER_gdal_version(PG_FUNCTION_ARGS) +{ + const char *ver = rt_util_gdal_version("--version"); + text *result; + + /* add indicator if GDAL isn't configured right */ + if (!rt_util_gdal_configured()) { + char *rtn = NULL; + rtn = palloc(strlen(ver) + strlen(" GDAL_DATA not found") + 1); + if (!rtn) + result = cstring2text(ver); + else { + sprintf(rtn, "%s GDAL_DATA not found", ver); + result = cstring2text(rtn); + pfree(rtn); + } + } + else + result = cstring2text(ver); + + PG_RETURN_POINTER(result); +} + +PG_FUNCTION_INFO_V1(RASTER_minPossibleValue); +Datum RASTER_minPossibleValue(PG_FUNCTION_ARGS) +{ + text *pixeltypetext = NULL; + char *pixeltypechar = NULL; + rt_pixtype pixtype = PT_END; + double pixsize = 0; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + pixeltypetext = PG_GETARG_TEXT_P(0); + pixeltypechar = text_to_cstring(pixeltypetext); + + pixtype = rt_pixtype_index_from_name(pixeltypechar); + if (pixtype == PT_END) { + elog(ERROR, "RASTER_minPossibleValue: Invalid pixel type: %s", pixeltypechar); + PG_RETURN_NULL(); + } + + pixsize = rt_pixtype_get_min_value(pixtype); + + /* + correct pixsize of unsigned pixel types + example: for PT_8BUI, the value is CHAR_MIN but if char is signed, + the value returned is -127 instead of 0. + */ + switch (pixtype) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: + case PT_16BUI: + case PT_32BUI: + pixsize = 0; + break; + default: + break; + } + + PG_RETURN_FLOAT8(pixsize); +} + diff --git a/raster/rt_pg/rtpostgis.c b/raster/rt_pg/rtpostgis.c new file mode 100644 index 000000000..363185953 --- /dev/null +++ b/raster/rt_pg/rtpostgis.c @@ -0,0 +1,270 @@ +/* + * $Id$ + * + * WKTRaster - Raster Types for PostGIS + * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage + * + * Copyright (C) 2011-2013 Regents of the University of California + * + * Copyright (C) 2010-2011 Jorge Arevalo + * Copyright (C) 2010-2011 David Zwarg + * Copyright (C) 2009-2011 Pierre Racine + * Copyright (C) 2009-2011 Mateusz Loskot + * Copyright (C) 2008-2009 Sandro Santilli + * + * 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 3 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/*************************************************************** + * Some rules for returning NOTICE or ERROR... + * + * Send an ERROR like: + * + * elog(ERROR, "RASTER_out: Could not deserialize raster"); + * + * only when: + * + * -something wrong happen with memory, + * -a function got an invalid argument ('3BUI' as pixel type) so that no row can + * be processed + * + * *** IMPORTANT: elog(ERROR, ...) does NOT return to calling function *** + * + * Send a NOTICE like: + * + * elog(NOTICE, "Invalid band index (must use 1-based). Returning NULL"); + * + * when arguments (e.g. x, y, band) are NULL or out of range so that some or + * most rows can be processed anyway + * + * in this case, + * for SET functions or function normally returning a modified raster, return + * the original raster + * for GET functions, return NULL + * try to deduce a valid parameter value if it makes sence (e.g. out of range + * index for addBand) + * + * Do not put the name of the faulty function for NOTICEs, only with ERRORs. + * + ****************************************************************/ + +/****************************************************************************** + * Some notes on memory management... + * + * Every time a SQL function is called, PostgreSQL creates a new memory context. + * So, all the memory allocated with palloc/repalloc in that context is + * automatically free'd at the end of the function. If you want some data to + * live between function calls, you have 2 options: + * + * - Use fcinfo->flinfo->fn_mcxt contex to store the data (by pointing the + * data you want to keep with fcinfo->flinfo->fn_extra) + * - Use SRF funcapi, and storing the data at multi_call_memory_ctx (by pointing + * the data you want to keep with funcctx->user_fctx. funcctx is created by + * funcctx = SPI_FIRSTCALL_INIT()). Recommended way in functions returning rows, + * like RASTER_dumpAsPolygons (see section 34.9.9 at + * http://www.postgresql.org/docs/8.4/static/xfunc-c.html). + * + * But raster code follows the same philosophy than the rest of PostGIS: keep + * memory as clean as possible. So, we free all allocated memory. + * + * TODO: In case of functions returning NULL, we should free the memory too. + *****************************************************************************/ + +/****************************************************************************** + * Notes for use of PG_DETOAST_DATUM(), PG_DETOAST_DATUM_SLICE() + * and PG_DETOAST_DATUM_COPY() + * + * When ONLY getting raster (not band) metadata, use PG_DETOAST_DATUM_SLICE() + * as it is generally quicker to get only the chunk of memory that contains + * the raster metadata. + * + * Example: PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(0), 0, + * sizeof(struct rt_raster_serialized_t)) + * + * When ONLY setting raster or band(s) metadata OR reading band data, use + * PG_DETOAST_DATUM() as rt_raster_deserialize() allocates local memory + * for the raster and band(s) metadata. + * + * Example: PG_DETOAST_DATUM(PG_GETARG_DATUM(0)) + * + * When SETTING band pixel values, use PG_DETOAST_DATUM_COPY(). This is + * because band data (not metadata) is just a pointer to the correct + * memory location in the detoasted datum. What is returned from + * PG_DETOAST_DATUM() may or may not be a copy of the input datum. + * + * From the comments in postgresql/src/include/fmgr.h... + * + * pg_detoast_datum() gives you either the input datum (if not toasted) + * or a detoasted copy allocated with palloc(). + * + * From the mouth of Tom Lane... + * http://archives.postgresql.org/pgsql-hackers/2002-01/msg01289.php + * + * PG_DETOAST_DATUM_COPY guarantees to give you a copy, even if the + * original wasn't toasted. This allows you to scribble on the input, + * in case that happens to be a useful way of forming your result. + * Without a forced copy, a routine for a pass-by-ref datatype must + * NEVER, EVER scribble on its input ... because very possibly it'd + * be scribbling on a valid tuple in a disk buffer, or a valid entry + * in the syscache. + * + * The key detail above is that the raster datatype is a varlena, a + * passed by reference datatype. + * + * Example: PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0)) + * + * If in doubt, use PG_DETOAST_DATUM_COPY() as that guarantees that the input + * datum is copied for use. + *****************************************************************************/ + +#include /* for palloc */ +#include /* for PG_MODULE_MAGIC */ +#include "utils/guc.h" + +#include "../../postgis_config.h" +#include "lwgeom_pg.h" + +#include "rtpostgis.h" + +/* + * This is required for builds against pgsql + */ +PG_MODULE_MAGIC; + +/* Module load callback */ +void _PG_init(void); + +static void +rtpg_assignHookGDALDataPath(const char *newpath, void *extra) { + POSTGIS_RT_DEBUGF(4, "newpath = %s", newpath); + + /* clear finder cache */ + CPLFinderClean(); + + /* clear cached OSR */ + OSRCleanup(); + + /* set GDAL_DATA */ + CPLSetConfigOption("GDAL_DATA", newpath); + POSTGIS_RT_DEBUGF(4, "GDAL_DATA = %s", CPLGetConfigOption("GDAL_DATA", NULL)); +} + +static char *gdaldatapath; + +/* Module load callback */ +void +_PG_init(void) { + /* Install liblwgeom handlers */ + pg_install_lwgeom_handlers(); + + /* TODO: Install raster callbacks (see rt_init_allocators)??? */ + + /* Define custom GUC variables. */ + DefineCustomStringVariable( + "postgis.gdal.datapath", /* name */ + "Path to GDAL data files.", /* short_desc */ + "Physical path to directory containing GDAL data files (sets the GDAL_DATA config option).", /* long_desc */ + &gdaldatapath, /* valueAddr */ + NULL, /* bootValue */ + PGC_SUSET, /* GucContext context */ + 0, /* int flags */ +#if POSTGIS_PGSQL_VERSION >= 91 + NULL, /* GucStringCheckHook check_hook */ +#endif + rtpg_assignHookGDALDataPath, /* GucStringAssignHook assign_hook */ + NULL /* GucShowHook show_hook */ + ); +} + +/* ---------------------------------------------------------------- */ +/* Memory allocation / error reporting hooks */ +/* TODO: reuse the ones in libpgcommon ? */ +/* ---------------------------------------------------------------- */ + +static void * +rt_pg_alloc(size_t size) +{ + void * result; + + POSTGIS_RT_DEBUGF(5, "rt_pgalloc(%ld) called", (long int) size); + + result = palloc(size); + + return result; +} + +static void * +rt_pg_realloc(void *mem, size_t size) +{ + void * result; + + POSTGIS_RT_DEBUGF(5, "rt_pg_realloc(%ld) called", (long int) size); + + if (mem) + result = repalloc(mem, size); + + else + result = palloc(size); + + return result; +} + +static void +rt_pg_free(void *ptr) +{ + POSTGIS_RT_DEBUG(5, "rt_pfree called"); + pfree(ptr); +} + +static void +rt_pg_error(const char *fmt, va_list ap) +{ +#define ERRMSG_MAXLEN 256 + + char errmsg[ERRMSG_MAXLEN+1]; + + vsnprintf (errmsg, ERRMSG_MAXLEN, fmt, ap); + + errmsg[ERRMSG_MAXLEN]='\0'; + ereport(ERROR, (errmsg_internal("%s", errmsg))); +} + +static void +rt_pg_notice(const char *fmt, va_list ap) +{ + char *msg; + + /* + * This is a GNU extension. + * Dunno how to handle errors here. + */ + if (!lw_vasprintf (&msg, fmt, ap)) + { + va_end (ap); + return; + } + ereport(NOTICE, (errmsg_internal("%s", msg))); + free(msg); +} + + +void +rt_init_allocators(void) +{ + /* raster callback - install raster handlers */ + rt_set_handlers(rt_pg_alloc, rt_pg_realloc, rt_pg_free, rt_pg_error, + rt_pg_notice, rt_pg_notice); +} diff --git a/raster/rt_pg/rt_pg.h b/raster/rt_pg/rtpostgis.h similarity index 75% rename from raster/rt_pg/rt_pg.h rename to raster/rt_pg/rtpostgis.h index 3137513b6..2c885de53 100644 --- a/raster/rt_pg/rt_pg.h +++ b/raster/rt_pg/rtpostgis.h @@ -26,14 +26,10 @@ * */ -#ifndef RT_PG_H_INCLUDED -#define RT_PG_H_INCLUDED +#ifndef RTPOSTGIS_H_INCLUDED +#define RTPOSTGIS_H_INCLUDED -#include /* for int16_t and friends */ - -#include "rt_api.h" -#include "../../postgis_config.h" -#include "../raster_config.h" +#include "librtcore.h" /* Debugging macros */ #if POSTGIS_DEBUG_LEVEL > 0 @@ -64,39 +60,12 @@ #endif - -typedef struct rt_pgband8_t { - uint8_t pixtype; - uint8_t data[1]; -} rt_pgband8; - -typedef struct rt_pgband16_t { - uint8_t pixtype; - uint8_t pad; - uint8_t data[1]; -} rt_pgband16; - -typedef struct rt_pgband32_t { - uint8_t pixtype; - uint8_t pad0; - uint8_t pad1; - uint8_t pad2; - uint8_t data[1]; -} rt_pgband32; - -typedef struct rt_pgband64_t { - uint8_t pixtype; - uint8_t pad[7]; - uint8_t data[1]; -} rt_pgband64; - -typedef struct rt_pgband_t { - uint8_t pixtype; - uint8_t data[1]; -} rt_pgband; - /* Header of PostgreSQL-stored RASTER value, * and binary representation of it */ typedef struct rt_raster_serialized_t rt_pgraster; -#endif /* RT_PG_H_INCLUDED */ +/* maximum char length required to hold any double or long long value */ +#define MAX_DBL_CHARLEN (3 + DBL_MANT_DIG - DBL_MIN_EXP) +#define MAX_INT_CHARLEN 32 + +#endif /* RTPOSTGIS_H_INCLUDED */ diff --git a/raster/test/cunit/cu_tester.h b/raster/test/cunit/cu_tester.h index 26d572421..ab4b4ec9e 100644 --- a/raster/test/cunit/cu_tester.h +++ b/raster/test/cunit/cu_tester.h @@ -9,7 +9,10 @@ * **********************************************************************/ -#include "rt_api.h" +#include "librtcore.h" + +#include "../../../postgis_config.h" +#include "../../raster_config.h" #define PG_TEST(test_func) { #test_func, test_func } #define MAX_CUNIT_MSG_LENGTH 512