* @param vals : the pixel values
* @param *nvals : the number of pixel values being returned
*
- * @return values of multiple pixels
+ * @return 0 on success, -1 on error
*/
int rt_band_get_pixel_line(
rt_band band,
assert(NULL != band);
+ /* initialize to no values */
+ *nvals = 0;
+
if (
x < 0 || x >= band->width ||
y < 0 || y >= band->height
return -1;
}
+ if (len < 1)
+ return 0;
+
data = rt_band_get_data(band);
if (data == NULL) {
rterror("rt_band_get_pixel_line: Cannot get band data");
_nvals = len;
maxlen = band->width * band->height;
- if ((maxlen - (int) (offset + _nvals)) < 0) {
- _nvals = _nvals - (((int) (offset + _nvals)) - maxlen);
+ if (((int) (offset + _nvals)) > maxlen) {
+ _nvals = maxlen - offset;
rtwarn("Limiting returning number values to %d", _nvals);
}
RASTER_DEBUGF(4, "_nvals = %d", _nvals);
* @param vals : the pixel values
* @param *nvals : the number of pixel values being returned
*
- * @return 0 if success, -1 otherwise
+ * @return 0 on success, -1 on error
*/
int rt_band_get_pixel_line(
rt_band band,
Datum RASTER_addBand(PG_FUNCTION_ARGS);
Datum RASTER_copyBand(PG_FUNCTION_ARGS);
Datum RASTER_addBandRasterArray(PG_FUNCTION_ARGS);
+Datum RASTER_tile(PG_FUNCTION_ARGS);
/* create new raster from existing raster's bands */
Datum RASTER_band(PG_FUNCTION_ARGS);
PG_RETURN_NULL();
}
+/**
+ * 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 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) {
+ elog(ERROR, "RASTER_tile: Unable to allocate memory for arguments");
+ MemoryContextSwitchTo(oldcontext);
+ 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 (!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:
+ elog(ERROR, "RASTER_tile: Invalid data type for band indexes");
+ rt_raster_destroy(arg1->raster.raster);
+ pfree(arg1);
+ 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_tile: Unable to allocate memory for band indexes");
+ rt_raster_destroy(arg1->raster.raster);
+ pfree(arg1);
+ 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_tile: Unable to reallocate memory for band indexes");
+ rt_raster_destroy(arg1->raster.raster);
+ pfree(arg1);
+ 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(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;
+ arg1->nbands = palloc(sizeof(int) * arg1->numbands);
+
+ if (arg1->nbands == NULL) {
+ elog(ERROR, "RASTER_dumpValues: Unable to allocate memory for pixel values");
+ rt_raster_destroy(arg1->raster.raster);
+ pfree(arg1);
+ 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);
+ }
+ }
+
+ /* 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 k = 0;
+ int tx = 0;
+ int ty = 0;
+ int rx = 0;
+ int ry = 0;
+ 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);
+
+ /* create empty raster */
+ tile = rt_raster_new(arg2->tile.width, arg2->tile.height);
+ rt_raster_set_geotransform_matrix(tile, arg2->raster.gt);
+ rt_raster_set_srid(tile, arg2->raster.srid);
+
+ /*
+ 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);
+
+ /* upper-left of tile */
+ rx = tx * arg2->tile.width;
+ ry = ty * arg2->tile.height;
+ POSTGIS_RT_DEBUGF(4, "raster coordinates = %d, %d", rx, ry);
+ if (!rt_raster_cell_to_geopoint(arg2->raster.raster, rx, ry, &ulx, &uly, arg2->raster.gt)) {
+ elog(ERROR, "RASTER_tile: Unable to compute the coordinates of the upper-left corner of the output tile");
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ SRF_RETURN_DONE(funcctx);
+ }
+ POSTGIS_RT_DEBUGF(4, "spatial coordinates = %f, %f", ulx, uly);
+ rt_raster_set_offsets(tile, 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, "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) {
+ elog(ERROR, "RASTER_tile: Unable to get band %d from source raster", arg2->nbands[i] + 1);
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ 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
+ nodataval = rt_band_get_min_value(_band);
+
+ if (rt_raster_generate_new_band(tile, pixtype, nodataval, hasnodata, nodataval, i) < 0) {
+ elog(ERROR, "RASTER_tile: Unable to add new band to output tile");
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ SRF_RETURN_DONE(funcctx);
+ }
+ band = rt_raster_get_band(tile, i);
+ if (band == NULL) {
+ elog(ERROR, "RASTER_tile: Unable to get newly added band from output tile");
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ 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) != 0) {
+ elog(ERROR, "RASTER_tile: Unable to get pixel line from source raster");
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ if (nvals && !rt_band_set_pixel_line(band, 0, j, vals, nvals)) {
+ elog(ERROR, "RASTER_tile: Unable to set pixel line of output tile");
+ rt_raster_destroy(tile);
+ rt_raster_destroy(arg2->raster.raster);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ SRF_RETURN_DONE(funcctx);
+ }
+ }
+ }
+
+ pgtile = rt_raster_serialize(tile);
+ rt_raster_destroy(tile);
+ if (!pgtile) {
+ rt_raster_destroy(arg2->raster.raster);
+ 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);
+ pfree(arg2->nbands);
+ pfree(arg2);
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
/**
* Copy a band from one raster to another one at the given position.
*/
$$
LANGUAGE 'plpgsql' IMMUTABLE STRICT; -- WITH (isstrict);
+-----------------------------------------------------------------------
+-- ST_Tile(raster)
+-----------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION st_tile(
+ rast raster,
+ width integer, height integer,
+ nband int[] DEFAULT NULL
+)
+ RETURNS SETOF raster
+ AS 'MODULE_PATHNAME','RASTER_tile'
+ LANGUAGE 'c' IMMUTABLE;
+
-----------------------------------------------------------------------
-- Raster Band Editors
-----------------------------------------------------------------------
rt_bytea \
box3d \
rt_addband \
- rt_band
+ rt_band \
+ rt_tile
TEST_PROPS = \
rt_dimensions \
--- /dev/null
+DROP TABLE IF EXISTS raster_tile;
+CREATE TABLE raster_tile AS
+ WITH foo AS (
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 0, 0, 1, -1, 0, 0, 0), 1, '8BUI', 1, 0), 2, '8BUI', 10, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 3, 0, 1, -1, 0, 0, 0), 1, '8BUI', 2, 0), 2, '8BUI', 20, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 6, 0, 1, -1, 0, 0, 0), 1, '8BUI', 3, 0), 2, '8BUI', 30, 0) AS rast UNION ALL
+
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 0, -3, 1, -1, 0, 0, 0), 1, '8BUI', 4, 0), 2, '8BUI', 40, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 3, -3, 1, -1, 0, 0, 0), 1, '8BUI', 5, 0), 2, '8BUI', 50, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 6, -3, 1, -1, 0, 0, 0), 1, '8BUI', 6, 0), 2, '8BUI', 60, 0) AS rast UNION ALL
+
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 0, -6, 1, -1, 0, 0, 0), 1, '8BUI', 7, 0), 2, '8BUI', 70, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 3, -6, 1, -1, 0, 0, 0), 1, '8BUI', 8, 0), 2, '8BUI', 80, 0) AS rast UNION ALL
+ SELECT ST_AddBand(ST_AddBand(ST_MakeEmptyRaster(3, 3, 6, -6, 1, -1, 0, 0, 0), 1, '8BUI', 9, 0), 2, '8BUI', 90, 0) AS rast
+ )
+ SELECT ST_Union(rast) AS rast FROM foo;
+
+WITH foo AS (
+ SELECT ST_Tile(rast, 3, 3) AS rast FROM raster_tile
+)
+SELECT
+ ST_DumpValues(rast)
+FROM foo;
+
+WITH foo AS (
+ SELECT ST_Tile(rast, 3, 3, ARRAY[1]) AS rast FROM raster_tile
+)
+SELECT
+ ST_DumpValues(rast)
+FROM foo;
+
+WITH foo AS (
+ SELECT ST_Tile(rast, 2, 2) AS rast FROM raster_tile
+)
+SELECT
+ ST_DumpValues(rast)
+FROM foo;
+
+WITH foo AS (
+ SELECT ST_Tile(rast, 1, 1) AS rast FROM raster_tile
+)
+SELECT
+ ST_DumpValues(rast)
+FROM foo;
+
+WITH foo AS (
+ SELECT ST_Tile(rast, 5, 5) AS rast FROM raster_tile
+)
+SELECT
+ ST_DumpValues(rast)
+FROM foo;
+
+DROP TABLE IF EXISTS raster_tile;
--- /dev/null
+NOTICE: table "raster_tile" does not exist, skipping
+(1,"{{1,1,1},{1,1,1},{1,1,1}}")
+(2,"{{10,10,10},{10,10,10},{10,10,10}}")
+(1,"{{2,2,2},{2,2,2},{2,2,2}}")
+(2,"{{20,20,20},{20,20,20},{20,20,20}}")
+(1,"{{3,3,3},{3,3,3},{3,3,3}}")
+(2,"{{30,30,30},{30,30,30},{30,30,30}}")
+(1,"{{4,4,4},{4,4,4},{4,4,4}}")
+(2,"{{40,40,40},{40,40,40},{40,40,40}}")
+(1,"{{5,5,5},{5,5,5},{5,5,5}}")
+(2,"{{50,50,50},{50,50,50},{50,50,50}}")
+(1,"{{6,6,6},{6,6,6},{6,6,6}}")
+(2,"{{60,60,60},{60,60,60},{60,60,60}}")
+(1,"{{7,7,7},{7,7,7},{7,7,7}}")
+(2,"{{70,70,70},{70,70,70},{70,70,70}}")
+(1,"{{8,8,8},{8,8,8},{8,8,8}}")
+(2,"{{80,80,80},{80,80,80},{80,80,80}}")
+(1,"{{9,9,9},{9,9,9},{9,9,9}}")
+(2,"{{90,90,90},{90,90,90},{90,90,90}}")
+(1,"{{1,1,1},{1,1,1},{1,1,1}}")
+(1,"{{2,2,2},{2,2,2},{2,2,2}}")
+(1,"{{3,3,3},{3,3,3},{3,3,3}}")
+(1,"{{4,4,4},{4,4,4},{4,4,4}}")
+(1,"{{5,5,5},{5,5,5},{5,5,5}}")
+(1,"{{6,6,6},{6,6,6},{6,6,6}}")
+(1,"{{7,7,7},{7,7,7},{7,7,7}}")
+(1,"{{8,8,8},{8,8,8},{8,8,8}}")
+(1,"{{9,9,9},{9,9,9},{9,9,9}}")
+(1,"{{1,1},{1,1}}")
+(2,"{{10,10},{10,10}}")
+(1,"{{1,2},{1,2}}")
+(2,"{{10,20},{10,20}}")
+(1,"{{2,2},{2,2}}")
+(2,"{{20,20},{20,20}}")
+(1,"{{3,3},{3,3}}")
+(2,"{{30,30},{30,30}}")
+(1,"{{3,NULL},{3,NULL}}")
+(2,"{{30,NULL},{30,NULL}}")
+(1,"{{1,1},{4,4}}")
+(2,"{{10,10},{40,40}}")
+(1,"{{1,2},{4,5}}")
+(2,"{{10,20},{40,50}}")
+(1,"{{2,2},{5,5}}")
+(2,"{{20,20},{50,50}}")
+(1,"{{3,3},{6,6}}")
+(2,"{{30,30},{60,60}}")
+(1,"{{3,NULL},{6,NULL}}")
+(2,"{{30,NULL},{60,NULL}}")
+(1,"{{4,4},{4,4}}")
+(2,"{{40,40},{40,40}}")
+(1,"{{4,5},{4,5}}")
+(2,"{{40,50},{40,50}}")
+(1,"{{5,5},{5,5}}")
+(2,"{{50,50},{50,50}}")
+(1,"{{6,6},{6,6}}")
+(2,"{{60,60},{60,60}}")
+(1,"{{6,NULL},{6,NULL}}")
+(2,"{{60,NULL},{60,NULL}}")
+(1,"{{7,7},{7,7}}")
+(2,"{{70,70},{70,70}}")
+(1,"{{7,8},{7,8}}")
+(2,"{{70,80},{70,80}}")
+(1,"{{8,8},{8,8}}")
+(2,"{{80,80},{80,80}}")
+(1,"{{9,9},{9,9}}")
+(2,"{{90,90},{90,90}}")
+(1,"{{9,NULL},{9,NULL}}")
+(2,"{{90,NULL},{90,NULL}}")
+(1,"{{7,7},{NULL,NULL}}")
+(2,"{{70,70},{NULL,NULL}}")
+(1,"{{7,8},{NULL,NULL}}")
+(2,"{{70,80},{NULL,NULL}}")
+(1,"{{8,8},{NULL,NULL}}")
+(2,"{{80,80},{NULL,NULL}}")
+(1,"{{9,9},{NULL,NULL}}")
+(2,"{{90,90},{NULL,NULL}}")
+(1,"{{9,NULL},{NULL,NULL}}")
+(2,"{{90,NULL},{NULL,NULL}}")
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{1}})
+(2,{{10}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{2}})
+(2,{{20}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{3}})
+(2,{{30}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{4}})
+(2,{{40}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{5}})
+(2,{{50}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{6}})
+(2,{{60}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{7}})
+(2,{{70}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{8}})
+(2,{{80}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,{{9}})
+(2,{{90}})
+(1,"{{1,1,1,2,2},{1,1,1,2,2},{1,1,1,2,2},{4,4,4,5,5},{4,4,4,5,5}}")
+(2,"{{10,10,10,20,20},{10,10,10,20,20},{10,10,10,20,20},{40,40,40,50,50},{40,40,40,50,50}}")
+(1,"{{2,3,3,3,NULL},{2,3,3,3,NULL},{2,3,3,3,NULL},{5,6,6,6,NULL},{5,6,6,6,NULL}}")
+(2,"{{20,30,30,30,NULL},{20,30,30,30,NULL},{20,30,30,30,NULL},{50,60,60,60,NULL},{50,60,60,60,NULL}}")
+(1,"{{4,4,4,5,5},{7,7,7,8,8},{7,7,7,8,8},{7,7,7,8,8},{NULL,NULL,NULL,NULL,NULL}}")
+(2,"{{40,40,40,50,50},{70,70,70,80,80},{70,70,70,80,80},{70,70,70,80,80},{NULL,NULL,NULL,NULL,NULL}}")
+(1,"{{5,6,6,6,NULL},{8,9,9,9,NULL},{8,9,9,9,NULL},{8,9,9,9,NULL},{NULL,NULL,NULL,NULL,NULL}}")
+(2,"{{50,60,60,60,NULL},{80,90,90,90,NULL},{80,90,90,90,NULL},{80,90,90,90,NULL},{NULL,NULL,NULL,NULL,NULL}}")