From: Bborie Park Date: Tue, 22 May 2012 17:05:45 +0000 (+0000) Subject: Modified ST_Neighborhood and underlying functions to return 2D double X-Git-Tag: 2.1.0beta2~994 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f924f88a76616e1185366c33d10c1dcc3a965600;p=postgis Modified ST_Neighborhood and underlying functions to return 2D double precision array. This allows the output to be readily passed onto the ST_xxx4ma functions. git-svn-id: http://svn.osgeo.org/postgis/trunk@9789 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/raster/rt_core/rt_api.c b/raster/rt_core/rt_api.c index 0f9d3051a..f0bc93dca 100644 --- a/raster/rt_core/rt_api.c +++ b/raster/rt_core/rt_api.c @@ -1030,6 +1030,116 @@ rt_pixtype_get_min_value(rt_pixtype pixtype) { } } +/*- rt_pixel ----------------------------------------------------------*/ + +/* + * Convert an array of rt_pixel objects to two 2D arrays of value and NODATA + * + * @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 distance: the number of pixels around the center pixel + * @param value: pointer to pointer for 2D value array + * @param nodata: pointer to pointer for 2D NODATA array + * + * @return 0 on error, otherwise the X/Y axis length of value and NODATA + */ +int rt_pixel_set_to_array( + rt_pixel npixel, int count, + int x, int y, + uint16_t distance, + double ***value, + int ***nodata +) { + uint32_t i; + uint32_t j; + uint32_t length = 0; + double **values = NULL; + int **nodatas = NULL; + int zero[2] = {0}; + int _x; + int _y; + + assert(npixel != NULL); + assert(count > 0); + + /* length */ + length = distance * 2 + 1; + RASTER_DEBUGF(4, "length = %d", length); + + /* establish 2D arrays */ + values = rtalloc(sizeof(double *) * length); + nodatas = rtalloc(sizeof(int *) * length); + + if (values == NULL || nodatas == NULL) { + rterror("rt_pixel_set_to_array: Unable to allocate memory for 2D array"); + return 0; + } + + /* initialize */ + for (i = 0; i < length; i++) { + values[i] = rtalloc(sizeof(double) * length); + nodatas[i] = rtalloc(sizeof(int) * length); + + if (values[i] == NULL || nodatas[i] == NULL) { + rterror("rt_pixel_set_to_array: Unable to 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 0; + } + + /* set values to 0 */ + memset(values[i], 0, sizeof(double) * length); + + /* set nodatas to 1 */ + for (j = 0; j < length; j++) + nodatas[i][j] = 1; + } + + /* get zero, zero of grid */ + zero[0] = x - distance; + zero[1] = y - distance; + + /* 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[_x][_y] = npixel[i].value; + nodatas[_x][_y] = 0; + + RASTER_DEBUGF(4, "(x, y, nodata, value) = (%d, %d, %d, %f)", _x, _y, nodatas[_x][_y], values[_x][_y]); + } + + *value = &(*values); + *nodata = &(*nodatas); + + return length; +} + /*- rt_band ----------------------------------------------------------*/ /** @@ -2269,17 +2379,17 @@ int rt_band_get_nearest_pixel( /* 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); } else { - err = rt_band_get_pixel( + if (rt_band_get_pixel( band, _x, _y, &pixval - ); - if (err < 0) { + ) < 0) { rterror("rt_band_get_nearest_pixel: Unable to get pixel value"); if (count) rtdealloc(*npixels); return -1; @@ -2313,6 +2423,7 @@ int rt_band_get_nearest_pixel( npixel = &((*npixels)[count - 1]); npixel->x = _x; npixel->y = _y; + npixel->nodata = 0; npixel->value = pixval; } diff --git a/raster/rt_core/rt_api.h b/raster/rt_core/rt_api.h index 9e2182c39..9fcba016a 100644 --- a/raster/rt_core/rt_api.h +++ b/raster/rt_core/rt_api.h @@ -308,6 +308,29 @@ rt_pixtype rt_pixtype_index_from_name(const char* pixname); */ double rt_pixtype_get_min_value(rt_pixtype pixtype); +/*- rt_pixel ----------------------------------------------------------*/ + +/* + * Convert an array of rt_pixel objects to two 2D arrays of value and NODATA + * + * @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 distance: the number of pixels around the center pixel + * @param value: pointer to pointer for 2D value array + * @param nodata: pointer to pointer for 2D NODATA array + * + * @return 0 on error, otherwise the X/Y axis length of value and NODATA + */ +int rt_pixel_set_to_array( + rt_pixel npixel, int count, + int x, int y, + uint16_t distance, + double ***value, + int ***nodata +); + /*- rt_band ----------------------------------------------------------*/ /** @@ -1663,6 +1686,8 @@ struct rt_band_t { struct rt_pixel_t { int x; /* column */ int y; /* line */ + + uint8_t nodata; double value; }; diff --git a/raster/rt_pg/rt_pg.c b/raster/rt_pg/rt_pg.c index 52480a0fa..390a335f1 100644 --- a/raster/rt_pg/rt_pg.c +++ b/raster/rt_pg/rt_pg.c @@ -2560,178 +2560,240 @@ Datum RASTER_nearestValue(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(RASTER_neighborhood); Datum RASTER_neighborhood(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - TupleDesc tupdesc; + 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 = 0; + bool exclude_nodata_value = TRUE; + double pixval; - int call_cntr; - int max_calls; + rt_pixel npixels = NULL; + int count; + int length; + double **value2D = NULL; + int **nodata2D = NULL; - rt_pixel npixel1 = NULL; - rt_pixel npixel2 = 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; - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; + int16 typlen; + bool typbyval; + char typalign; - 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 distance = 0; - bool exclude_nodata_value = TRUE; + /* pgraster is null, return nothing */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - int count; + raster = rt_raster_deserialize(pgraster, FALSE); + if (!raster) { + elog(ERROR, "RASTER_neighborhood: Could not deserialize raster"); + PG_RETURN_NULL(); + } - /* create a function context for cross-call persistence */ - funcctx = SRF_FIRSTCALL_INIT(); + /* 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_RETURN_NULL(); + } - /* switch to memory context appropriate for multiple function calls */ - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + /* pixel column, 1-based */ + x = PG_GETARG_INT32(2); + _x = x - 1; - /* 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)); + /* pixel row, 1-based */ + y = PG_GETARG_INT32(3); + _y = y - 1; - raster = rt_raster_deserialize(pgraster, FALSE); - if (!raster) { - elog(ERROR, "RASTER_neighborhood: Could not deserialize raster"); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } + /* distance */ + distance = PG_GETARG_INT32(4); + if (distance < 1) { + elog(NOTICE, "Invalid value for distance (must be greater than zero). Returning NULL"); + rt_raster_destroy(raster); + PG_RETURN_NULL(); + } + distance = (uint16_t) distance; - /* 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); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } + /* exclude_nodata_value flag */ + if (!PG_ARGISNULL(5)) + exclude_nodata_value = PG_GETARG_BOOL(5); + + /* 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_RETURN_NULL(); + } - /* pixel column, 1-based */ - x = PG_GETARG_INT32(2); + /* get neighborhood */ + count = rt_band_get_nearest_pixel( + band, + _x, _y, + distance, + exclude_nodata_value, + &npixels + ); + /* error or no neighbors */ + if (count < 1) { + /* error */ + if (count < 0) + elog(NOTICE, "Unable to get the pixel's neighborhood for band at index %d", bandindex); + /* no neighbors */ + else + elog(NOTICE, "Pixel has no neighbors for band at distance %d", distance); + + rt_band_destroy(band); + rt_raster_destroy(raster); - /* pixel row, 1-based */ - y = PG_GETARG_INT32(3); + PG_RETURN_NULL(); + } - /* distance */ - distance = PG_GETARG_INT32(4); - if (distance < 1) { - elog(NOTICE, "Invalid value for distance (must be greater than zero). Returning 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 + ) < 0) { + elog(NOTICE, "Unable to get the pixel of band at index %d. Returning NULL", bandindex); + rt_band_destroy(band); rt_raster_destroy(raster); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); + PG_RETURN_NULL(); } + } + /* outside band extent, set to NODATA */ + else { + /* has NODATA, use NODATA */ + if (rt_band_get_hasnodata_flag(band)) + pixval = rt_band_get_nodata(band); + /* no NODATA, use min possible value */ + else + pixval = rt_band_get_min_value(band); + } + POSTGIS_RT_DEBUGF(4, "pixval: %f", pixval); - /* exclude_nodata_value flag */ - if (!PG_ARGISNULL(5)) - exclude_nodata_value = PG_GETARG_BOOL(5); + /* add pixel to neighborhood */ + if ( + !exclude_nodata_value || ( + exclude_nodata_value && + (rt_band_get_hasnodata_flag(band) != FALSE) && ( + FLT_NEQ(pixval, rt_band_get_nodata(band)) && + (rt_band_clamped_value_is_nodata(band, pixval) != 1) + ) + ) + ) { + count++; + npixels = (rt_pixel) repalloc(npixels, sizeof(struct rt_pixel_t) * count); + if (npixels == NULL) { + elog(ERROR, "RASTER_neighborhood: Unable to reallocate memory for neighborhood"); - /* 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_band_destroy(band); rt_raster_destroy(raster); - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); - } - /* get neighborhood */ - count = rt_band_get_nearest_pixel( - band, - x - 1, y - 1, - (uint16_t) distance, - exclude_nodata_value, - &npixel1 - ); - rt_band_destroy(band); - rt_raster_destroy(raster); - /* error or no neighbors */ - if (count < 1) { - /* error */ - if (count < 0) - elog(NOTICE, "Unable to get the pixel's neighborhood for band at index %d", bandindex); - /* no neighbors */ - else - elog(NOTICE, "Pixel has no neighbors for band at distance %d", distance); - - MemoryContextSwitchTo(oldcontext); - SRF_RETURN_DONE(funcctx); + PG_RETURN_NULL(); } - /* Store needed information */ - funcctx->user_fctx = npixel1; - - /* 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" - ) - )); - } + npixels[count - 1].x = _x; + npixels[count - 1].y = _y; + npixels[count - 1].nodata = 0; + npixels[count - 1].value = pixval; + } - BlessTupleDesc(tupdesc); - funcctx->tuple_desc = tupdesc; + /* free unnecessary stuff */ + rt_band_destroy(band); + rt_raster_destroy(raster); - MemoryContextSwitchTo(oldcontext); + /* convert set of rt_pixel to 2D array */ + length = rt_pixel_set_to_array( + npixels, count, + _x, _y, + distance, + &value2D, + &nodata2D + ); + pfree(npixels); + if (!length) { + elog(NOTICE, "Unable to create 2D array of neighborhood"); + PG_RETURN_NULL(); } - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); + /* dimensions of the PG array */ + dim[0] = length; + dim[1] = length; - call_cntr = funcctx->call_cntr; - max_calls = funcctx->max_calls; - tupdesc = funcctx->tuple_desc; - npixel2 = funcctx->user_fctx; + /* 1D arrays for values and nodata from 2D arrays */ + value1D = palloc(sizeof(Datum) * length * length); + nodata1D = palloc(sizeof(bool) * length * length); - /* do when there is more left to send */ - if (call_cntr < max_calls) { - int values_length = 3; - Datum values[values_length]; - bool *nulls = NULL; - HeapTuple tuple; - Datum result; + if (value1D == NULL || nodata1D == NULL) { + elog(ERROR, "RASTER_neighborhood: Unable to allocate memory for return 2D array"); - POSTGIS_RT_DEBUGF(3, "Result %d", call_cntr); + for (i = 0; i < length; i++) { + pfree(value2D[i]); + pfree(nodata2D[i]); + } + pfree(value2D); + pfree(nodata2D); - nulls = palloc(sizeof(bool) * values_length); - memset(nulls, FALSE, values_length); + PG_RETURN_NULL(); + } - /* x,y are 0-based, make 1-based for end users */ - values[0] = Int64GetDatum(npixel2[call_cntr].x + 1); - values[1] = Int64GetDatum(npixel2[call_cntr].y + 1); - values[2] = Float8GetDatum(npixel2[call_cntr].value); + /* copy values from 2D arrays to 1D arrays */ + k = 0; + for (i = 0; i < length; i++) { + for (j = 0; j < length; j++) { + nodata1D[k] = (bool) nodata2D[i][j]; + if (!nodata1D[k]) + value1D[k] = Float8GetDatum(value2D[i][j]); + else + value1D[k] = PointerGetDatum(NULL); - /* build a tuple */ - tuple = heap_form_tuple(tupdesc, values, nulls); + k++; + } + } - /* make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); + /* no more need for 2D arrays */ + for (i = 0; i < length; i++) { + pfree(value2D[i]); + pfree(nodata2D[i]); + } + pfree(value2D); + pfree(nodata2D); - /* clean up */ - pfree(nulls); + /* info about the type of item in the multi-dimensional array (float8). */ + get_typlenbyvalalign(FLOAT8OID, &typlen, &typbyval, &typalign); - SRF_RETURN_NEXT(funcctx, result); - } - /* do when there is no more left */ - else { - pfree(npixel2); - SRF_RETURN_DONE(funcctx); - } + 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/rtpostgis.sql.in.c b/raster/rt_pg/rtpostgis.sql.in.c index 7e8fcc599..d20336609 100644 --- a/raster/rt_pg/rtpostgis.sql.in.c +++ b/raster/rt_pg/rtpostgis.sql.in.c @@ -3540,11 +3540,9 @@ CREATE OR REPLACE FUNCTION st_neighborhood( rast raster, band integer, ix integer, iy integer, distance integer, - exclude_nodata_value boolean DEFAULT TRUE, - OUT x integer, OUT y integer, - OUT val double precision + exclude_nodata_value boolean DEFAULT TRUE ) - RETURNS SETOF record + RETURNS double precision[][] AS 'MODULE_PATHNAME', 'RASTER_neighborhood' LANGUAGE 'C' IMMUTABLE STRICT; @@ -3552,27 +3550,24 @@ CREATE OR REPLACE FUNCTION st_neighborhood( rast raster, ix integer, iy integer, distance integer, - exclude_nodata_value boolean DEFAULT TRUE, - OUT x integer, OUT y integer, - OUT val double precision + exclude_nodata_value boolean DEFAULT TRUE ) - RETURNS SETOF record - AS $$ SELECT x, y, val FROM st_neighborhood($1, 1, $2, $3, $4, $5) $$ + RETURNS double precision[][] + AS $$ SELECT st_neighborhood($1, 1, $2, $3, $4, $5) $$ LANGUAGE 'SQL' IMMUTABLE STRICT; CREATE OR REPLACE FUNCTION st_neighborhood( rast raster, band integer, pt geometry, distance integer, - exclude_nodata_value boolean DEFAULT TRUE, - OUT x integer, OUT y integer, - OUT val double precision + exclude_nodata_value boolean DEFAULT TRUE ) - RETURNS SETOF record + RETURNS double precision[][] AS $$ DECLARE wx int; wy int; + rtn double precision[][]; BEGIN IF (st_geometrytype($3) != 'ST_Point') THEN RAISE EXCEPTION 'Attempting to get the neighbor of a pixel with a non-point geometry'; @@ -3580,15 +3575,14 @@ CREATE OR REPLACE FUNCTION st_neighborhood( wx := st_x($3); wy := st_y($3); - RETURN QUERY - SELECT x, y, val - FROM st_neighborhood( - $1, $2, - st_world2rastercoordx(rast, wx, wy), - st_world2rastercoordy(rast, wx, wy), - $4, - $5 - ); + SELECT st_neighborhood( + $1, $2, + st_world2rastercoordx(rast, wx, wy), + st_world2rastercoordy(rast, wx, wy), + $4, + $5 + ) INTO rtn; + RETURN rtn; END; $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT; @@ -3596,12 +3590,10 @@ CREATE OR REPLACE FUNCTION st_neighborhood( rast raster, pt geometry, distance integer, - exclude_nodata_value boolean DEFAULT TRUE, - OUT x integer, OUT y integer, - OUT val double precision + exclude_nodata_value boolean DEFAULT TRUE ) - RETURNS SETOF record - AS $$ SELECT x, y, val FROM st_neighborhood($1, 1, $2, $3, $4) $$ + RETURNS double precision[][] + AS $$ SELECT st_neighborhood($1, 1, $2, $3, $4) $$ LANGUAGE 'SQL' IMMUTABLE STRICT; ------------------------------------------------------------------------------ diff --git a/raster/test/core/testapi.c b/raster/test/core/testapi.c index 1baebb177..492e085de 100644 --- a/raster/test/core/testapi.c +++ b/raster/test/core/testapi.c @@ -2598,6 +2598,10 @@ static void testNearestPixel() { const int maxY = 10; rt_pixel npixels = NULL; + int length; + double **value; + int **nodata; + rast = rt_raster_new(maxX, maxY); assert(rast); @@ -2734,6 +2738,24 @@ static void testNearestPixel() { &npixels ); CHECK((rtn == 2)); + + length = rt_pixel_set_to_array( + npixels, rtn, + -1, 1, + 1, + &value, + &nodata + ); + CHECK((length == 3)); + + for (x = 0; x < length; x++) { + rtdealloc(nodata[x]); + rtdealloc(value[x]); + } + + rtdealloc(nodata); + rtdealloc(value); + if (rtn) rtdealloc(npixels); diff --git a/raster/test/regress/rt_neighborhood_expected b/raster/test/regress/rt_neighborhood_expected index 0f0011d61..36b8ca1e2 100644 --- a/raster/test/regress/rt_neighborhood_expected +++ b/raster/test/regress/rt_neighborhood_expected @@ -1,91 +1,12 @@ NOTICE: table "raster_neighborhood" does not exist, skipping -(1,2,1) -(2,2,1) -(2,1,1) -(2,1,1) -(3,1,1) -(1,3,1) -(3,3,1) -(1,2,1) -(3,2,1) -(4,4,1) -(5,4,1) -(6,4,1) -(4,6,1) -(5,6,1) -(6,6,1) -(4,5,1) -(4,4,1) -(5,4,1) -(6,4,1) -(4,6,1) -(5,6,1) -(6,6,1) -(4,5,1) -(3,3,1) -(4,3,1) -(6,3,1) -(7,3,1) -(3,7,1) -(5,7,1) -(6,7,1) -(3,4,1) -(3,6,1) -(7,4,1) -(7,5,1) -(7,6,1) -(10,10,1) +{{NULL,NULL,NULL},{NULL,NULL,1},{NULL,1,1}} +{{NULL,1,1},{1,1,NULL},{1,1,1}} +{{1,1,1},{1,1,1},{1,NULL,1}} +{{1,1,NULL,1,1},{1,1,1,1,NULL},{NULL,1,1,1,1},{1,1,NULL,1,1},{1,1,1,1,NULL}} +{{1,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}} NOTICE: Pixel has no neighbors for band at distance 1 NOTICE: Pixel has no neighbors for band at distance 1 -(1,3,1) -(1,2,1) +{{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,1,1}} NOTICE: Pixel has no neighbors for band at distance 1 NOTICE: Pixel has no neighbors for band at distance 3 -(-10,2,0) -(-9,2,0) -(-8,2,0) -(-10,4,0) -(-9,4,0) -(-8,4,0) -(-10,3,0) -(-8,3,0) -(-11,1,0) -(-10,1,0) -(-9,1,0) -(-8,1,0) -(-7,1,0) -(-11,5,0) -(-10,5,0) -(-9,5,0) -(-8,5,0) -(-7,5,0) -(-11,2,0) -(-11,3,0) -(-11,4,0) -(-7,2,0) -(-7,3,0) -(-7,4,0) -(-12,0,0) -(-11,0,0) -(-10,0,0) -(-9,0,0) -(-8,0,0) -(-7,0,0) -(-6,0,0) -(-12,6,0) -(-11,6,0) -(-10,6,0) -(-9,6,0) -(-8,6,0) -(-7,6,0) -(-6,6,0) -(-12,1,0) -(-12,2,0) -(-12,3,0) -(-12,4,0) -(-12,5,0) -(-6,1,0) -(-6,2,0) -(-6,3,0) -(-6,4,0) -(-6,5,0) +{{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,0}}