]> granicus.if.org Git - postgis/commitdiff
Reworked ST_AsMVT and new ST_AsMVTGeom implementation
authorBjörn Harrtell <bjorn@wololo.org>
Tue, 7 Mar 2017 20:32:18 +0000 (20:32 +0000)
committerBjörn Harrtell <bjorn@wololo.org>
Tue, 7 Mar 2017 20:32:18 +0000 (20:32 +0000)
References #3712

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

doc/reference_output.xml
postgis/lwgeom_out_mvt.c
postgis/mvt.c
postgis/mvt.h
postgis/postgis.sql.in
regress/mvt.sql
regress/mvt_expected

index 5795534115851ff25b6156252865c617bea03545..6eb31654a15e52fda534ab8e923d30541c90fc73 100644 (file)
@@ -1344,21 +1344,68 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5);
          </refsection>
        </refentry>
 
+       <refentry id="ST_AsMVTGeom">
+         <refnamediv>
+               <refname>ST_AsMVTGeom</refname>
+
+               <refpurpose>Transform a geometry into the coordinate space of a <ulink url="https://www.mapbox.com/vector-tiles/">Mapbox Vector Tile</ulink>.</refpurpose>
+         </refnamediv>
+         <refsynopsisdiv>
+               <funcsynopsis>
+                       <funcprototype>
+                               <funcdef>geometry <function>ST_AsMVTGeom</function></funcdef>
+                               <paramdef><type>geometry </type> <parameter>geom</parameter></paramdef>
+                               <paramdef><type>box2d </type> <parameter>bounds</parameter></paramdef>
+                               <paramdef><type>int4 </type> <parameter>extent</parameter></paramdef>
+                               <paramdef><type>int4 </type> <parameter>buffer</parameter></paramdef>
+                               <paramdef><type>bool </type> <parameter>clip_geom</parameter></paramdef>
+                       </funcprototype>
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+
+               <para>Transform a geometry into the coordinate space of a <ulink url="https://www.mapbox.com/vector-tiles/">Mapbox Vector Tile</ulink> 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.
+               </para>
+
+               <para><varname>geom</varname> is the geometry to transform.</para>
+               <para><varname>bounds</varname> is the geometric bounds of the tile contents without buffer.</para>
+               <para><varname>extent</varname> is the tile extent in tile coordinate space as defined by the <ulink url="https://www.mapbox.com/vector-tiles/specification/">specification</ulink>. If NULL it will default to 4096.</para>
+               <para><varname>buffer</varname> is the buffer distance in tile coordinate space to optionally clip geometries. If NULL it will default to 0.</para>
+               <para><varname>clip_geom</varname> is a boolean to control if geometries should be clipped or encoded as is. If NULL it will default to true.</para>
+
+               <para>Availability: 2.4.0</para>
+         </refsection>
+
+         <refsection>
+               <title>Examples</title>
+               <programlisting><![CDATA[SELECT 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));
+                              st_astext
+--------------------------------------------------------------------
+ MULTIPOLYGON(((5 4096,10 4096,10 4091,5 4096)),((5 4096,0 4096,0 4101,5 4096)))
+
+               ]]>
+               </programlisting>
+         </refsection>
+       </refentry>
+
        <refentry id="ST_AsMVT">
          <refnamediv>
                <refname>ST_AsMVT</refname>
 
-               <refpurpose>Return a Mapbox Vector Tile representation of a set of rows.</refpurpose>
+               <refpurpose>Return a <ulink url="https://www.mapbox.com/vector-tiles/">Mapbox Vector Tile</ulink> representation of a set of rows.</refpurpose>
          </refnamediv>
          <refsynopsisdiv>
                <funcsynopsis>
                        <funcprototype>
                                <funcdef>bytea <function>ST_AsMVT</function></funcdef>
                                <paramdef><type>text </type> <parameter>name</parameter></paramdef>
-                               <paramdef><type>box2d </type> <parameter>bounds</parameter></paramdef>
                                <paramdef><type>int4 </type> <parameter>extent</parameter></paramdef>
