/* 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);
PG_RETURN_FLOAT8(pixvalue);
}
+/* ---------------------------------------------------------------- */
+/* ST_DumpValue 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: Unable to 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 hasnodata = 0;
+ double nodataval = 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to initialize argument structure");
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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:
+ elog(ERROR, "RASTER_dumpValues: Invalid data type for band indexes");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to allocate memory for pixel values");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to reallocate memory for pixel values");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to allocate memory for pixel values");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to allocate memory for pixel values");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to get band at index %d", arg1->nbands[z] + 1);
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ /* band's hasnodata and nodataval */
+ hasnodata = rt_band_get_hasnodata_flag(band);
+ if (hasnodata)
+ nodataval = rt_band_get_nodata(band);
+ POSTGIS_RT_DEBUGF(4, "(hasnodata, nodataval) = (%d, %f)", hasnodata, nodataval);
+
+ /* 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) {
+ elog(ERROR, "RASTER_dumpValues: Unable to allocate memory for pixel values");
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ 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;
+ 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) != 0) {
+ elog(ERROR, "RASTER_dumpValues: Unable to pixel (%d, %d) of band %d", x, y, arg1->nbands[z] + 1);
+ rtpg_dumpvalues_arg_destroy(arg1);
+ rt_raster_destroy(raster);
+ PG_FREE_IF_COPY(pgraster, 0);
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ arg1->values[z][i] = Float8GetDatum(val);
+ POSTGIS_RT_DEBUGF(5, "arg1->values[z][i] = %f", DatumGetFloat8(arg1->values[z][i]));
+ if (hasnodata) {
+ POSTGIS_RT_DEBUGF(5, "FLT_EQ?: %d", FLT_EQ(val, nodataval) ? 1 : 0);
+ }
+ POSTGIS_RT_DEBUGF(5, "clamped is?: %d", rt_band_clamped_value_is_nodata(band, val));
+
+ if (
+ exclude_nodata_value &&
+ hasnodata && (
+ FLT_EQ(val, nodataval) ||
+ rt_band_clamped_value_is_nodata(band, val) == 1
+ )
+ ) {
+ 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.
*/
AS 'MODULE_PATHNAME','RASTER_dumpAsPolygons'
LANGUAGE 'c' IMMUTABLE STRICT;
+-----------------------------------------------------------------------
+-- ST_DumpValues
+-----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION st_dumpvalues(
+ rast raster, nband integer[] DEFAULT NULL, exclude_nodata_value boolean DEFAULT TRUE,
+ OUT nband integer, OUT valarray double precision[][]
+)
+ RETURNS SETOF record
+ AS 'MODULE_PATHNAME','RASTER_dumpValues'
+ LANGUAGE 'c' IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION st_dumpvalues(rast raster, nband integer, exclude_nodata_value boolean DEFAULT TRUE)
+ RETURNS double precision[][]
+ AS $$ SELECT valarray FROM st_dumpvalues($1, ARRAY[$2]::integer[], $3) $$
+ LANGUAGE 'sql' IMMUTABLE STRICT;
+
-----------------------------------------------------------------------
-- ST_Polygon
-----------------------------------------------------------------------
rt_reclass \
rt_resample \
rt_asraster \
+ rt_dumpvalues
TEST_MAPALGEBRA = \
rt_mapalgebraexpr \
--- /dev/null
+SET client_min_messages TO warning;
+DROP TABLE IF EXISTS raster_dumpvalues;
+CREATE TABLE raster_dumpvalues (
+ rid integer,
+ rast raster
+);
+CREATE OR REPLACE FUNCTION make_raster(
+ rast raster DEFAULT NULL,
+ pixtype text DEFAULT '8BUI',
+ rows integer DEFAULT 3,
+ columns integer DEFAULT 3,
+ nodataval double precision DEFAULT 0,
+ start_val double precision DEFAULT 1,
+ step double precision DEFAULT 1,
+ skip_expr text DEFAULT NULL
+)
+ RETURNS raster
+ AS $$
+ DECLARE
+ x int;
+ y int;
+ value double precision;
+ values double precision[][][];
+ result boolean;
+ expr text;
+ _rast raster;
+ nband int;
+ BEGIN
+ IF rast IS NULL THEN
+ nband := 1;
+ _rast := ST_AddBand(ST_MakeEmptyRaster(columns, rows, 0, 0, 1, -1, 0, 0, 0), nband, pixtype, 0, nodataval);
+ ELSE
+ nband := ST_NumBands(rast) + 1;
+ _rast := ST_AddBand(rast, nband, pixtype, 0, nodataval);
+ END IF;
+
+ value := start_val;
+ values := array_fill(NULL::double precision, ARRAY[columns, rows]);
+
+ FOR y IN 1..columns LOOP
+ FOR x IN 1..rows LOOP
+ IF skip_expr IS NULL OR length(skip_expr) < 1 THEN
+ result := TRUE;
+ ELSE
+ expr := replace(skip_expr, '[v]'::text, value::text);
+ EXECUTE 'SELECT (' || expr || ')::boolean' INTO result;
+ END IF;
+
+ IF result IS TRUE THEN
+ values[y][x] := value;
+ END IF;
+
+ value := value + step;
+ END LOOP;
+ END LOOP;
+
+ _rast := ST_SetValues(_rast, nband, 1, 1, values);
+ RETURN _rast;
+ END;
+ $$ LANGUAGE 'plpgsql';
+
+INSERT INTO raster_dumpvalues
+ SELECT 1, make_raster(NULL, '8BSI', 3, 3, 0, 1) UNION ALL
+ SELECT 2, make_raster(NULL, '8BSI', 3, 3, 0, -1) UNION ALL
+ SELECT 3, make_raster(NULL, '8BSI', 3, 3, 0, 1) UNION ALL
+ SELECT 4, make_raster(NULL, '8BSI', 3, 3, 0, -2) UNION ALL
+ SELECT 5, make_raster(NULL, '8BSI', 3, 3, 0, 2)
+;
+
+INSERT INTO raster_dumpvalues
+ SELECT
+ rid + 10,
+ make_raster(rast, '16BSI', 3, 3, rid, (rid / 2)::integer)
+ FROM raster_dumpvalues
+ WHERE rid <= 10;
+
+INSERT INTO raster_dumpvalues
+ SELECT
+ rid + 10,
+ make_raster(rast, '32BSI', 3, 3, rid, (rid / 2)::integer)
+ FROM raster_dumpvalues
+ WHERE rid BETWEEN 11 AND 20;
+
+DROP FUNCTION IF EXISTS make_raster(raster, text, integer, integer, double precision, double precision, double precision, text);
+
+SELECT
+ rid,
+ (ST_DumpValues(rast)).*
+FROM raster_dumpvalues
+ORDER BY rid;
+
+SELECT
+ rid,
+ (ST_DumpValues(rast, ARRAY[3,2,1])).*
+FROM raster_dumpvalues
+WHERE rid > 20
+ORDER BY rid;
+
+DROP TABLE IF EXISTS raster_dumpvalues;
--- /dev/null
+1|1|{{1,2,3},{4,5,6},{7,8,9}}
+2|1|{{-1,NULL,1},{2,3,4},{5,6,7}}
+3|1|{{1,2,3},{4,5,6},{7,8,9}}
+4|1|{{-2,-1,NULL},{1,2,3},{4,5,6}}
+5|1|{{2,3,4},{5,6,7},{8,9,10}}
+11|1|{{1,2,3},{4,5,6},{7,8,9}}
+11|2|{{0,NULL,2},{3,4,5},{6,7,8}}
+12|1|{{-1,NULL,1},{2,3,4},{5,6,7}}
+12|2|{{1,NULL,3},{4,5,6},{7,8,9}}
+13|1|{{1,2,3},{4,5,6},{7,8,9}}
+13|2|{{1,2,NULL},{4,5,6},{7,8,9}}
+14|1|{{-2,-1,NULL},{1,2,3},{4,5,6}}
+14|2|{{2,3,NULL},{5,6,7},{8,9,10}}
+15|1|{{2,3,4},{5,6,7},{8,9,10}}
+15|2|{{2,3,4},{NULL,6,7},{8,9,10}}
+21|1|{{1,2,3},{4,5,6},{7,8,9}}
+21|2|{{0,NULL,2},{3,4,5},{6,7,8}}
+21|3|{{5,6,7},{8,9,10},{NULL,12,13}}
+22|1|{{-1,NULL,1},{2,3,4},{5,6,7}}
+22|2|{{1,NULL,3},{4,5,6},{7,8,9}}
+22|3|{{6,7,8},{9,10,11},{NULL,13,14}}
+23|1|{{1,2,3},{4,5,6},{7,8,9}}
+23|2|{{1,2,NULL},{4,5,6},{7,8,9}}
+23|3|{{6,7,8},{9,10,11},{12,NULL,14}}
+24|1|{{-2,-1,NULL},{1,2,3},{4,5,6}}
+24|2|{{2,3,NULL},{5,6,7},{8,9,10}}
+24|3|{{7,8,9},{10,11,12},{13,NULL,15}}
+25|1|{{2,3,4},{5,6,7},{8,9,10}}
+25|2|{{2,3,4},{NULL,6,7},{8,9,10}}
+25|3|{{7,8,9},{10,11,12},{13,14,NULL}}
+21|3|{{5,6,7},{8,9,10},{NULL,12,13}}
+21|2|{{0,NULL,2},{3,4,5},{6,7,8}}
+21|1|{{1,2,3},{4,5,6},{7,8,9}}
+22|3|{{6,7,8},{9,10,11},{NULL,13,14}}
+22|2|{{1,NULL,3},{4,5,6},{7,8,9}}
+22|1|{{-1,NULL,1},{2,3,4},{5,6,7}}
+23|3|{{6,7,8},{9,10,11},{12,NULL,14}}
+23|2|{{1,2,NULL},{4,5,6},{7,8,9}}
+23|1|{{1,2,3},{4,5,6},{7,8,9}}
+24|3|{{7,8,9},{10,11,12},{13,NULL,15}}
+24|2|{{2,3,NULL},{5,6,7},{8,9,10}}
+24|1|{{-2,-1,NULL},{1,2,3},{4,5,6}}
+25|3|{{7,8,9},{10,11,12},{13,14,NULL}}
+25|2|{{2,3,4},{NULL,6,7},{8,9,10}}
+25|1|{{2,3,4},{5,6,7},{8,9,10}}