]> granicus.if.org Git - postgis/commitdiff
Adds ST_ValueCount to count the number of times a user-provided value or all values...
authorBborie Park <bkpark at ucdavis.edu>
Thu, 26 May 2011 04:23:54 +0000 (04:23 +0000)
committerBborie Park <bkpark at ucdavis.edu>
Thu, 26 May 2011 04:23:54 +0000 (04:23 +0000)
Associated ticket is #953

git-svn-id: http://svn.osgeo.org/postgis/trunk@7252 b70326c6-7e19-0410-871a-916f4a2858ee

raster/rt_core/rt_api.c
raster/rt_core/rt_api.h
raster/rt_pg/rt_pg.c
raster/rt_pg/rtpostgis.sql.in.c
raster/test/core/testapi.c
raster/test/regress/Makefile.in
raster/test/regress/rt_valuecount.sql [new file with mode: 0644]
raster/test/regress/rt_valuecount_expected [new file with mode: 0644]

index b1fc8114f4254c0ecb4b215cbead353310f345b2..d3a9ec19d5751f1ce525ca9f425c7b6138e810f5 100644 (file)
@@ -130,8 +130,8 @@ rt_util_clamp_to_32F(double value) {
 }
 
 /* quicksort */
-#define   swap(x, y)    { double t; t = x; x = y; y = t; }
-#define   order(x, y)   if (x > y) swap(x, y)
+#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;
@@ -141,9 +141,9 @@ static double pivot(double *left, double *right) {
        r = *right;
 
        /* order */
-       order(l, m);
-       order(l, r);
-       order(m, r);
+       ORDER(l, m);
+       ORDER(l, r);
+       ORDER(m, r);
 
        /* pivot is higher of two values */
        if (l < m) return m;
@@ -165,7 +165,7 @@ static double *partition(double *left, double *right, double pivot) {
                while (*right >= pivot) --right;
 
                if (left < right) {
-                       swap(*left, *right);
+                       SWAP(*left, *right);
                        ++left;
                        --right;
                }
@@ -1500,7 +1500,7 @@ struct rt_bandstats_t {
 /**
  * Compute summary statistics for a band
  *
- * @param band: the band to query for minimum and maximum pixel values
+ * @param band: the band to query for summary stats
  * @param hasnodata: if non-zero, ignore nodata values
  * @param sample: percentage of pixels to sample
  * @param inc_vals: flag to include values in return struct
@@ -1510,7 +1510,6 @@ struct rt_bandstats_t {
 rt_bandstats
 rt_band_get_summary_stats(rt_band band, int hasnodata, double sample,
        int inc_vals) {
-       rt_pixtype pixtype = PT_END;
        uint8_t *data = NULL;
        uint32_t x = 0;
        uint32_t y = 0;
@@ -1556,15 +1555,12 @@ rt_band_get_summary_stats(rt_band band, int hasnodata, double sample,
        }
 
        data = rt_band_get_data(band);
-       pixtype = band->pixtype;
 
        hasnodata_flag = rt_band_get_hasnodata_flag(band);
-       if (hasnodata_flag != FALSE) {
+       if (hasnodata_flag != FALSE)
                nodata = rt_band_get_nodata(band);
-       }
-       else {
+       else
                hasnodata = 0;
-       }
 
        RASTER_DEBUGF(3, "nodata = %f", nodata);
        RASTER_DEBUGF(3, "hasnodata_flag = %d", hasnodata_flag);
@@ -2143,6 +2139,273 @@ rt_band_get_quantiles(rt_bandstats stats,
        return rtn;
 }
 
+/* symmetrical rounding */
+#define ROUND(x, y) (((x > 0.0) ? floor((x * pow(10, y) + 0.5)) : ceil((x * pow(10, y) - 0.5))) / pow(10, y));
+
+struct rt_valuecount_t {
+       double value;
+       uint32_t count;
+       double proportion;
+};
+
+/**
+ * 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 hasnodata: 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_count: the number of value counts being returned
+ *
+ * @return the default set of or requested quantiles for a band
+ */
+rt_valuecount
+rt_band_get_value_count(rt_band band, int hasnodata,
+       double *search_values, uint32_t search_values_count, double roundto,
+       int *rtn_count) {
+       rt_valuecount vcnts = NULL;
+       rt_pixtype pixtype = PT_END;
+       uint8_t *data = NULL;
+       int hasnodata_flag = FALSE;
+       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;
+       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);
+
+       if (band->offline) {
+               rterror("rt_band_get_count_of_values not implemented yet for OFFDB bands");
+               return NULL;
+       }
+
+       data = rt_band_get_data(band);
+       pixtype = band->pixtype;
+
+       hasnodata_flag = rt_band_get_hasnodata_flag(band);
+       if (hasnodata_flag != FALSE)
+               nodata = rt_band_get_nodata(band);
+       else
+               hasnodata = 0;
+
+       RASTER_DEBUGF(3, "nodata = %f", nodata);
+       RASTER_DEBUGF(3, "hasnodata_flag = %d", hasnodata_flag);
+       RASTER_DEBUGF(3, "user hasnodata = %d", hasnodata);
+
+       /* process roundto */
+       if (roundto < 0 || (fabs(roundto - 0.0) < FLT_EPSILON)) {
+               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 (fabs((tmpd - ((int) tmpd)) - 0.0) < FLT_EPSILON) 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 || fabs(tmpd - 1.0) < FLT_EPSILON) {
+                               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: Unable to allocate memory for value counts");
+                       *rtn_count = 0;
+                       return NULL;
+               }
+
+               for (i = 0; i < search_values_count; i++) {
+                       vcnts[i].count = 0;
+                       vcnts[i].proportion = 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 (hasnodata) {
+                       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 (fabs(tmpd - vcnts[i].value) > FLT_EPSILON)
+                                               continue;
+
+                                       vcnts[i].count = band->width * band->height;
+                                       vcnts->proportion = 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: Unable to allocate memory for value counts");
+                                       *rtn_count = 0;
+                                       return NULL;
+                               }
+
+                               vcnts->value = nodata;
+                               vcnts->count = band->width * band->height;
+                               vcnts->proportion = 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);
+
+                       /* error getting value, continue */
+                       if (rtn == -1) continue;
+
+                       if (
+                               !hasnodata || (
+                                       hasnodata &&
+                                       (hasnodata_flag != FALSE) &&
+                                       (fabs(pxlval - nodata) > FLT_EPSILON)
+                               )
+                       ) {
+                               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 (fabs(vcnts[i].value - rpxlval) < FLT_EPSILON) {
+                                               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: Unable to allocate memory for value counts");
+                                       *rtn_count = 0;
+                                       return NULL;
+                               }
+
+                               vcnts[vcnts_count].value = rpxlval;
+                               vcnts[vcnts_count].count = 1;
+                               vcnts[vcnts_count].proportion = 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].proportion = (double) vcnts[i].count / total;
+               RASTER_DEBUGF(5, "(value, count) => (%0.6f, %d)", vcnts[i].value, vcnts[i].count);
+       }
+
+       RASTER_DEBUG(3, "done");
+       *rtn_count = vcnts_count;
+       return vcnts;
+}
+
 struct rt_reclassexpr_t {
        struct rt_reclassrange {
                double min;
@@ -2171,8 +2434,8 @@ rt_band_reclass(rt_band srcband, rt_pixtype pixtype,
        uint32_t hasnodata, double nodataval, rt_reclassexpr *exprset,
        int exprcount) {
        rt_band band = NULL;
-       int width = 0;
-       int height = 0;
+       uint32_t width = 0;
+       uint32_t height = 0;
        int numval = 0;
        int memsize = 0;
        void *mem = NULL;
@@ -2180,8 +2443,8 @@ rt_band_reclass(rt_band srcband, rt_pixtype pixtype,
        double src_nodataval = 0.0;
 
        int rtn;
-       int x;
-       int y;
+       uint32_t x;
+       uint32_t y;
        int i;
        double or = 0;
        double ov = 0;
index 86145a1578a82e16350233fa537aa52a7dd6a4af..b079262817c765971312107c9ae80e02fc9cad8c 100644 (file)
@@ -450,7 +450,7 @@ int rt_band_check_is_nodata(rt_band band);
 /**
  * Compute summary statistics for a band
  *
- * @param band: the band to query for minimum and maximum pixel values
+ * @param band: the band to query for summary stats 
  * @param hasnodata: if non-zero, ignore nodata values
  * @param sample: percentage of pixels to sample
  * @param inc_vals: flag to include values in return struct
@@ -493,6 +493,24 @@ typedef struct rt_quantile_t* rt_quantile;
 rt_quantile rt_band_get_quantiles(rt_bandstats stats,
        double *quantiles, int quantiles_count, int *rtn_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 hasnodata: 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_count: the number of value counts being returned
+ *
+ * @return the default set of or requested quantiles for a band
+ */
+typedef struct rt_valuecount_t* rt_valuecount;
+rt_valuecount rt_band_get_value_count(rt_band band, int hasnodata,
+       double *search_values, uint32_t search_values_count,
+       double roundto, int *rtn_count);
+
 /**
  * Returns new band with values reclassified
  * 
index 9c0adf14807ad5dc674e959e7e8bc431c998c347..aa1c4eb6a086a7716e7eaa88bef4f670ddcb0b80 100644 (file)
@@ -209,6 +209,9 @@ Datum RASTER_histogram(PG_FUNCTION_ARGS);
 /* get quantiles */
 Datum RASTER_quantile(PG_FUNCTION_ARGS);
 
+/* get counts of values */
+Datum RASTER_valueCount(PG_FUNCTION_ARGS);
+
 /* reclassify specified bands of a raster */
 Datum RASTER_reclass(PG_FUNCTION_ARGS);
 
@@ -3656,6 +3659,250 @@ Datum RASTER_quantile(PG_FUNCTION_ARGS)
        }
 }
 
+struct rt_valuecount_t {
+       double value;
+       uint32_t count;
+       double proportion;
+};
+
+/* get counts of values */
+PG_FUNCTION_INFO_V1(RASTER_valueCount);
+Datum RASTER_valueCount(PG_FUNCTION_ARGS) {
+       FuncCallContext *funcctx;
+       TupleDesc tupdesc;
+       AttInMetadata *attinmeta;
+
+       int count;
+       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 hasnodata = TRUE;
+               double *search_values = NULL;
+               int search_values_count = 0;
+               double roundto = 0;
+
+               int i;
+               int j;
+               int n;
+
+               ArrayType *array;
+               Oid etype;
+               Datum *e;
+               bool *nulls;
+               int16 typlen;
+               bool typbyval;
+               char typalign;
+               int ndims = 1;
+               int *dims;
+               int *lbs;
+
+               /* 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)) SRF_RETURN_DONE(funcctx);
+               pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+
+               raster = rt_raster_deserialize(pgraster);
+               if (!raster) {
+                       elog(ERROR, "RASTER_valueCount: Could not deserialize raster");
+                       PG_RETURN_NULL();
+               }
+
+               /* 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);
+                       SRF_RETURN_DONE(funcctx);
+               }
+               assert(0 <= (bandindex - 1));
+
+               /* hasnodata flag */
+               if (!PG_ARGISNULL(2))
+                       hasnodata = 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:
+                                       elog(ERROR, "RASTER_valueCount: Invalid data type for values");
+                                       rt_raster_destroy(raster);
+                                       PG_RETURN_NULL();
+                                       break;
+                       }
+
+                       ndims = ARR_NDIM(array);
+                       dims = ARR_DIMS(array);
+                       lbs = ARR_LBOUND(array);
+
+                       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 raster band of index %d. Returning NULL", bandindex);
+                       rt_raster_destroy(raster);
+                       SRF_RETURN_DONE(funcctx);
+               }
+
+               /* get counts of values */
+               vcnts = rt_band_get_value_count(band, (int) hasnodata, search_values, search_values_count, roundto, &count);
+               rt_band_destroy(band);
+               rt_raster_destroy(raster);
+               if (NULL == vcnts || !count) {
+                       elog(NOTICE, "Could not count the values of raster band of index %d", bandindex);
+                       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"
+                               )
+                       ));
+               }
+
+               /*
+                * generate attribute metadata needed later to produce tuples from raw
+                * C strings
+                */
+               attinmeta = TupleDescGetAttInMetadata(tupdesc);
+               funcctx->attinmeta = attinmeta;
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+  /* stuff done on every call of the function */
+  funcctx = SRF_PERCALL_SETUP();
+
+       call_cntr = funcctx->call_cntr;
+       max_calls = funcctx->max_calls;
+       attinmeta = funcctx->attinmeta;
+       vcnts2 = funcctx->user_fctx;
+
+       /* do when there is more left to send */
+       if (call_cntr < max_calls) {
+               char **values;
+               HeapTuple tuple;
+               Datum result;
+
+               POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr);
+
+               /*
+                * Prepare a values array for building the returned tuple.
+                * This should be an array of C strings which will
+                * be processed later by the type input functions.
+                */
+               values = (char **) palloc(3 * sizeof(char *));
+
+               values[0] = (char *) palloc(sizeof(char) * (MAX_DBL_CHARLEN + 1));
+               values[1] = (char *) palloc(sizeof(char) * (MAX_INT_CHARLEN + 1));
+               values[2] = (char *) palloc(sizeof(char) * (MAX_DBL_CHARLEN + 1));
+
+               snprintf(
+                       values[0],
+                       sizeof(char) * (MAX_DBL_CHARLEN + 1),
+                       "%f",
+                       vcnts2[call_cntr].value
+               );
+               snprintf(
+                       values[1],
+                       sizeof(char) * (MAX_INT_CHARLEN + 1),
+                       "%d",
+                       vcnts2[call_cntr].count
+               );
+               snprintf(
+                       values[2],
+                       sizeof(char) * (MAX_DBL_CHARLEN + 1),
+                       "%f",
+                       vcnts2[call_cntr].proportion
+               );
+
+               /* build a tuple */
+               tuple = BuildTupleFromCStrings(attinmeta, values);
+
+               /* make the tuple into a datum */
+               result = HeapTupleGetDatum(tuple);
+
+               /* clean up (this is not really necessary) */
+               pfree(values[2]);
+               pfree(values[1]);
+               pfree(values[0]);
+               pfree(values);
+
+               SRF_RETURN_NEXT(funcctx, result);
+       }
+       /* do when there is no more left */
+       else {
+               pfree(vcnts2);
+               SRF_RETURN_DONE(funcctx);
+       }
+}
+
 struct rt_reclassexpr_t {
        struct rt_reclassrange {
                double min;
index 9d0ca23e6d7d9a301d3f38037bee747c41c1bc87..19bf95225a90e9174b82337ecffc4469b648c362 100644 (file)
@@ -1163,6 +1163,188 @@ CREATE OR REPLACE FUNCTION st_approxquantile(rast raster, hasnodata boolean, qua
        AS $$ SELECT quantile, value FROM _st_quantile($1, 1, $2, 1, ARRAY[$3]::double precision[]) $$
        LANGUAGE 'sql' IMMUTABLE STRICT;
 
+-----------------------------------------------------------------------
+-- ST_ValueCount
+-----------------------------------------------------------------------
+CREATE TYPE valuecount AS (
+       value double precision,
+       count integer,
+       proportion double precision
+);
+
+-- None of the functions can be strict as "searchvalues" and "roundto" can be NULL
+
+CREATE OR REPLACE FUNCTION _st_valuecount(rast raster, nband integer, hasnodata boolean, searchvalues double precision[], roundto double precision)
+       RETURNS SETOF valuecount
+       AS 'MODULE_PATHNAME', 'RASTER_valueCount'
+       LANGUAGE 'C' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, hasnodata boolean, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, $3, $4, $5) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, TRUE, $3, $4) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, searchvalues double precision[], OUT value double precision, OUT count integer)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, TRUE, $3, 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM _st_valuecount($1, 1, TRUE, $2, $3) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, searchvalues double precision[], OUT value double precision, OUT count integer)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM _st_valuecount($1, 1, TRUE, $2, 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, hasnodata boolean, searchvalue double precision, roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, $3, ARRAY[$4]::double precision[], $5) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, searchvalue double precision, roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, TRUE, ARRAY[$3]::double precision[], $4) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, nband integer, searchvalue double precision, OUT value double precision, OUT count integer)
+       RETURNS record
+       AS $$ SELECT value, count FROM _st_valuecount($1, $2, TRUE, ARRAY[$3]::double precision[], 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, searchvalue double precision, roundto double precision, OUT value double precision, OUT count integer)
+       RETURNS record
+       AS $$ SELECT value, count FROM _st_valuecount($1, 1, TRUE, ARRAY[$2]::double precision[], $3) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rast raster, searchvalue double precision, OUT value double precision, OUT count integer)
+       RETURNS record
+       AS $$ SELECT value, count FROM _st_valuecount($1, 1, TRUE, ARRAY[$2]::double precision[], 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION _st_valuecount(rastertable text, rastercolumn text, nband integer, hasnodata boolean, searchvalues double precision[], roundto double precision)
+       RETURNS SETOF valuecount
+       AS $$
+       DECLARE
+               curs refcursor;
+
+               ctable text;
+               ccolumn text;
+               rast raster;
+
+               vcnts valuecount;
+       BEGIN           
+               -- nband
+               IF nband < 1 THEN
+                       RAISE WARNING 'Invalid band index (must use 1-based). Returning NULL';
+                       RETURN;
+               END IF;
+
+               -- rastertable and rastercolumn
+               IF rastertable IS NULL THEN
+                       RAISE WARNING 'rastertable cannot be NULL. Returning NULL';
+                       RETURN;
+               ELSEIF rastercolumn IS NULL THEN
+                       RAISE WARNING 'rastercolumn cannot be NULL. Returning NULL';
+                       RETURN;
+               END IF;
+
+               -- clean rastertable and rastercolumn
+               ctable := quote_ident(rastertable);
+               ccolumn := quote_ident(rastercolumn);
+
+               BEGIN
+                       OPEN curs FOR EXECUTE 'SELECT '
+                                       || ccolumn
+                                       || ' FROM '
+                                       || ctable
+                                       || ' WHERE '
+                                       || ccolumn
+                                       || ' IS NOT NULL';
+               EXCEPTION
+                       WHEN OTHERS THEN
+                               RAISE WARNING 'Invalid table or column name. Returning NULL';
+                               RETURN;
+               END;
+
+               LOOP
+                       FETCH curs INTO rast;
+                       EXIT WHEN NOT FOUND;
+
+                       FOR vcnts IN SELECT * FROM _st_valuecount(rast, nband, hasnodata, searchvalues, roundto) LOOP
+                               IF vcnts IS NULL THEN
+                                       CONTINUE;
+                               END IF;
+                               --RAISE NOTICE 'vcnts = %', vcnts;
+                               
+                               RETURN NEXT vcnts;
+                       END LOOP;
+
+               END LOOP;
+
+               CLOSE curs;
+
+               RETURN;
+       END;
+       $$ LANGUAGE 'plpgsql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, hasnodata boolean, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS SETOF record
+       AS $$ SELECT value, sum(count) AS count FROM _st_valuecount($1, $2, $3, $4, $5, $6) GROUP BY 1 ORDER BY 1 $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, $3, TRUE, $4, $5) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, searchvalues double precision[], OUT value double precision, OUT count bigint)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, $3, TRUE, $4, 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, searchvalues double precision[], roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, 1, TRUE, $3, $4) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, searchvalues double precision[], OUT value double precision, OUT count bigint)
+       RETURNS SETOF record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, 1, TRUE, $3, 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, hasnodata boolean, searchvalue double precision, roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, $3, $4, ARRAY[$5]::double precision[], $6) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, searchvalue double precision, roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, $3, TRUE, ARRAY[$4]::double precision[], $5) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, nband integer, searchvalue double precision, OUT value double precision, OUT count bigint)
+       RETURNS record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, $3, TRUE, ARRAY[$4]::double precision[], 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, searchvalue double precision, roundto double precision, OUT value double precision, OUT count bigint)
+       RETURNS record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, 1, TRUE, ARRAY[$3]::double precision[], $4) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_valuecount(rastertable text, rastercolumn text, searchvalue double precision, OUT value double precision, OUT count bigint)
+       RETURNS record
+       AS $$ SELECT value, count FROM st_valuecount($1, $2, 1, TRUE, ARRAY[$3]::double precision[], 0) $$
+       LANGUAGE 'sql' IMMUTABLE;
+
 -----------------------------------------------------------------------
 -- ST_Reclass
 -----------------------------------------------------------------------