-                               <paramdef><type>int4 </type> <parameter>buffer</parameter></paramdef>
-                               <paramdef><type>bool </type> <parameter>clip_geom</parameter></paramdef>
                                <paramdef><type>text </type> <parameter>geom_name</parameter></paramdef>
                                <paramdef><type>anyelement </type> <parameter>row</parameter></paramdef>
                        </funcprototype>
@@ -1368,17 +1415,15 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5);
          <refsection>
                <title>Description</title>
 
-               <para>Return a Mapbox Vector Tile representation (<ulink url="https://www.mapbox.com/vector-tiles/specification/">https://www.mapbox.com/vector-tiles/specification/</ulink>) of a set of rows corresponding to a Layer.
+               <para>Return a <ulink url="https://www.mapbox.com/vector-tiles/">Mapbox Vector Tile</ulink> 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 <ulink url="https://www.mapbox.com/vector-tiles/specification/">specification</ulink>.
+               Typically ST_AsMVTGeom can be used to transform geometry into tile coordinate space.
+               Other row data will be encoded as attributes.
                </para>
 
                <para><varname>name</varname> is the name of the Layer</para>
-               <para><varname>bounds</varname> is the bounds of the tile in the coordinate system of the geometry field in the row data.</para>
                <para><varname>extent</varname> is the tile extent in screen space as defined by the specification. If NULL it will default to 4096.</para>
-               <para><varname>buffer</varname> is the buffer distance in screen space to optionally clip geometries. If NULL it will default to 0.</para>
-               <para><varname>clip_geom</varname> is a boolean to control if geometries should be clipped or encoded as is. If NULL it will default to true.</para>
                <para><varname>geom_name</varname> is the name of the geometry column in the row data.</para>
                <para><varname>row</varname> row data with at least a geometry column.</para>
 
@@ -1387,11 +1432,12 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5);
 
          <refsection>
                <title>Examples</title>
-               <programlisting><![CDATA[SELECT ST_AsMVT('test', ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false, 'geom', q)
-FROM (SELECT 1 AS c1, ST_GeomFromText('POINT(25 17)') AS geom) AS q;
-                              st_asmvt                              
+               <programlisting><![CDATA[SELECT ST_AsMVT('test', 4096, 'geom', q) 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;
+                              st_asmvt
 --------------------------------------------------------------------
- \x1a1e0a0474657374120c12020000180122040932de3f1a026331220220017802
+ \x1a330a0474657374122112020000180322190946ec3f2214453b0a092832140f091d271a1e09091e13130f1a026331220228017802
 
                ]]>
                </programlisting>
index 5d61359c0ea898f6fc3340b86e1ee847df1c1568..f97da9815dfbd3a25d01f73b363e679c81518bef 100644 (file)
 #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
 }
index 8c077f83be9a427810fbcf7aa3496846374c1119..ee19248bef78fb027445df24e5b378b5e5948b22 100644 (file)
@@ -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; i<poly->nrings; i++)
+                       ptarray_mirror_y(poly->rings[i], extent);
+               break;
+       case MULTIPOINTTYPE:
+       case MULTILINETYPE:
+       case MULTIPOLYGONTYPE:
+       case COLLECTIONTYPE:
+               col = (LWCOLLECTION *) in;
+               for (i=0; i<col->ngeoms; 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);
 }
 
index 60b08ca6f131cc27ba5ad078e32c5e903ddb1f7f..750b7ddb9a259847f99a1781cb8f8ab3dcfad728 100644 (file)
@@ -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"
 
 #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);
index 20c1a7f6d65569b4ef100141c503f55455cf2951..aa4839ba0b5eed88955d0f79215ea07653c605f0 100644 (file)
@@ -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
index 8306d6e2a31b20e2c87a33f453051a8f49b6c904..ffa22c800544a99afdfaa5f2e28495fc0abba994 100644 (file)
@@ -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
index 1d71f526c869a5015aab3a3d11aa7e62e8d0b718..2239f862956df28dfc0ba90fd87c8c731421121b 100644 (file)
@@ -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