]> granicus.if.org Git - postgis/commitdiff
Parallel implementation of ST_AsMVT (Closes #3927)
authorPaul Ramsey <pramsey@cleverelephant.ca>
Thu, 9 Nov 2017 17:21:45 +0000 (17:21 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Thu, 9 Nov 2017 17:21:45 +0000 (17:21 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@16105 b70326c6-7e19-0410-871a-916f4a2858ee

NEWS
postgis/lwgeom_out_mvt.c
postgis/mvt.c
postgis/mvt.h
postgis/postgis.sql.in

diff --git a/NEWS b/NEWS
index 4d7e68b173007a9eac768badd6f0167024aa0ec8..252ed325f71d728daa423bbbde6f879e531116b2 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,7 @@ PostGIS 2.5.0
            with core PostGIS functions.
 
 * Enhancements and Fixes*
+  - #3927, Parallel implementation of ST_AsMVT
   - #3925, Simplify geometry using map grid cell size before generating MVT
   - #3899, BTree sort order is now defined on collections of EMPTY and
            same-prefix geometries (Darafei Praliaskouski)
index 8dacd2d2dc700fbabd6d018dfbf4c47b1b5a26c5..be66559e570c08dad3cdbb20652a7ed89c82f702 100644 (file)
@@ -55,7 +55,7 @@ Datum ST_AsMVTGeom(PG_FUNCTION_ARGS)
        geom_in = PG_GETARG_GSERIALIZED_P_COPY(0);
        lwgeom_in = lwgeom_from_gserialized(geom_in);
        if (PG_ARGISNULL(1))
-               elog(ERROR, "ST_AsMVTGeom: parameter bounds cannot be null");
+               elog(ERROR, "%s: parameter bounds cannot be null", __func__);
        bounds = (GBOX *) PG_GETARG_POINTER(1);
        extent = PG_ARGISNULL(2) ? 4096 : PG_GETARG_INT32(2);
        buffer = PG_ARGISNULL(3) ? 256 : PG_GETARG_INT32(3);
@@ -83,10 +83,10 @@ Datum pgis_asmvt_transfn(PG_FUNCTION_ARGS)
        PG_RETURN_NULL();
 #else
        MemoryContext aggcontext;
-       struct mvt_agg_context *ctx;
+       mvt_agg_context *ctx;
 
        if (!AggCheckCallContext(fcinfo, &aggcontext))
-               elog(ERROR, "pgis_asmvt_transfn: called in non-aggregate context");
+               elog(ERROR, "%s called in non-aggregate context", __func__);
        MemoryContextSwitchTo(aggcontext);
 
        if (PG_ARGISNULL(0)) {
@@ -102,11 +102,11 @@ Datum pgis_asmvt_transfn(PG_FUNCTION_ARGS)
                        ctx->geom_name = text_to_cstring(PG_GETARG_TEXT_P(4));
                mvt_agg_init_context(ctx);
        } else {
-               ctx = (struct mvt_agg_context *) PG_GETARG_POINTER(0);
+               ctx = (mvt_agg_context *) PG_GETARG_POINTER(0);
        }
 
        if (!type_is_rowtype(get_fn_expr_argtype(fcinfo->flinfo, 1)))
-               elog(ERROR, "pgis_asmvt_transfn: parameter row cannot be other than a rowtype");
+               elog(ERROR, "%s: parameter row cannot be other than a rowtype", __func__);
        ctx->row = PG_GETARG_HEAPTUPLEHEADER(1);
 
        mvt_agg_transfn(ctx);
@@ -125,10 +125,11 @@ Datum pgis_asmvt_finalfn(PG_FUNCTION_ARGS)
        elog(ERROR, "Missing libprotobuf-c");
        PG_RETURN_NULL();
 #else
-       struct mvt_agg_context *ctx;
-       uint8_t *buf;
+       mvt_agg_context *ctx;
+       bytea *buf;
+       elog(DEBUG2, "%s called", __func__);
        if (!AggCheckCallContext(fcinfo, NULL))
-               elog(ERROR, "pgis_asmvt_finalfn called in non-aggregate context");
+               elog(ERROR, "%s called in non-aggregate context", __func__);
 
        if (PG_ARGISNULL(0))
        {
@@ -137,8 +138,76 @@ Datum pgis_asmvt_finalfn(PG_FUNCTION_ARGS)
                PG_RETURN_BYTEA_P(emptybuf);
        }
 
-       ctx = (struct mvt_agg_context *) PG_GETARG_POINTER(0);
+       ctx = (mvt_agg_context *) PG_GETARG_POINTER(0);
        buf = mvt_agg_finalfn(ctx);
        PG_RETURN_BYTEA_P(buf);
 #endif
 }
+
+PG_FUNCTION_INFO_V1(pgis_asmvt_serialfn);
+Datum pgis_asmvt_serialfn(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBPROTOBUF
+       elog(ERROR, "Missing libprotobuf-c");
+       PG_RETURN_NULL();
+#else
+       mvt_agg_context *ctx;
+       elog(DEBUG2, "%s called", __func__);
+       if (!AggCheckCallContext(fcinfo, NULL))
+               elog(ERROR, "%s called in non-aggregate context", __func__);
+
+       if (PG_ARGISNULL(0))
+       {
+               bytea *emptybuf = palloc(VARHDRSZ);
+               SET_VARSIZE(emptybuf, VARHDRSZ);
+               PG_RETURN_BYTEA_P(emptybuf);
+       }
+
+       ctx = (mvt_agg_context *) PG_GETARG_POINTER(0);
+       PG_RETURN_BYTEA_P(mvt_ctx_serialize(ctx));
+#endif
+}
+
+
+PG_FUNCTION_INFO_V1(pgis_asmvt_deserialfn);
+Datum pgis_asmvt_deserialfn(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBPROTOBUF
+       elog(ERROR, "Missing libprotobuf-c");
+       PG_RETURN_NULL();
+#else
+       MemoryContext aggcontext, oldcontext;
+       elog(DEBUG2, "%s called", __func__);
+       if (!AggCheckCallContext(fcinfo, &aggcontext))
+               elog(ERROR, "%s called in non-aggregate context", __func__);
+
+       oldcontext = MemoryContextSwitchTo(aggcontext);
+       mvt_agg_context *ctx = mvt_ctx_deserialize(PG_GETARG_BYTEA_P(0));
+       MemoryContextSwitchTo(oldcontext);
+
+       PG_RETURN_POINTER(ctx);
+#endif
+}
+
+PG_FUNCTION_INFO_V1(pgis_asmvt_combinefn);
+Datum pgis_asmvt_combinefn(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBPROTOBUF
+       elog(ERROR, "Missing libprotobuf-c");
+       PG_RETURN_NULL();
+#else
+       MemoryContext aggcontext, oldcontext;
+       elog(DEBUG2, "%s called", __func__);
+       if (!AggCheckCallContext(fcinfo, &aggcontext))
+               elog(ERROR, "%s called in non-aggregate context", __func__);
+
+       mvt_agg_context *ctx, *ctx1, *ctx2;
+       ctx1 = (mvt_agg_context*)PG_GETARG_POINTER(0);
+       ctx2 = (mvt_agg_context*)PG_GETARG_POINTER(1);
+       oldcontext = MemoryContextSwitchTo(aggcontext);
+       ctx = mvt_ctx_combine(ctx1, ctx2);
+       MemoryContextSwitchTo(oldcontext);
+       PG_RETURN_POINTER(ctx);
+#endif
+}
+
index dcf2d9d77eb505715ee7e9b5f14492f999f4f364..38f82dac311d2c461fedd05822ccc3dcedacd9ae 100644 (file)
@@ -103,7 +103,7 @@ static inline uint32_t p_int(int32_t value)
        return (value << 1) ^ (value >> 31);
 }
 
-static uint32_t encode_ptarray(struct mvt_agg_context *ctx, enum mvt_type type,
+static uint32_t encode_ptarray(mvt_agg_context *ctx, enum mvt_type type,
                               POINTARRAY *pa, uint32_t *buffer,
                               int32_t *px, int32_t *py)
 {
@@ -149,7 +149,7 @@ static uint32_t encode_ptarray(struct mvt_agg_context *ctx, enum mvt_type type,
        return offset;
 }
 
-static uint32_t encode_ptarray_initial(struct mvt_agg_context *ctx,
+static uint32_t encode_ptarray_initial(mvt_agg_context *ctx,
                                       enum mvt_type type,
                                       POINTARRAY *pa, uint32_t *buffer)
 {
@@ -157,7 +157,7 @@ static uint32_t encode_ptarray_initial(struct mvt_agg_context *ctx,
        return encode_ptarray(ctx, type, pa, buffer, &px, &py);
 }
 
-static void encode_point(struct mvt_agg_context *ctx, LWPOINT *point)
+static void encode_point(mvt_agg_context *ctx, LWPOINT *point)
 {
        VectorTile__Tile__Feature *feature = ctx->feature;
        feature->type = VECTOR_TILE__TILE__GEOM_TYPE__POINT;
@@ -167,7 +167,7 @@ static void encode_point(struct mvt_agg_context *ctx, LWPOINT *point)
        encode_ptarray_initial(ctx, MVT_POINT, point->point, feature->geometry);
 }
 
-static void encode_mpoint(struct mvt_agg_context *ctx, LWMPOINT *mpoint)
+static void encode_mpoint(mvt_agg_context *ctx, LWMPOINT *mpoint)
 {
        size_t c;
        VectorTile__Tile__Feature *feature = ctx->feature;
@@ -181,7 +181,7 @@ static void encode_mpoint(struct mvt_agg_context *ctx, LWMPOINT *mpoint)
                lwline->points, feature->geometry);
 }
 
-static void encode_line(struct mvt_agg_context *ctx, LWLINE *lwline)
+static void encode_line(mvt_agg_context *ctx, LWLINE *lwline)
 {
        size_t c;
        VectorTile__Tile__Feature *feature = ctx->feature;
@@ -193,7 +193,7 @@ static void encode_line(struct mvt_agg_context *ctx, LWLINE *lwline)
                lwline->points, feature->geometry);
 }
 
-static void encode_mline(struct mvt_agg_context *ctx, LWMLINE *lwmline)
+static void encode_mline(mvt_agg_context *ctx, LWMLINE *lwmline)
 {
        uint32_t i;
        int32_t px = 0, py = 0;
@@ -211,7 +211,7 @@ static void encode_mline(struct mvt_agg_context *ctx, LWMLINE *lwmline)
        feature->n_geometry = offset;
 }
 
-static void encode_poly(struct mvt_agg_context *ctx, LWPOLY *lwpoly)
+static void encode_poly(mvt_agg_context *ctx, LWPOLY *lwpoly)
 {
        uint32_t i;
        int32_t px = 0, py = 0;
@@ -229,7 +229,7 @@ static void encode_poly(struct mvt_agg_context *ctx, LWPOLY *lwpoly)
        feature->n_geometry = offset;
 }
 
-static void encode_mpoly(struct mvt_agg_context *ctx, LWMPOLY *lwmpoly)
+static void encode_mpoly(mvt_agg_context *ctx, LWMPOLY *lwmpoly)
 {
        uint32_t i, j;
        int32_t px = 0, py = 0;
@@ -250,7 +250,7 @@ static void encode_mpoly(struct mvt_agg_context *ctx, LWMPOLY *lwmpoly)
        feature->n_geometry = offset;
 }
 
-static void encode_geometry(struct mvt_agg_context *ctx, LWGEOM *lwgeom)
+static void encode_geometry(mvt_agg_context *ctx, LWGEOM *lwgeom)
 {
        int type = lwgeom->type;
 
@@ -272,7 +272,7 @@ static void encode_geometry(struct mvt_agg_context *ctx, LWGEOM *lwgeom)
        }
 }
 
-static TupleDesc get_tuple_desc(struct mvt_agg_context *ctx)
+static TupleDesc get_tuple_desc(mvt_agg_context *ctx)
 {
        Oid tupType = HeapTupleHeaderGetTypeId(ctx->row);
        int32 tupTypmod = HeapTupleHeaderGetTypMod(ctx->row);
@@ -280,7 +280,7 @@ static TupleDesc get_tuple_desc(struct mvt_agg_context *ctx)
        return tupdesc;
 }
 
-static uint32_t get_key_index(struct mvt_agg_context *ctx, char *name)
+static uint32_t get_key_index(mvt_agg_context *ctx, char *name)
 {
        struct mvt_kv_key *kv;
        size_t size = strlen(name);
@@ -290,7 +290,7 @@ static uint32_t get_key_index(struct mvt_agg_context *ctx, char *name)
        return kv->id;
 }
 
-static uint32_t add_key(struct mvt_agg_context *ctx, char *name)
+static uint32_t add_key(mvt_agg_context *ctx, char *name)
 {
        struct mvt_kv_key *kv;
        size_t size = strlen(name);
@@ -301,7 +301,7 @@ static uint32_t add_key(struct mvt_agg_context *ctx, char *name)
        return kv->id;
 }
 
-static void parse_column_keys(struct mvt_agg_context *ctx)
+static void parse_column_keys(mvt_agg_context *ctx)
 {
        TupleDesc tupdesc = get_tuple_desc(ctx);
        int natts = tupdesc->natts;
@@ -343,7 +343,7 @@ static void parse_column_keys(struct mvt_agg_context *ctx)
        ReleaseTupleDesc(tupdesc);
 }
 
-static void encode_keys(struct mvt_agg_context *ctx)
+static void encode_keys(mvt_agg_context *ctx)
 {
        struct mvt_kv_key *kv;
        size_t n_keys = ctx->keys_hash_i;
@@ -375,7 +375,7 @@ static VectorTile__Tile__Value *create_value()
        } \
 }
 
-static void encode_values(struct mvt_agg_context *ctx)
+static void encode_values(mvt_agg_context *ctx)
 {
        POSTGIS_DEBUG(2, "encode_values called");
        VectorTile__Tile__Value **values;
@@ -454,7 +454,7 @@ static void encode_values(struct mvt_agg_context *ctx)
        MVT_PARSE_INT_VALUE(value); \
 }
 
-static void add_value_as_string(struct mvt_agg_context *ctx,
+static void add_value_as_string(mvt_agg_context *ctx,
        char *value, uint32_t *tags, uint32_t k)
 {
        struct mvt_kv_string_value *kv;
@@ -475,7 +475,7 @@ static void add_value_as_string(struct mvt_agg_context *ctx,
        tags[ctx->c*2+1] = kv->id;
 }
 
-static void parse_datum_as_string(struct mvt_agg_context *ctx, Oid typoid,
+static void parse_datum_as_string(mvt_agg_context *ctx, Oid typoid,
        Datum datum, uint32_t *tags, uint32_t k)
 {
        Oid foutoid;
@@ -489,7 +489,7 @@ static void parse_datum_as_string(struct mvt_agg_context *ctx, Oid typoid,
 }
 
 #if POSTGIS_PGSQL_VERSION >= 94
-static uint32_t *parse_jsonb(struct mvt_agg_context *ctx, Jsonb *jb,
+static uint32_t *parse_jsonb(mvt_agg_context *ctx, Jsonb *jb,
        uint32_t *tags)
 {
        JsonbIterator *it;
@@ -553,7 +553,7 @@ static uint32_t *parse_jsonb(struct mvt_agg_context *ctx, Jsonb *jb,
 }
 #endif
 
-static void parse_values(struct mvt_agg_context *ctx)
+static void parse_values(mvt_agg_context *ctx)
 {
        POSTGIS_DEBUG(2, "parse_values called");
        uint32_t n_keys = ctx->keys_hash_i;
@@ -797,7 +797,7 @@ LWGEOM *mvt_geom(LWGEOM *lwgeom, const GBOX *gbox, uint32_t extent, uint32_t buf
 /**
  * Initialize aggregation context.
  */
-void mvt_agg_init_context(struct mvt_agg_context *ctx)
+void mvt_agg_init_context(mvt_agg_context *ctx)
 {
        VectorTile__Tile__Layer *layer;
 
@@ -806,6 +806,7 @@ void mvt_agg_init_context(struct mvt_agg_context *ctx)
        if (ctx->extent == 0)
                elog(ERROR, "mvt_agg_init_context: extent cannot be 0");
 
+       ctx->tile = NULL;
        ctx->features_capacity = FEATURES_CAPACITY_INITIAL;
        ctx->keys_hash = NULL;
        ctx->string_values_hash = NULL;
@@ -836,7 +837,7 @@ void mvt_agg_init_context(struct mvt_agg_context *ctx)
  * Allocates a new feature, increment feature counter and
  * encode geometry and properties into it.
  */
-void mvt_agg_transfn(struct mvt_agg_context *ctx)
+void mvt_agg_transfn(mvt_agg_context *ctx)
 {
        bool isnull = false;
        Datum datum;
@@ -885,45 +886,259 @@ void mvt_agg_transfn(struct mvt_agg_context *ctx)
        parse_values(ctx);
 }
 
-/**
- * Finalize aggregation.
- *
- * Encode keys and values and put the aggregated Layer message into
- * a Tile message and returns it packed as a bytea.
- */
-uint8_t *mvt_agg_finalfn(struct mvt_agg_context *ctx)
+static VectorTile__Tile * mvt_ctx_to_tile(mvt_agg_context *ctx)
+{
+       encode_keys(ctx);
+       encode_values(ctx);
+
+       int n_layers = 1;
+       VectorTile__Tile *tile = palloc(sizeof(VectorTile__Tile));
+       vector_tile__tile__init(tile);
+       tile->layers = palloc(sizeof(VectorTile__Tile__Layer*) * n_layers);
+       tile->layers[0] = ctx->layer;
+       tile->n_layers = n_layers;
+       return tile;
+}
+
+static bytea *mvt_ctx_to_bytea(mvt_agg_context *ctx)
 {
-       VectorTile__Tile__Layer *layers[1];
-       VectorTile__Tile tile = VECTOR_TILE__TILE__INIT;
-       size_t len;
-       uint8_t *buf;
+       /* Fill out the file slot, if it's not already filled. */
+       /* We should only have a filled slow when all the work of building */
+       /* out the data is complete, so after a serialize/deserialize cycle */
+       /* or after a context combine */
 
-       POSTGIS_DEBUG(2, "mvt_agg_finalfn called");
-       POSTGIS_DEBUGF(2, "mvt_agg_finalfn n_features == %zd", ctx->layer->n_features);
+       if (!ctx->tile)
+       {
+               ctx->tile = mvt_ctx_to_tile(ctx);
+       }
 
        /* Zero features => empty bytea output */
-       if (ctx->layer->n_features == 0)
+       if (ctx && ctx->layer && ctx->layer->n_features == 0)
        {
-               buf = palloc(VARHDRSZ);
-               SET_VARSIZE(buf, VARHDRSZ);
-               return buf;
+               bytea *ba = palloc(VARHDRSZ);
+               SET_VARSIZE(ba, VARHDRSZ);
+               return ba;
        }
 
-       encode_keys(ctx);
-       encode_values(ctx);
+       /* Serialize the Tile */
+       size_t len = VARHDRSZ + vector_tile__tile__get_packed_size(ctx->tile);
+       bytea *ba = palloc(len);
+       vector_tile__tile__pack(ctx->tile, (uint8_t*)VARDATA(ba));
+       SET_VARSIZE(ba, len);
+       return ba;
+}
+
 
-       layers[0] = ctx->layer;
+bytea * mvt_ctx_serialize(mvt_agg_context *ctx)
+{
+       return mvt_ctx_to_bytea(ctx);
+}
 
-       tile.n_layers = 1;
-       tile.layers = layers;
+static void * mvt_allocator(void *data, size_t size)
+{
+       return palloc(size);
+}
 
-       len = vector_tile__tile__get_packed_size(&tile);
-       buf = palloc(sizeof(*buf) * (len + VARHDRSZ));
-       vector_tile__tile__pack(&tile, buf + VARHDRSZ);
+static void mvt_deallocator(void *data, void *ptr)
+{
+       return pfree(ptr);
+}
 
-       SET_VARSIZE(buf, VARHDRSZ + len);
+mvt_agg_context * mvt_ctx_deserialize(const bytea *ba)
+{
+       ProtobufCAllocator allocator = {
+               mvt_allocator,
+               mvt_deallocator,
+               NULL
+       };
+
+       size_t len = VARSIZE(ba) - VARHDRSZ;
+       VectorTile__Tile *tile = vector_tile__tile__unpack(&allocator, len, (uint8_t*)VARDATA(ba));
+       mvt_agg_context *ctx = palloc(sizeof(mvt_agg_context));
+       memset(ctx, 0, sizeof(mvt_agg_context));
+       ctx->tile = tile;
+       return ctx;
+}
 
-       return buf;
+static VectorTile__Tile__Value *
+tile_value_copy(const VectorTile__Tile__Value *value)
+{
+       VectorTile__Tile__Value *nvalue = palloc(sizeof(VectorTile__Tile__Value));
+       memcpy(nvalue, value, sizeof(VectorTile__Tile__Value));
+       if (value->string_value)
+               nvalue->string_value = pstrdup(value->string_value);
+       return nvalue;
 }
 
+static VectorTile__Tile__Feature *
+tile_feature_copy(const VectorTile__Tile__Feature *feature, int key_offset, int value_offset)
+{
+       int i;
+
+       /* Null in => Null out */
+       if (!feature) return NULL;
+
+       /* Init object */
+       VectorTile__Tile__Feature *nfeature = palloc(sizeof(VectorTile__Tile__Feature));
+       vector_tile__tile__feature__init(nfeature);
+
+       /* Copy settings straight over */
+       nfeature->has_id = feature->has_id;
+       nfeature->id = feature->id;
+       nfeature->has_type = feature->has_type;
+       nfeature->type = feature->type;
+
+       /* Copy tags over, offsetting indexes so they match the dictionaries */
+       /* at the Tile_Layer level */
+       if (feature->n_tags > 0)
+       {
+               nfeature->n_tags = feature->n_tags;
+               nfeature->tags = palloc(sizeof(uint32_t)*feature->n_tags);
+               for (i = 0; i < feature->n_tags/2; i++)
+               {
+                       nfeature->tags[2*i] = feature->tags[2*i] + key_offset;
+                       nfeature->tags[2*i+1] = feature->tags[2*i+1] + value_offset;
+               }
+       }
+
+       /* Copy the raw geometry data over literally */
+       if (feature->n_geometry > 0)
+       {
+               nfeature->n_geometry = feature->n_geometry;
+               nfeature->geometry = palloc(sizeof(uint32_t)*feature->n_geometry);
+               memcpy(nfeature->geometry, feature->geometry, sizeof(uint32_t)*feature->n_geometry);
+       }
+
+       /* Done */
+       return nfeature;
+}
+
+static VectorTile__Tile__Layer *
+vectortile_layer_combine(const VectorTile__Tile__Layer *layer1, const VectorTile__Tile__Layer *layer2)
+{
+       int i, j;
+       int key2_offset, value2_offset;
+       VectorTile__Tile__Layer *layer = palloc(sizeof(VectorTile__Tile__Layer));
+       vector_tile__tile__layer__init(layer);
+
+       /* Take globals from layer1 */
+       layer->version = layer1->version;
+       layer->name = pstrdup(layer1->name);
+    layer->has_extent = layer1->has_extent;
+       layer->extent = layer1->extent;
+
+       /* Copy keys into new layer */
+       j = 0;
+       layer->n_keys = layer1->n_keys + layer2->n_keys;
+       layer->keys = layer->n_keys ? palloc(layer->n_keys * sizeof(void*)) : NULL;
+       for (i = 0; i < layer1->n_keys; i++)
+               layer->keys[j++] = pstrdup(layer1->keys[i]);
+       key2_offset = j;
+       for (i = 0; i < layer2->n_keys; i++)
+               layer->keys[j++] = pstrdup(layer2->keys[i]);
+
+       /* Copy values into new layer */
+       /* TODO, apply hash logic here too, so that merged tiles */
+       /* retain unique value maps */
+       layer->n_values = layer1->n_values + layer2->n_values;
+       layer->values = layer->n_values ? palloc(layer->n_values * sizeof(void*)) : NULL;
+       j = 0;
+       for (i = 0; i < layer1->n_values; i++)
+               layer->values[j++] = tile_value_copy(layer1->values[i]);
+       value2_offset = j;
+       for (i = 0; i < layer2->n_values; i++)
+               layer->values[j++] = tile_value_copy(layer2->values[i]);
+
+
+       layer->n_features = layer1->n_features + layer2->n_features;
+       layer->features = layer->n_features ? palloc(layer->n_features * sizeof(void*)) : NULL;
+       j = 0;
+       for (i = 0; i < layer1->n_features; i++)
+               layer->features[j++] = tile_feature_copy(layer1->features[i], 0, 0);
+       value2_offset = j;
+       for (i = 0; i < layer2->n_features; i++)
+               layer->features[j++] = tile_feature_copy(layer2->features[i], key2_offset, value2_offset);
+
+       return layer;
+}
+
+
+static VectorTile__Tile *
+vectortile_tile_combine(VectorTile__Tile *tile1, VectorTile__Tile *tile2)
+{
+       int i, j;
+
+       /* Hopelessly messing up memory ownership here */
+       if (tile1->n_layers == 0 && tile2->n_layers == 0)
+               return tile1;
+       else if (tile1->n_layers == 0)
+               return tile2;
+       else if (tile2->n_layers == 0)
+               return tile1;
+
+       VectorTile__Tile *tile = palloc(sizeof(VectorTile__Tile));
+       vector_tile__tile__init(tile);
+       tile->layers = palloc(sizeof(void*));
+       tile->n_layers = 0;
+
+       /* Merge all matching layers in the files (basically always only one) */
+       for (i = 0; i < tile1->n_layers; i++)
+       {
+               for (j = 0; j < tile2->n_layers; j++)
+               {
+                       VectorTile__Tile__Layer *l1 = tile1->layers[i];
+                       VectorTile__Tile__Layer *l2 = tile2->layers[j];
+                       if (strcmp(l1->name, l2->name)==0)
+                       {
+                               VectorTile__Tile__Layer *layer = vectortile_layer_combine(l1, l2);
+                               if (!layer)
+                                       continue;
+                               tile->layers[tile->n_layers++] = layer;
+                               /* Add a spare slot at the end of the array */
+                               tile->layers = repalloc(tile->layers, (tile->n_layers+1) * sizeof(void*));
+                       }
+               }
+       }
+       return tile;
+}
+
+mvt_agg_context * mvt_ctx_combine(mvt_agg_context *ctx1, mvt_agg_context *ctx2)
+{
+       if (ctx1 || ctx2)
+       {
+               if (ctx1 && ! ctx2) return ctx1;
+               if (ctx2 && ! ctx1) return ctx2;
+               if (ctx1 && ctx2 && ctx1->tile && ctx2->tile)
+               {
+                       mvt_agg_context *ctxnew = palloc(sizeof(mvt_agg_context));
+                       memset(ctxnew, 0, sizeof(mvt_agg_context));
+                       ctxnew->tile = vectortile_tile_combine(ctx1->tile, ctx2->tile);
+                       return ctxnew;
+               }
+               else
+               {
+                       elog(DEBUG2, "ctx1->tile = %p", ctx1->tile);
+                       elog(DEBUG2, "ctx2->tile = %p", ctx2->tile);
+                       elog(ERROR, "%s: unable to combine contexts where tile attribute is null", __func__);
+                       return NULL;
+               }
+       }
+       else
+       {
+               return NULL;
+       }
+}
+
+/**
+ * Finalize aggregation.
+ *
+ * Encode keys and values and put the aggregated Layer message into
+ * a Tile message and returns it packed as a bytea.
+ */
+bytea *mvt_agg_finalfn(mvt_agg_context *ctx)
+{
+       return mvt_ctx_to_bytea(ctx);
+}
+
+
 #endif
index 9f51054b127490c218ca74b67dafd091901a2f74..86c56a914b5102f89a21131a1e3eb29e0e39ee5e 100644 (file)
@@ -46,7 +46,7 @@
 
 #include "vector_tile.pb-c.h"
 
-struct mvt_agg_context {
+typedef struct mvt_agg_context {
        char *name;
        uint32_t extent;
        char *geom_name;
@@ -54,6 +54,7 @@ struct mvt_agg_context {
        HeapTupleHeader row;
        VectorTile__Tile__Feature *feature;
        VectorTile__Tile__Layer *layer;
+       VectorTile__Tile *tile;
        size_t features_capacity;
        struct mvt_kv_key *keys_hash;
        struct mvt_kv_string_value *string_values_hash;
@@ -65,13 +66,17 @@ struct mvt_agg_context {
        uint32_t values_hash_i;
        uint32_t keys_hash_i;
        uint32_t c;
-};
+} mvt_agg_context;
+
+/* Prototypes */
+LWGEOM *mvt_geom(LWGEOM *geom, const GBOX *bounds, uint32_t extent, uint32_t buffer, bool clip_geom);
+void mvt_agg_init_context(mvt_agg_context *ctx);
+void mvt_agg_transfn(mvt_agg_context *ctx);
+bytea *mvt_agg_finalfn(mvt_agg_context *ctx);
+bytea *mvt_ctx_serialize(mvt_agg_context *ctx);
+mvt_agg_context * mvt_ctx_deserialize(const bytea *ba);
+mvt_agg_context * mvt_ctx_combine(mvt_agg_context *ctx1, mvt_agg_context *ctx2);
 
-LWGEOM *mvt_geom(LWGEOM *geom, const 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);
 
 #endif  /* HAVE_LIBPROTOBUF */
 
index 847ef7f19ba47574f7820a40c8b3beb6beb31e91..c05cae31bc73841ef4490d223e2aad31f6e09b36 100644 (file)
@@ -4476,46 +4476,80 @@ CREATE OR REPLACE FUNCTION pgis_asmvt_finalfn(internal)
        AS 'MODULE_PATHNAME', 'pgis_asmvt_finalfn'
        LANGUAGE 'c' IMMUTABLE _PARALLEL;
 
+-- Availability: 2.5.0
+CREATE OR REPLACE FUNCTION pgis_asmvt_combinefn(internal, internal)
+       RETURNS internal
+       AS 'MODULE_PATHNAME', 'pgis_asmvt_combinefn'
+       LANGUAGE 'c' IMMUTABLE _PARALLEL;
+
+-- Availability: 2.5.0
+CREATE OR REPLACE FUNCTION pgis_asmvt_serialfn(internal)
+       RETURNS bytea
+       AS 'MODULE_PATHNAME', 'pgis_asmvt_serialfn'
+       LANGUAGE 'c' IMMUTABLE _PARALLEL;
+
+-- Availability: 2.5.0
+CREATE OR REPLACE FUNCTION pgis_asmvt_deserialfn(bytea, internal)
+       RETURNS internal
+       AS 'MODULE_PATHNAME', 'pgis_asmvt_deserialfn'
+       LANGUAGE 'c' IMMUTABLE _PARALLEL;
+
 -- Availability: 2.4.0
+-- Changed: 2.5.0
 CREATE AGGREGATE ST_AsMVT(anyelement)
 (
        sfunc = pgis_asmvt_transfn,
        stype = internal,
 #if POSTGIS_PGSQL_VERSION >= 96
        parallel = safe,
+       serialfunc = pgis_asmvt_serialfn,
+       deserialfunc = pgis_asmvt_deserialfn,
+       combinefunc = pgis_asmvt_combinefn,
 #endif
        finalfunc = pgis_asmvt_finalfn
 );
 
 -- Availability: 2.4.0
+-- Changed: 2.5.0
 CREATE AGGREGATE ST_AsMVT(anyelement, text)
 (
        sfunc = pgis_asmvt_transfn,
        stype = internal,
 #if POSTGIS_PGSQL_VERSION >= 96
        parallel = safe,
+       serialfunc = pgis_asmvt_serialfn,
+       deserialfunc = pgis_asmvt_deserialfn,
+       combinefunc = pgis_asmvt_combinefn,
 #endif
        finalfunc = pgis_asmvt_finalfn
 );
 
 -- Availability: 2.4.0
+-- Changed: 2.5.0
 CREATE AGGREGATE ST_AsMVT(anyelement, text, int4)
 (
        sfunc = pgis_asmvt_transfn,
        stype = internal,
 #if POSTGIS_PGSQL_VERSION >= 96
        parallel = safe,
+       serialfunc = pgis_asmvt_serialfn,
+       deserialfunc = pgis_asmvt_deserialfn,
+       combinefunc = pgis_asmvt_combinefn,
 #endif
        finalfunc = pgis_asmvt_finalfn
 );
 
 -- Availability: 2.4.0
+-- Changed: 2.5.0
 CREATE AGGREGATE ST_AsMVT(anyelement, text, int4, text)
 (
        sfunc = pgis_asmvt_transfn,
        stype = internal,
 #if POSTGIS_PGSQL_VERSION >= 96
        parallel = safe,
+       serialfunc = pgis_asmvt_serialfn,
+       deserialfunc = pgis_asmvt_deserialfn,
+       combinefunc = pgis_asmvt_combinefn,
 #endif
        finalfunc = pgis_asmvt_finalfn
 );