index 0d9319e6dbbed056e674888e3c693db7c94b5879..8cff63f95a375f8e5172067b3f187e33cc0cfe78 100644 (file)
@@ -1124,8 +1124,7 @@ static void testBandStats() {
        rtdealloc(stats->values);
        rtdealloc(stats);
 
-       rt_band_destroy(band);
-       rt_raster_destroy(raster);
+       deepRelease(raster);
 }
 
 static void testRasterReplaceBand() {
@@ -1257,9 +1256,8 @@ static void testBandReclass() {
        for (i = cnt - 1; i >= 0; i--)
                free(exprset[i]);
        free(exprset);
-       rt_band_destroy(band);
        rt_band_destroy(newband);
-       rt_raster_destroy(raster);
+       deepRelease(raster);
 }
 
 struct rt_gdaldriver_t {
@@ -1307,6 +1305,69 @@ static void testRasterToGDAL(rt_raster raster) {
        rt_raster_destroy(rast);
 }
 
+struct rt_valuecount_t {
+       double value;
+       uint32_t count;
+       double proportion;
+};
+static void testValueCount() {
+       rt_valuecount vcnts = NULL;
+
+       rt_raster raster;
+       rt_band band;
+       uint32_t x;
+       uint32_t xmax = 100;
+       uint32_t y;
+       uint32_t ymax = 100;
+       int rtn = 0;
+
+       double count[] = {3, 4, 5};
+
+       raster = rt_raster_new(xmax, ymax);
+       assert(raster); /* or we're out of virtual memory */
+       band = addBand(raster, PT_32BF, 0, 0);
+       CHECK(band);
+       rt_band_set_nodata(band, 0);
+
+       for (x = 0; x < xmax; x++) {
+               for (y = 0; y < ymax; y++) {
+                       rtn = rt_band_set_pixel(band, x, y, (((double) x * y) + (x + y) + (x + y * x)) / (x + y + 1));
+                       CHECK((rtn != -1));
+               }
+       }
+       vcnts = rt_band_get_value_count(band, 1, NULL, 0, 0, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       vcnts = rt_band_get_value_count(band, 1, NULL, 0, 0.01, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       vcnts = rt_band_get_value_count(band, 1, NULL, 0, 0.1, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       vcnts = rt_band_get_value_count(band, 1, NULL, 0, 1, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       vcnts = rt_band_get_value_count(band, 1, NULL, 0, 10, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       vcnts = rt_band_get_value_count(band, 1, count, 3, 1, &rtn);
+       CHECK(vcnts);
+       CHECK((rtn > 0));
+       rtdealloc(vcnts);
+
+       deepRelease(raster);
+}
+
 int
 main()
 {
@@ -1637,6 +1698,10 @@ main()
                testGDALDrivers();
                printf("Successfully tested rt_raster_gdal_drivers\n");
 
+               printf("Testing rt_band_get_value_count\n");
+               testValueCount();
+               printf("Successfully tested rt_band_get_value_count\n");
+
     deepRelease(raster);
 
     return EXIT_SUCCESS;
index 5829db9287f8d75d8060cb1b6325ed684c34d23c..ce9a7322fe7571f845da05296f731d9dfd418b0e 100644 (file)
@@ -75,6 +75,7 @@ TEST_BANDPROPS = \
        rt_minmax.sql \
        rt_histogram.sql \
        rt_quantile.sql \
+       rt_valuecount.sql \
        $(NULL)
 
 TEST_PIXEL = \
diff --git a/raster/test/regress/rt_valuecount.sql b/raster/test/regress/rt_valuecount.sql
new file mode 100644 (file)
index 0000000..85018f1
--- /dev/null
@@ -0,0 +1,246 @@
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, TRUE, ARRAY[]::double precision[], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, TRUE, NULL::double precision[], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, FALSE, NULL::double precision[], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, TRUE, NULL::double precision[], 0.1);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, ARRAY[3.1], 0.1);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, ARRAY[-10]);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, ARRAY[-10], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, ARRAY[-10, 3]);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, TRUE, 3.14, 0.19);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, FALSE, 3.14, 0.01);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, -10, 0.1);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 1, -10);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, -10., 10);
+SELECT round(value::numeric, 3), count FROM st_valuecount(
+       ST_SetValue(
+               ST_SetValue(
+                       ST_SetValue(
+                               ST_AddBand(
+                                       ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                       , 1, '64BF', 0, 0
+                               )
+                               , 1, 1, 1, -10
+                       )
+                       , 1, 5, 4, 0
+               )
+               , 1, 5, 5, 3.14159
+       )
+, 3.14159);
+BEGIN;
+CREATE TEMP TABLE test
+       ON COMMIT DROP AS
+       SELECT
+               rast.rast
+       FROM (
+               SELECT ST_SetValue(
+                       ST_SetValue(
+                               ST_SetValue(
+                                       ST_AddBand(
+                                               ST_MakeEmptyRaster(10, 10, 10, 10, 2, 2, 0, 0,-1)
+                                               , 1, '64BF', 0, 0
+                                       )
+                                       , 1, 1, 1, -10
+                               )
+                               , 1, 5, 4, 0
+                       )
+                       , 1, 5, 5, 3.14159
+               ) AS rast
+       ) AS rast
+       FULL JOIN (
+               SELECT generate_series(1, 10) AS id
+       ) AS id
+               ON 1 = 1;
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, FALSE, NULL::double precision[], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, ARRAY[-10]::double precision[], 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, ARRAY[1]::double precision[]);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', NULL::double precision[], 0.1);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', ARRAY[-1, 3.1]::double precision[], 0.1);
+
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, TRUE, NULL::double precision, 0);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, 3.14, 1);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 1, -1);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', 3.1, 0.1);
+SELECT round(value::numeric, 3), count FROM st_valuecount('test', 'rast', -9);
+ROLLBACK;
diff --git a/raster/test/regress/rt_valuecount_expected b/raster/test/regress/rt_valuecount_expected
new file mode 100644 (file)
index 0000000..48751d8
--- /dev/null
@@ -0,0 +1,36 @@
+-10.000|1
+3.142|1
+-10.000|1
+3.142|1
+-10.000|1
+0.000|98
+3.142|1
+-10.000|1
+3.100|1
+3.100|1
+-10.000|1
+-10.000|1
+-10.000|1
+3.000|0
+3.140|1
+3.140|1
+-10.000|1
+-10.000|1
+-10.000|1
+3.142|1
+BEGIN
+-10.000|10
+0.000|980
+3.142|10
+-10.000|10
+1.000|0
+-10.000|10
+3.100|10
+-1.000|0
+3.100|10
+-10.000|10
+3.000|10
+-1.000|0
+3.100|10
+-9.000|0
+COMMIT