From: Björn Harrtell Date: Tue, 7 Mar 2017 20:32:18 +0000 (+0000) Subject: Reworked ST_AsMVT and new ST_AsMVTGeom implementation X-Git-Tag: 2.4.0alpha~160 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=86e94be12086bcb88ff82b0c9133190259a937e8;p=postgis Reworked ST_AsMVT and new ST_AsMVTGeom implementation References #3712 git-svn-id: http://svn.osgeo.org/postgis/trunk@15323 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/doc/reference_output.xml b/doc/reference_output.xml index 579553411..6eb31654a 100644 --- a/doc/reference_output.xml +++ b/doc/reference_output.xml @@ -1344,21 +1344,68 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5); + + + ST_AsMVTGeom + + Transform a geometry into the coordinate space of a Mapbox Vector Tile. + + + + + geometry ST_AsMVTGeom + geometry geom + box2d bounds + int4 extent + int4 buffer + bool clip_geom + + + + + + Description + + Transform a geometry into the coordinate space of a Mapbox Vector Tile of a set of rows corresponding to a Layer. + Makes best effort to keep and even correct validity and might collapse geometry into a lower dimension in the process. + + + geom is the geometry to transform. + bounds is the geometric bounds of the tile contents without buffer. + extent is the tile extent in tile coordinate space as defined by the specification. If NULL it will default to 4096. + buffer is the buffer distance in tile coordinate space to optionally clip geometries. If NULL it will default to 0. + clip_geom is a boolean to control if geometries should be clipped or encoded as is. If NULL it will default to true. + + Availability: 2.4.0 + + + + Examples + + + + + ST_AsMVT - Return a Mapbox Vector Tile representation of a set of rows. + Return a Mapbox Vector Tile representation of a set of rows. bytea ST_AsMVT text name - box2d bounds int4 extent - int4 buffer - bool clip_geom text geom_name anyelement row @@ -1368,17 +1415,15 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5); Description - Return a Mapbox Vector Tile representation (https://www.mapbox.com/vector-tiles/specification/) of a set of rows corresponding to a Layer. + Return a Mapbox Vector Tile representation of a set of rows corresponding to a Layer. Multiple calls can be concatenated to a tile with multiple Layers. - Geometry will be automatically scaled from the geometric bounds to tile screen space and repeated coordinates thrown away. - Other row data will be encoded as attributes without redundancy as described by the specification. + Geometry is assumed to be in tile coordinate space and valid as per specification. + Typically ST_AsMVTGeom can be used to transform geometry into tile coordinate space. + Other row data will be encoded as attributes. name is the name of the Layer - bounds is the bounds of the tile in the coordinate system of the geometry field in the row data. extent is the tile extent in screen space as defined by the specification. If NULL it will default to 4096. - buffer is the buffer distance in screen space to optionally clip geometries. If NULL it will default to 0. - clip_geom is a boolean to control if geometries should be clipped or encoded as is. If NULL it will default to true. geom_name is the name of the geometry column in the row data. row row data with at least a geometry column. @@ -1387,11 +1432,12 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5); Examples - diff --git a/postgis/lwgeom_out_mvt.c b/postgis/lwgeom_out_mvt.c index 5d61359c0..f97da9815 100644 --- a/postgis/lwgeom_out_mvt.c +++ b/postgis/lwgeom_out_mvt.c @@ -35,6 +35,35 @@ #include "vector_tile.pb-c.h" #endif /* HAVE_LIBPROTOBUF */ +/** + * Process input parameters to mvt_geom and returned serialized geometry + */ +PG_FUNCTION_INFO_V1(ST_AsMVTGeom); +Datum ST_AsMVTGeom(PG_FUNCTION_ARGS) +{ + LWGEOM *lwgeom_in, *lwgeom_out; + GSERIALIZED *geom_in, *geom_out; + GBOX *bounds; + int extent, buffer; + bool clip_geom; + if (PG_ARGISNULL(0)) + lwerror("ST_AsMVTGeom: geom cannot be null"); + geom_in = PG_GETARG_GSERIALIZED_P(0); + lwgeom_in = lwgeom_from_gserialized(geom_in); + if (PG_ARGISNULL(1)) + lwerror("ST_AsMVTGeom: parameter bounds cannot be null"); + bounds = (GBOX *) PG_GETARG_POINTER(1); + extent = PG_ARGISNULL(2) ? 4096 : PG_GETARG_INT32(2); + buffer = PG_ARGISNULL(3) ? 0 : PG_GETARG_INT32(3); + clip_geom = PG_ARGISNULL(4) ? true : PG_GETARG_BOOL(4); + lwgeom_out = mvt_geom(lwgeom_in, bounds, extent, buffer, clip_geom); + lwgeom_free(lwgeom_in); + geom_out = geometry_serialize(lwgeom_out); + lwgeom_free(lwgeom_out); + PG_FREE_IF_COPY(geom_in, 0); + PG_RETURN_POINTER(geom_out); +} + /** * Process input parameters and row data into state */ @@ -56,26 +85,26 @@ Datum pgis_asmvt_transfn(PG_FUNCTION_ARGS) ctx = palloc(sizeof(*ctx)); if (PG_ARGISNULL(1)) lwerror("pgis_asmvt_transfn: parameter name cannot be null"); - ctx->name = text_to_cstring(PG_GETARG_TEXT_P(1)); - if (PG_ARGISNULL(2)) - lwerror("pgis_asmvt_transfn: parameter bounds cannot be null"); - ctx->bounds = (GBOX *) PG_GETARG_POINTER(2); - ctx->extent = PG_ARGISNULL(3) ? 4096 : PG_GETARG_INT32(3); - ctx->buffer = PG_ARGISNULL(4) ? 0 : PG_GETARG_INT32(4); - ctx->clip_geoms = PG_ARGISNULL(5) ? true : PG_GETARG_BOOL(5); - if (PG_ARGISNULL(6)) + text *name = PG_GETARG_TEXT_P(1); + ctx->name = text_to_cstring(name); + PG_FREE_IF_COPY(name, 1); + ctx->extent = PG_ARGISNULL(2) ? 4096 : PG_GETARG_INT32(2); + if (PG_ARGISNULL(3)) lwerror("pgis_asmvt_transfn: parameter geom_name cannot be null"); - ctx->geom_name = text_to_cstring(PG_GETARG_TEXT_P(6)); + text *geom_name = PG_GETARG_TEXT_P(3); + ctx->geom_name = text_to_cstring(geom_name); + PG_FREE_IF_COPY(geom_name, 3); mvt_agg_init_context(ctx); } else { ctx = (struct mvt_agg_context *) PG_GETARG_POINTER(0); } - if (!type_is_rowtype(get_fn_expr_argtype(fcinfo->flinfo, 7))) + if (!type_is_rowtype(get_fn_expr_argtype(fcinfo->flinfo, 4))) lwerror("pgis_asmvt_transfn: parameter row cannot be other than a rowtype"); - ctx->row = PG_GETARG_HEAPTUPLEHEADER(7); + ctx->row = PG_GETARG_HEAPTUPLEHEADER(4); mvt_agg_transfn(ctx); + PG_FREE_IF_COPY(ctx->row, 4); PG_RETURN_POINTER(ctx); #endif } diff --git a/postgis/mvt.c b/postgis/mvt.c index 8c077f83b..ee19248be 100644 --- a/postgis/mvt.c +++ b/postgis/mvt.c @@ -30,7 +30,7 @@ #define FEATURES_CAPACITY_INITIAL 50 -enum mvd_cmd_id { +enum mvt_cmd_id { CMD_MOVE_TO = 1, CMD_LINE_TO = 2, CMD_CLOSE_PATH = 7 @@ -78,7 +78,7 @@ struct mvt_kv_bool_value { UT_hash_handle hh; }; -static inline uint32_t c_int(enum mvd_cmd_id id, uint32_t count) +static inline uint32_t c_int(enum mvt_cmd_id id, uint32_t count) { return (id & 0x7) | (count << 3); } @@ -88,38 +88,27 @@ static inline uint32_t p_int(int32_t value) return (value << 1) ^ (value >> 31); } -static inline int32_t scale_translate(double value, double res, - double offset) { - return (value - offset) / res; -} - static uint32_t encode_ptarray(struct mvt_agg_context *ctx, enum mvt_type type, POINTARRAY *pa, uint32_t *buffer, int32_t *px, int32_t *py) { - POINT2D p; uint32_t offset = 0; uint32_t i, c = 0; int32_t dx, dy, x, y; - uint32_t cmd_offset; - double xres = ctx->xres; - double yres = ctx->yres; - double xmin = ctx->bounds->xmin; - double ymin = ctx->bounds->ymin; - /* loop points, scale to extent and add to buffer if not repeated */ + /* loop points and add to buffer */ for (i = 0; i < pa->npoints; i++) { - /* move offset for command and remember the latest */ + /* move offset for command */ if (i == 0 || (i == 1 && type > MVT_POINT)) - cmd_offset = offset++; - getPoint2d_p(pa, i, &p); - x = scale_translate(p.x, xres, xmin); - y = ctx->extent - scale_translate(p.y, yres, ymin); + offset++; + /* skip closing point for rings */ + if (type == MVT_RING && i == pa->npoints - 1) + break; + const POINT2D *p = getPoint2d_cp(pa, i); + x = p->x; + y = p->y; dx = x - *px; dy = y - *py; - /* skip point if repeated */ - if (i > type && dx == 0 && dy == 0) - continue; buffer[offset++] = p_int(dx); buffer[offset++] = p_int(dy); *px = x; @@ -130,12 +119,12 @@ static uint32_t encode_ptarray(struct mvt_agg_context *ctx, enum mvt_type type, /* determine initial move and eventual line command */ if (type == MVT_POINT) { /* point or multipoint, use actual number of point count */ - buffer[cmd_offset] = c_int(CMD_MOVE_TO, c); + buffer[0] = c_int(CMD_MOVE_TO, c); } else { /* line or polygon, assume count 1 */ - buffer[cmd_offset - 3] = c_int(CMD_MOVE_TO, 1); - /* line command with move point subtracted from count */ - buffer[cmd_offset] = c_int(CMD_LINE_TO, c - 1); + buffer[0] = c_int(CMD_MOVE_TO, 1); + /* line command with move point subtracted from count */ + buffer[3] = c_int(CMD_LINE_TO, c - 1); } /* add close command if ring */ @@ -216,7 +205,7 @@ static void encode_poly(struct mvt_agg_context *ctx, LWPOLY *lwpoly) feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POLYGON; feature->has_type = 1; for (i = 0; i < lwpoly->nrings; i++) - c += 3 + lwpoly->rings[i]->npoints * 2; + c += 3 + ((lwpoly->rings[i]->npoints - 1) * 2); feature->geometry = palloc(sizeof(*feature->geometry) * c); for (i = 0; i < lwpoly->nrings; i++) offset += encode_ptarray(ctx, MVT_RING, @@ -236,87 +225,18 @@ static void encode_mpoly(struct mvt_agg_context *ctx, LWMPOLY *lwmpoly) feature->has_type = 1; for (i = 0; i < lwmpoly->ngeoms; i++) for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++) - c += 3 + poly->rings[j]->npoints * 2; + c += 3 + ((poly->rings[j]->npoints - 1) * 2); feature->geometry = palloc(sizeof(*feature->geometry) * c); for (i = 0; i < lwmpoly->ngeoms; i++) for (j = 0; poly = lwmpoly->geoms[i], j < poly->nrings; j++) - offset += encode_ptarray(ctx, MVT_LINE, + offset += encode_ptarray(ctx, MVT_RING, poly->rings[j], feature->geometry + offset, &px, &py); feature->n_geometry = offset; } -static bool check_geometry_size(LWGEOM *lwgeom, struct mvt_agg_context *ctx) -{ - GBOX bbox; - lwgeom_calculate_gbox(lwgeom, &bbox); - double w = bbox.xmax - bbox.xmin; - double h = bbox.ymax - bbox.ymin; - return w >= ctx->xres * 2 && h >= ctx->yres * 2; -} - -static bool clip_geometry(struct mvt_agg_context *ctx) -{ - LWGEOM *lwgeom = ctx->lwgeom; - GBOX *bounds = ctx->bounds; - int type = lwgeom->type; - double buffer_map_xunits = ctx->xres * ctx->buffer; - double buffer_map_yunits = ctx->yres * ctx->buffer; - double x0 = bounds->xmin - buffer_map_xunits; - double y0 = bounds->ymin - buffer_map_yunits; - double x1 = bounds->xmax + buffer_map_xunits; - double y1 = bounds->ymax + buffer_map_yunits; -#if POSTGIS_GEOS_VERSION < 35 - LWPOLY *lwenv = lwpoly_construct_envelope(0, x0, y0, x1, y1); - lwgeom = lwgeom_intersection(lwgeom, lwpoly_as_lwgeom(lwenv)); -#else - lwgeom = lwgeom_clip_by_rect(lwgeom, x0, y0, x1, y1); -#endif - if (lwgeom_is_empty(lwgeom)) - return false; - if (lwgeom->type == COLLECTIONTYPE) { - if (type == MULTIPOLYGONTYPE) - type = POLYGONTYPE; - else if (type == MULTILINETYPE) - type = LINETYPE; - else if (type == MULTIPOINTTYPE) - type = POINTTYPE; - lwgeom = lwcollection_as_lwgeom(lwcollection_extract((LWCOLLECTION*)lwgeom, type)); - } - ctx->lwgeom = lwgeom; - return true; -} - -static bool coerce_geometry(struct mvt_agg_context *ctx) -{ - LWGEOM *lwgeom; - - if (ctx->clip_geoms && !clip_geometry(ctx)) - return false; - - lwgeom = ctx->lwgeom; - - switch (lwgeom->type) { - case POINTTYPE: - return true; - case LINETYPE: - case POLYGONTYPE: - case MULTIPOINTTYPE: - case MULTILINETYPE: - case MULTIPOLYGONTYPE: - if (!check_geometry_size(lwgeom, ctx)) - ctx->lwgeom = lwgeom_centroid(lwgeom); - return true; - default: lwerror("encode_geometry: '%s' geometry type not supported", - lwtype_name(lwgeom->type)); - } - - return true; -} - -static void encode_geometry(struct mvt_agg_context *ctx) +static void encode_geometry(struct mvt_agg_context *ctx, LWGEOM *lwgeom) { - LWGEOM *lwgeom = ctx->lwgeom; int type = lwgeom->type; switch (type) { @@ -356,7 +276,7 @@ static void encode_keys(struct mvt_agg_context *ctx) char *key = tupdesc->attrs[i]->attname.data; if (strcmp(key, ctx->geom_name) == 0) { ctx->geom_index = i; - geom_name_found = true; + geom_name_found = 1; continue; } keys[k++] = key; @@ -445,7 +365,8 @@ static void encode_values(struct mvt_agg_context *ctx) } static void parse_value_as_string(struct mvt_agg_context *ctx, Oid typoid, - Datum datum, uint32_t *tags, uint32_t c, uint32_t k) { + Datum datum, uint32_t *tags, uint32_t c, uint32_t k) +{ struct mvt_kv_string_value *kv; Oid foutoid; bool typisvarlena; @@ -523,24 +444,128 @@ static void parse_values(struct mvt_agg_context *ctx) ctx->feature->tags = tags; } +static void ptarray_mirror_y(POINTARRAY *pa, uint32_t extent) +{ + int i; + POINT2D *p; + + for (i = 0; i < pa->npoints; i++) { + p = (POINT2D *) getPoint_internal(pa, i); + p->y = extent - p->y; + } +} + +static void lwgeom_mirror_y(LWGEOM *in, uint32_t extent) +{ + LWCOLLECTION *col; + LWPOLY *poly; + int i; + + if ( (!in) || lwgeom_is_empty(in) ) return; + + switch (in->type) { + case POINTTYPE: + ptarray_mirror_y(lwgeom_as_lwpoint(in)->point, extent); + break; + case LINETYPE: + ptarray_mirror_y(lwgeom_as_lwline(in)->points, extent); + break; + case POLYGONTYPE: + poly = (LWPOLY *) in; + for (i=0; inrings; i++) + ptarray_mirror_y(poly->rings[i], extent); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + col = (LWCOLLECTION *) in; + for (i=0; ingeoms; i++) + lwgeom_mirror_y(col->geoms[i], extent); + break; + default: + lwerror("lwgeom_mirror_y: unsupported geometry type: %s", + lwtype_name(in->type)); + return; + } + + lwgeom_drop_bbox(in); + lwgeom_add_bbox(in); +} + +/** + * Transform a geometry into vector tile coordinate space. + * + * Makes best effort to keep validity. Might collapse geometry into lower + * dimension. + */ +LWGEOM *mvt_geom(LWGEOM *lwgeom, GBOX *gbox, uint32_t extent, uint32_t buffer, + bool clip_geom) +{ + double width = gbox->xmax - gbox->xmin; + double height = gbox->ymax - gbox->ymin; + double resx = width / extent; + double resy = height / extent; + + if (width == 0 || height == 0) + lwerror("mvt_geom: bounds width or height cannot be 0"); + + if (extent == 0) + lwerror("mvt_geom: extent cannot be 0"); + + if (clip_geom) { + double buffer_map_xunits = resx * buffer; + double buffer_map_yunits = resy * buffer; + double x0 = gbox->xmin - buffer_map_xunits; + double y0 = gbox->ymin - buffer_map_yunits; + double x1 = gbox->xmax + buffer_map_xunits; + double y1 = gbox->ymax + buffer_map_yunits; +#if POSTGIS_GEOS_VERSION < 35 + LWPOLY *lwenv = lwpoly_construct_envelope(0, x0, y0, x1, y1); + lwgeom = lwgeom_intersection(lwgeom, lwpoly_as_lwgeom(lwenv)); + lwpoly_free(lwenv); +#else + lwgeom = lwgeom_clip_by_rect(lwgeom, x0, y0, x1, y1); +#endif + } + + POINT4D factors; + factors.x = resx; + factors.y = resy; + factors.z = 1; + factors.m = 1; + + lwgeom_scale(lwgeom, &factors); + + gridspec grid; + memset(&grid, 0, sizeof(gridspec)); + grid.ipx = 0; + grid.ipy = 0; + grid.xsize = 1; + grid.ysize = 1; + + LWGEOM *lwgeom_out = lwgeom_grid(lwgeom, &grid); + + if (lwgeom_out == NULL) + lwgeom_out = lwgeom_grid(lwgeom_centroid(lwgeom), &grid); + + lwgeom_force_clockwise(lwgeom_out); + lwgeom_mirror_y(lwgeom_out, extent); + lwgeom_out = lwgeom_make_valid(lwgeom_out); + + return lwgeom_out; +} + /** * Initialize aggregation context. */ void mvt_agg_init_context(struct mvt_agg_context *ctx) { VectorTile__Tile__Layer *layer; - double width, height; - width = ctx->bounds->xmax - ctx->bounds->xmin; - height = ctx->bounds->ymax - ctx->bounds->ymin; - - if (width == 0 || height == 0) - lwerror("mvt_agg_init_context: bounds width or height cannot be 0"); if (ctx->extent == 0) lwerror("mvt_agg_init_context: extent cannot be 0"); - ctx->xres = width / ctx->extent; - ctx->yres = height / ctx->extent; ctx->features_capacity = FEATURES_CAPACITY_INITIAL; ctx->string_values_hash = NULL; ctx->float_values_hash = NULL; @@ -555,6 +580,7 @@ void mvt_agg_init_context(struct mvt_agg_context *ctx) vector_tile__tile__layer__init(layer); layer->version = 2; layer->name = ctx->name; + layer->has_extent = 1; layer->extent = ctx->extent; layer->features = palloc (ctx->features_capacity * sizeof(*layer->features)); @@ -592,14 +618,13 @@ void mvt_agg_transfn(struct mvt_agg_context *ctx) if (!datum) lwerror("mvt_agg_transfn: geometry column cannot be null"); GSERIALIZED *gs = (GSERIALIZED *) PG_DETOAST_DATUM(datum); - ctx->lwgeom = lwgeom_from_gserialized(gs); - - if (!coerce_geometry(ctx)) - return; + LWGEOM *lwgeom = lwgeom_from_gserialized(gs); layer->features[layer->n_features++] = feature; - encode_geometry(ctx); + encode_geometry(ctx, lwgeom); + lwgeom_free(lwgeom); + // TODO: free detoasted datum? parse_values(ctx); } diff --git a/postgis/mvt.h b/postgis/mvt.h index 60b08ca6f..750b7ddb9 100644 --- a/postgis/mvt.h +++ b/postgis/mvt.h @@ -37,6 +37,7 @@ #include "access/htup.h" #include "../postgis_config.h" #include "liblwgeom.h" +#include "liblwgeom_internal.h" #include "lwgeom_pg.h" #include "lwgeom_log.h" @@ -45,20 +46,14 @@ #include "vector_tile.pb-c.h" struct mvt_agg_context { - GBOX *bounds; char *name; uint32_t extent; - uint32_t buffer; - bool clip_geoms; char *geom_name; uint32_t geom_index; - LWGEOM *lwgeom; HeapTupleHeader row; VectorTile__Tile__Feature *feature; VectorTile__Tile__Layer *layer; size_t features_capacity; - double xres; - double yres; struct mvt_kv_string_value *string_values_hash; struct mvt_kv_float_value *float_values_hash; struct mvt_kv_double_value *double_values_hash; @@ -69,6 +64,7 @@ struct mvt_agg_context { uint32_t values_hash_i; } ; +LWGEOM *mvt_geom(LWGEOM *geom, GBOX *bounds, uint32_t extent, uint32_t buffer, bool clip_geom); void mvt_agg_init_context(struct mvt_agg_context *ctx); void mvt_agg_transfn(struct mvt_agg_context *ctx); uint8_t *mvt_agg_finalfn(struct mvt_agg_context *ctx); diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in index 20c1a7f6d..aa4839ba0 100644 --- a/postgis/postgis.sql.in +++ b/postgis/postgis.sql.in @@ -4387,7 +4387,7 @@ CREATE OR REPLACE FUNCTION ST_AsGeoJson(gj_version int4, geom geometry, maxdecim ----------------------------------------------------------------------- -- Availability: 2.4.0 -CREATE OR REPLACE FUNCTION pgis_asmvt_transfn(internal, text, box2d, int4, int4, bool, text, anyelement) +CREATE OR REPLACE FUNCTION pgis_asmvt_transfn(internal, text, int4, text, anyelement) RETURNS internal AS 'MODULE_PATHNAME', 'pgis_asmvt_transfn' LANGUAGE c IMMUTABLE; @@ -4399,13 +4399,19 @@ CREATE OR REPLACE FUNCTION pgis_asmvt_finalfn(internal) LANGUAGE c IMMUTABLE; -- Availability: 2.4.0 -CREATE AGGREGATE ST_AsMVT(text, box2d, int4, int4, bool, text, anyelement) +CREATE AGGREGATE ST_AsMVT(text, int4, text, anyelement) ( sfunc = pgis_asmvt_transfn, stype = internal, finalfunc = pgis_asmvt_finalfn ); +-- Availability: 2.4.0 +CREATE OR REPLACE FUNCTION ST_AsMVTGeom(geom geometry, bounds box2d, extent int4, buffer int4, clip_geom bool) + RETURNS geometry + AS 'MODULE_PATHNAME','ST_AsMVTGeom' + LANGUAGE 'c' IMMUTABLE _PARALLEL; + -- Availability: 2.4.0 CREATE OR REPLACE FUNCTION postgis_libprotobuf_version() RETURNS text diff --git a/regress/mvt.sql b/regress/mvt.sql index 8306d6e2a..ffa22c800 100644 --- a/regress/mvt.sql +++ b/regress/mvt.sql @@ -1,79 +1,80 @@ +-- geometry preprocessing tests +select 'PG1', ST_AsText(ST_AsMVTGeom( + ST_Point(1, 2), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), + 4096, 0, false)); +select 'PG2', ST_AsText(ST_AsMVTGeom( + ST_Point(1, 2), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096*2, 4096*2)), + 4096, 0, false)); +select 'PG3', ST_AsText(ST_AsMVTGeom( + ST_Point(1, 2), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096/2, 4096/2)), + 4096, 0, false)); +select 'PG4', ST_AsText(ST_AsMVTGeom( + ST_GeomFromText('POLYGON ((0 0, 10 0, 10 5, 0 -5, 0 0))'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), + 4096, 0, false)); +select 'PG5', ST_AsText(ST_AsMVTGeom( + ST_GeomFromText('POLYGON ((0 0, 10 0, 10 5, 0 -5, 0 0))'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(1, 1)), + 4096, 0, false)); + -- geometry encoding tests -SELECT 'TG1', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TG2', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('MULTIPOINT(25 17, 26 18)') AS geom) AS q; -SELECT 'TG3', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('LINESTRING(0 0, 1000 1000)') AS geom) AS q; -SELECT 'TG4', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('LINESTRING(0 0, 500 500, 1000 1000)') AS geom) AS q; -SELECT 'TG5', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('MULTILINESTRING((1 1, 501 501, 1001 1001),(2 2, 502 502, 1002 1002))') AS geom) AS q; -SELECT 'TG6', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))') AS geom) AS q; -SELECT 'TG7', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))') AS geom) AS q; -SELECT 'TG8', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, true, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TG9', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, true, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, - ST_GeomFromText('MULTIPOINT(25 17, -26 -18)') AS geom) AS q; +SELECT 'TG1', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG2', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('MULTIPOINT(25 17, 26 18)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG3', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('LINESTRING(0 0, 1000 1000)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG4', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('LINESTRING(0 0, 500 500, 1000 1000)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG5', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('MULTILINESTRING((1 1, 501 501, 1001 1001),(2 2, 502 502, 1002 1002))'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG6', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG7', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG8', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TG9', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, + ST_AsMVTGeom(ST_GeomFromText('MULTIPOINT(25 17, -26 -18)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; -- attribute encoding tests -SELECT 'TA1', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, 'abcd'::text AS c2, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TA2', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1.1::double precision AS c1, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TA3', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT NULL::integer AS c1, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TA4', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM ( - SELECT 1 AS c1, ST_GeomFromText('POINT(25 17)') AS geom +SELECT 'TA1', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, 'abcd'::text AS c2, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TA2', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1.1::double precision AS c1, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TA3', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT NULL::integer AS c1, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TA4', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM ( + SELECT 1 AS c1, ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom UNION - SELECT 2 AS c1, ST_GeomFromText('POINT(25 17)') AS geom) AS q; -SELECT 'TA5', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT ST_GeomFromText('POINT(25 17)') AS geom, 1 AS c1, 'abcd'::text AS c2) AS q; -SELECT 'TA6', encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT 1 AS c1, -1 AS c2, - ST_GeomFromText('POINT(25 17)') AS geom) AS q; + SELECT 2 AS c1, ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; +SELECT 'TA5', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom, 1 AS c1, 'abcd'::text AS c2) AS q; +SELECT 'TA6', encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') FROM (SELECT 1 AS c1, -1 AS c2, + ST_AsMVTGeom(ST_GeomFromText('POINT(25 17)'), + ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q; -- unsupported input --- NOTE: disabled test as it's dependant on PostgreSQL error text that cannot be expected to be stable ---SELECT 'TU1'; ---SELECT encode(ST_AsMVT('test', --- ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', NULL ---), 'base64'); SELECT 'TU2'; -SELECT encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', 1 -), 'base64'); +SELECT encode(ST_AsMVT('test', 4096, 'geom', 1), 'base64'); SELECT 'TU3'; -SELECT encode(ST_AsMVT('test', - ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q -), 'base64') FROM (SELECT NULL::integer AS c1, NULL AS geom) AS q; \ No newline at end of file +SELECT encode(ST_AsMVT('test', 4096, 'geom', q), 'base64') + FROM (SELECT NULL::integer AS c1, NULL AS geom) AS q; \ No newline at end of file diff --git a/regress/mvt_expected b/regress/mvt_expected index 1d71f526c..2239f8629 100644 --- a/regress/mvt_expected +++ b/regress/mvt_expected @@ -1,19 +1,25 @@ -TG1|Gh4KBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBeAI= -TG2|Gh4KBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBeAI= -TG3|GiMKBHRlc3QSERICAAAYAiIJCQCAQArQD88PGgJjMSICKAF4Ag== -TG4|GicKBHRlc3QSFRICAAAYAiINCQCAQBLoB+cH6AfnBxoCYzEiAigBeAI= -TG5|GjUKBHRlc3QSIxICAAAYAiIbCQL+PxLoB+cH6AfnBwnND84PEugH5wfoB+cHGgJjMSICKAF4Ag== -TG6|GjMKBHRlc3QSIRICAAAYAyIZCUbsPyIURTsKCSgyFA8JHScaHgkJHhMTDxoCYzEiAigBeAI= -TG7|Gj0KBHRlc3QSKxICAAAYAyIjCVCwPxonCTIeCRMJJwoqEwoAKCgKHh0xHQkUHhoTCgATFAoaAmMx -IgIoAXgC -TG8|Gh4KBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBeAI= -TG9|Gh4KBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBeAI= -TA1|GiwKBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgYKBGFiY2R4Ag== -TA2|GiUKBHRlc3QSDBICAAAYASIECTLePxoCYzEiCRmamZmZmZnxP3gC -TA3|GhYKBHRlc3QSCBgBIgQJMt4/GgJjMXgC -TA4|GjAKBHRlc3QSDBICAAAYASIECTLePxIMEgIAARgBIgQJMt4/GgJjMSICKAEiAigCeAI= -TA5|GiwKBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgYKBGFiY2R4Ag== -TA6|GigKBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgIwAXgC +PG1|POINT(1 4094) +PG2|POINT(2 4092) +PG3|POINT(0 4095) +PG4|MULTIPOLYGON(((5 4096,10 4096,10 4091,5 4096)),((0 4096,0 4101,5 4096,0 4096))) +PG5|POINT(0 4096) +TG1|GiEKBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBKIAgeAI= +TG2|GiMKBHRlc3QSDhICAAAYASIGETLePwIBGgJjMSICKAEogCB4Ag== +TG3|GiYKBHRlc3QSERICAAAYAiIJCQCAQArQD88PGgJjMSICKAEogCB4Ag== +TG4|GioKBHRlc3QSFRICAAAYAiINCQCAQBLoB+cH6AfnBxoCYzEiAigBKIAgeAI= +TG5|GjgKBHRlc3QSIxICAAAYAiIbCQL+PxLoB+cH6AfnBwnND84PEugH5wfoB+cHGgJjMSICKAEogCB4 +Ag== +TG6|GjIKBHRlc3QSHRICAAAYAyIVCUbsPxoxEwonPAkPCTEeEhQUCh0PGgJjMSICKAEogCB4Ag== +TG7|Gj0KBHRlc3QSKBICAAAYAyIgCVCwPxIKFDEdDwkAFCIyHh0eJwkAJw8JKBQSEwkAFA8aAmMxIgIo +ASiAIHgC +TG8|GiEKBHRlc3QSDBICAAAYASIECTLePxoCYzEiAigBKIAgeAI= +TG9|GiMKBHRlc3QSDhICAAAYASIGETLeP2VGGgJjMSICKAEogCB4Ag== +TA1|Gi8KBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgYKBGFiY2QogCB4Ag== +TA2|GigKBHRlc3QSDBICAAAYASIECTLePxoCYzEiCRmamZmZmZnxPyiAIHgC +TA3|GhkKBHRlc3QSCBgBIgQJMt4/GgJjMSiAIHgC +TA4|GjMKBHRlc3QSDBICAAAYASIECTLePxIMEgIAARgBIgQJMt4/GgJjMSICKAEiAigCKIAgeAI= +TA5|Gi8KBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgYKBGFiY2QogCB4Ag== +TA6|GisKBHRlc3QSDhIEAAABARgBIgQJMt4/GgJjMRoCYzIiAigBIgIwASiAIHgC TU2 ERROR: pgis_asmvt_transfn: parameter row cannot be other than a rowtype TU3