]> granicus.if.org Git - postgis/commitdiff
Geobuf output support via ST_AsGeobuf
authorBjörn Harrtell <bjorn@wololo.org>
Sun, 26 Feb 2017 18:16:54 +0000 (18:16 +0000)
committerBjörn Harrtell <bjorn@wololo.org>
Sun, 26 Feb 2017 18:16:54 +0000 (18:16 +0000)
Closes #3599

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

12 files changed:
NEWS
doc/reference_output.xml
postgis/Makefile.in
postgis/geobuf.LICENSE [new file with mode: 0644]
postgis/geobuf.c [new file with mode: 0644]
postgis/geobuf.h [new file with mode: 0644]
postgis/geobuf.proto [new file with mode: 0644]
postgis/lwgeom_out_geobuf.c [new file with mode: 0644]
postgis/postgis.sql.in
regress/Makefile.in
regress/geobuf.sql [new file with mode: 0644]
regress/geobuf_expected [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index a03bc2f59b338cbfe1caa170d11f85bb46c89d6c..56ef39370cebf0fc98531e3dbd81c9fee1663f1d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ PostGIS 2.4.0
 
  * New Features *
 
+  - #3599, Geobuf output support via ST_AsGeobuf (Björn Harrtell)
   - #3661, Mapbox vector tile output support via ST_AsMVT (Björn Harrtell / CartoDB)
 
 PostGIS 2.3.0
index e14944fd51898380ac57929003d801fd789dbcc9..5795534115851ff25b6156252865c617bea03545 100644 (file)
@@ -1300,6 +1300,50 @@ SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326),5);
          </refsection>
        </refentry>
 
+       <refentry id="ST_AsGeobuf">
+         <refnamediv>
+               <refname>ST_AsGeobuf</refname>
+
+               <refpurpose>Return a Geobuf representation of a set of rows.</refpurpose>
+         </refnamediv>
+         <refsynopsisdiv>
+               <funcsynopsis>
+                       <funcprototype>
+                               <funcdef>bytea <function>ST_AsGeobuf</function></funcdef>
+                               <paramdef><type>text </type> <parameter>geom_name</parameter></paramdef>
+                               <paramdef><type>anyelement </type> <parameter>row</parameter></paramdef>
+                       </funcprototype>
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+
+               <para>
+                       Return a Geobuf representation (<ulink url="https://github.com/mapbox/geobuf">https://github.com/mapbox/geobuf</ulink>) of a set of rows corresponding to a FeatureCollection.
+                       Every input geometry is analyzed to determine maximum precision for optimal storage.
+                       Note that Geobuf in its current form cannot be streamed so the full output will be assembled in memory.
+               </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>
+
+               <para>Availability: 2.4.0</para>
+         </refsection>
+
+         <refsection>
+               <title>Examples</title>
+               <programlisting><![CDATA[encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))') as geom) AS q;
+ st_asgeobuf
+----------------------------------
+ GAAiEAoOCgwIBBoIAAAAAgIAAAE=
+
+               ]]>
+               </programlisting>
+         </refsection>
+       </refentry>
+
        <refentry id="ST_AsMVT">
          <refnamediv>
                <refname>ST_AsMVT</refname>
index 299b2ac0676fbdf8b356ad3d9f601c8c18e00fd7..ee25399d276fdf4d1011e77be17149ffb008ea0e 100644 (file)
@@ -51,7 +51,7 @@ BRIN_OBJ= brin_2d.o brin_nd.o brin_common.o
 endif
 
 ifeq (@HAVE_PROTOBUF@,yes)
-PROTOBUF_OBJ=vector_tile.pb-c.o
+PROTOBUF_OBJ=vector_tile.pb-c.o geobuf.pb-c.o
 endif
 
 # SQL preprocessor
@@ -104,6 +104,8 @@ PG_OBJS= \
        $(PROTOBUF_OBJ) \
        mvt.o \
        lwgeom_out_mvt.o \
+       geobuf.o \
+       lwgeom_out_geobuf.o
 
 # Objects to build using PGXS
 OBJS=$(PG_OBJS)
@@ -135,7 +137,9 @@ EXTRA_CLEAN=$(SQL_OBJS) \
        sfcgal_upgrade.sql.in \
        sfcgal_upgrade.sql \
        vector_tile.pb-c.c \
-       vector_tile.pb-c.h
+       vector_tile.pb-c.h \
+       geobuf.pb-c.c \
+       geobuf.pb-c.h
 
 # PGXS information
 PG_CONFIG = @PG_CONFIG@
@@ -186,9 +190,15 @@ PROTOCC=@PROTOCC@
 vector_tile.pb-c.c vector_tile.pb-c.h: vector_tile.proto
        $(PROTOCC) --c_out=. $<
 
+# Generate Geobuf encoder/decoder using protobuf-c compiler 
+geobuf.pb-c.c geobuf.pb-c.h: geobuf.proto
+       $(PROTOCC) --c_out=. $<
+
 ifeq (@HAVE_PROTOBUF@,yes)
 lwgeom_out_mvt.o: vector_tile.pb-c.h
 mvt.o: vector_tile.pb-c.h
+lwgeom_out_geobuf.o: geobuf.pb-c.h
+geobuf.o: geobuf.pb-c.h
 endif
 
 # Borrow the $libdir substitution from PGXS but customise by running the preprocessor
diff --git a/postgis/geobuf.LICENSE b/postgis/geobuf.LICENSE
new file mode 100644 (file)
index 0000000..7d01fbc
--- /dev/null
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2015, Mapbox
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file
diff --git a/postgis/geobuf.c b/postgis/geobuf.c
new file mode 100644 (file)
index 0000000..b0c4b32
--- /dev/null
@@ -0,0 +1,600 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2016-2017 Björn Harrtell <bjorn@wololo.org>
+ *
+ **********************************************************************/
+
+#include <math.h>
+#include "geobuf.h"
+
+#ifdef HAVE_LIBPROTOBUF
+
+#define FEATURES_CAPACITY_INITIAL 50
+#define MAX_PRECISION 1e6
+
+static Data__Geometry *encode_geometry(struct geobuf_agg_context *ctx, LWGEOM *lwgeom);
+
+static Data__Geometry *galloc(Data__Geometry__Type type) {
+       Data__Geometry *geometry;
+       geometry = palloc (sizeof (Data__Geometry));
+       data__geometry__init(geometry);
+       geometry->type = type;
+       return geometry;
+}
+
+static TupleDesc get_tuple_desc(struct geobuf_agg_context *ctx)
+{
+       Oid tupType = HeapTupleHeaderGetTypeId(ctx->row);
+       int32 tupTypmod = HeapTupleHeaderGetTypMod(ctx->row);
+       TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+       return tupdesc;
+}
+
+static void encode_keys(struct geobuf_agg_context *ctx)
+{
+       TupleDesc tupdesc = get_tuple_desc(ctx);
+       int natts = tupdesc->natts;
+       char **keys = palloc(natts * sizeof(*keys));
+       uint32_t i, k = 0;
+       bool geom_name_found = false;
+       for (i = 0; i < natts; i++) {
+               char *key = tupdesc->attrs[i]->attname.data;
+               if (strcmp(key, ctx->geom_name) == 0) {
+                       ctx->geom_index = i;
+                       geom_name_found = true;
+                       continue;
+               }
+               keys[k++] = key;
+       }
+       if (!geom_name_found)
+               lwerror("encode_keys: no column with specificed geom_name found");
+       ctx->data->n_keys = k;
+       ctx->data->keys = keys;
+       ReleaseTupleDesc(tupdesc);
+}
+
+
+static void set_int_value(Data__Value *value, int64 intval) {
+       if (intval >= 0) {
+               value->value_type_case = DATA__VALUE__VALUE_TYPE_POS_INT_VALUE;
+               value->pos_int_value = intval;
+       } else {
+               value->value_type_case = DATA__VALUE__VALUE_TYPE_NEG_INT_VALUE;
+               value->neg_int_value = abs(intval);
+       }
+}
+
+static void encode_properties(struct geobuf_agg_context *ctx, Data__Feature *feature) {
+       uint32_t *properties;
+       Data__Value **values;
+       uint32_t i, k = 0, c = 0;
+       TupleDesc tupdesc = get_tuple_desc(ctx);
+       int natts = tupdesc->natts;
+       properties = palloc(sizeof (*properties) * (natts - 1) * 2);
+       values = palloc (sizeof (*values) * (natts - 1));
+
+       for (i = 0; i < natts; i++) {
+               Data__Value *value;
+               char *type, *string_value, *key;
+               Datum datum;
+               bool isnull;
+
+               if (i == ctx->geom_index)
+                       continue;
+               k++;
+
+               key = tupdesc->attrs[i]->attname.data;
+
+               value = palloc (sizeof (*value));
+               data__value__init(value);
+
+               type = SPI_gettype(tupdesc, i + 1);
+               datum = GetAttributeByNum(ctx->row, i + 1, &isnull);
+               if (isnull)
+                       continue;
+               Oid typoid = getBaseType(tupdesc->attrs[i]->atttypid);
+               if (strcmp(type, "int2") == 0) {
+                       set_int_value(value, DatumGetInt16(datum));
+               } else if (strcmp(type, "int4") == 0) {
+                       set_int_value(value, DatumGetInt32(datum));
+               } else if (strcmp(type, "int8") == 0) {
+                       set_int_value(value, DatumGetInt64(datum));
+               } else if (strcmp(type, "float4") == 0) {
+                       value->value_type_case = DATA__VALUE__VALUE_TYPE_DOUBLE_VALUE;
+                       value->double_value = DatumGetFloat4(datum);
+               } else if (strcmp(type, "float8") == 0) {
+                       value->value_type_case = DATA__VALUE__VALUE_TYPE_DOUBLE_VALUE;
+                       value->double_value = DatumGetFloat8(datum);
+               } else {
+                       Oid foutoid;
+                       bool typisvarlena;
+                       getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
+                       string_value = OidOutputFunctionCall(foutoid, datum);
+                       value->value_type_case = DATA__VALUE__VALUE_TYPE_STRING_VALUE;
+                       value->string_value = string_value;
+               }
+               properties[c * 2] = k - 1;
+               properties[c * 2 + 1] = c;
+               values[c++] = value;
+       }
+
+       ReleaseTupleDesc(tupdesc);
+
+       feature->n_values = c;
+       feature->values = values;
+       feature->n_properties = c * 2;
+       feature->properties = properties;
+}
+
+static int64_t *encode_coords(struct geobuf_agg_context *ctx, POINTARRAY *pa, int64_t *coords, int len, int offset) {
+       int i, c;
+       POINT4D pt;
+       int64_t sum[] = { 0, 0, 0, 0 };
+
+       if (offset == 0)
+               coords = palloc(sizeof (int64_t) * len * ctx->dimensions);
+       else
+               coords = repalloc(coords, sizeof (int64_t) * ((len * ctx->dimensions) + offset));
+
+       c = offset;
+       for (i = 0; i < len; i++) {
+               getPoint4d_p(pa, i, &pt);
+               sum[0] += coords[c++] = ceil(pt.x * ctx->e) - sum[0];
+               sum[1] += coords[c++] = ceil(pt.y * ctx->e) - sum[1];
+               if (ctx->dimensions == 3)
+                       sum[2] += coords[c++] = ceil(pt.z * ctx->e) - sum[2];
+               else if (ctx->dimensions == 4)
+                       sum[3] += coords[c++] = ceil(pt.m * ctx->e) - sum[3];
+       }
+       return coords;
+}
+
+static Data__Geometry *encode_point(struct geobuf_agg_context *ctx, LWPOINT *lwpoint) {
+       int npoints;
+       Data__Geometry *geometry;
+       POINTARRAY *pa;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__POINT);
+
+       pa = lwpoint->point;
+       npoints = pa->npoints;
+
+       if (npoints == 0)
+               return geometry;
+
+       geometry->n_coords = npoints * ctx->dimensions;
+       geometry->coords = encode_coords(ctx, pa, NULL, 1, 0);
+
+       return geometry;
+}
+
+static Data__Geometry *encode_mpoint(struct geobuf_agg_context *ctx, LWMPOINT *lwmpoint) {
+       int i, ngeoms;
+       POINTARRAY *pa;
+       Data__Geometry *geometry;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__MULTIPOINT);
+
+       ngeoms = lwmpoint->ngeoms;
+
+       if (ngeoms == 0)
+               return geometry;
+
+       pa = ptarray_construct_empty(0, 0, ngeoms);
+
+       for (i = 0; i < ngeoms; i++) {
+               POINT4D pt;
+               getPoint4d_p(lwmpoint->geoms[i]->point, 0, &pt);
+               ptarray_append_point(pa, &pt, 0);
+       }
+
+       geometry->n_coords = ngeoms * ctx->dimensions;
+       geometry->coords = encode_coords(ctx, pa, NULL, ngeoms, 0);
+
+       return geometry;
+}
+
+static Data__Geometry *encode_line(struct geobuf_agg_context *ctx, LWLINE *lwline) {
+       POINTARRAY *pa;
+       Data__Geometry *geometry;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__LINESTRING);
+
+       pa = lwline->points;
+
+       if (pa->npoints == 0)
+               return geometry;
+
+       geometry->n_coords = pa->npoints * ctx->dimensions;
+       geometry->coords = encode_coords(ctx, pa, NULL, pa->npoints, 0);
+
+       return geometry;
+}
+
+static Data__Geometry *encode_mline(struct geobuf_agg_context *ctx, LWMLINE *lwmline) {
+       int i, offset, ngeoms;
+       POINTARRAY *pa;
+       Data__Geometry *geometry;
+       uint32_t *lengths;
+       int64_t *coords = NULL;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__MULTILINESTRING);
+
+       ngeoms = lwmline->ngeoms;
+
+       if (ngeoms == 0)
+               return geometry;
+
+       lengths = palloc (sizeof (uint32_t) * ngeoms);
+
+       offset = 0;
+       for (i = 0; i < ngeoms; i++) {
+               pa = lwmline->geoms[i]->points;
+               coords = encode_coords(ctx, pa, coords, pa->npoints, offset);
+               offset += pa->npoints * ctx->dimensions;
+               lengths[i] = pa->npoints;
+       }
+
+       if (ngeoms > 1) {
+               geometry->n_lengths = ngeoms;
+               geometry->lengths = lengths;
+       }
+
+       geometry->n_coords = offset;
+       geometry->coords = coords;
+
+       return geometry;
+}
+
+static Data__Geometry *encode_poly(struct geobuf_agg_context *ctx, LWPOLY *lwpoly) {
+       int i, len, nrings, offset;
+       POINTARRAY *pa;
+       Data__Geometry *geometry;
+       uint32_t *lengths;
+       int64_t *coords = NULL;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__POLYGON);
+
+       nrings = lwpoly->nrings;
+
+       if (nrings == 0)
+               return geometry;
+
+       lengths = palloc (sizeof (uint32_t) * nrings);
+
+       offset = 0;
+       for (i = 0; i < nrings; i++) {
+               pa = lwpoly->rings[i];
+               len = pa->npoints - 1;
+               coords = encode_coords(ctx, pa, coords, len, offset);
+               offset += len * ctx->dimensions;
+               lengths[i] = len;
+       }
+
+       if (nrings > 1) {
+               geometry->n_lengths = nrings;
+               geometry->lengths = lengths;
+       }
+
+       geometry->n_coords = offset;
+       geometry->coords = coords;
+
+       return geometry;
+}
+
+static Data__Geometry *encode_mpoly(struct geobuf_agg_context *ctx, LWMPOLY* lwmpoly) {
+       int i, j, c, len, offset, n_lengths, ngeoms, nrings;
+       POINTARRAY *pa;
+       Data__Geometry *geometry;
+       uint32_t *lengths;
+       int64_t *coords = NULL;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__MULTIPOLYGON);
+
+       ngeoms = lwmpoly->ngeoms;
+
+       if (ngeoms == 0) return geometry;
+
+       n_lengths = 1;
+       for (i = 0; i < ngeoms; i++) {
+               nrings = lwmpoly->geoms[i]->nrings;
+               n_lengths++;
+               for (j = 0; j < nrings; j++)
+                       n_lengths++;
+       }
+
+       lengths = palloc (sizeof (uint32_t) * n_lengths);
+
+       c = 0;
+       offset = 0;
+       lengths[c++] = ngeoms;
+       for (i = 0; i < ngeoms; i++) {
+               nrings = lwmpoly->geoms[i]->nrings;
+               lengths[c++] = nrings;
+               for (j = 0; j < nrings; j++) {
+                       pa = lwmpoly->geoms[i]->rings[j];
+                       len = pa->npoints - 1;
+                       coords = encode_coords(ctx, pa, coords, len, offset);
+                       offset += len * ctx->dimensions;
+                       lengths[c++] = len;
+               }
+       }
+
+       if (c > 1) {
+               geometry->n_lengths = n_lengths;
+               geometry->lengths = lengths;
+       }
+
+       geometry->n_coords = offset;
+       geometry->coords = coords;
+
+       return geometry;
+}
+
+static Data__Geometry *encode_collection(struct geobuf_agg_context *ctx, LWCOLLECTION* lwcollection) {
+       int i, ngeoms;
+       Data__Geometry *geometry, **geometries;
+
+       geometry = galloc(DATA__GEOMETRY__TYPE__GEOMETRYCOLLECTION);
+
+       ngeoms = lwcollection->ngeoms;
+
+       if (ngeoms == 0)
+               return geometry;
+
+       geometries = palloc (sizeof (Data__Geometry *) * ngeoms);
+       for (i = 0; i < ngeoms; i++) {
+               LWGEOM *lwgeom = lwcollection->geoms[i];
+               Data__Geometry *geom = encode_geometry(ctx, lwgeom);
+               geometries[i] = geom;
+       }
+
+       geometry->n_geometries = ngeoms;
+       geometry->geometries = geometries;
+
+       return geometry;
+}
+
+static Data__Geometry *encode_geometry(struct geobuf_agg_context *ctx, LWGEOM *lwgeom) {
+       int type = lwgeom->type;
+       switch (type)
+       {
+       case POINTTYPE:
+               return encode_point(ctx, (LWPOINT*)lwgeom);
+       case LINETYPE:
+               return encode_line(ctx, (LWLINE*)lwgeom);
+       case POLYGONTYPE:
+               return encode_poly(ctx, (LWPOLY*)lwgeom);
+       case MULTIPOINTTYPE:
+               return encode_mpoint(ctx, (LWMPOINT*)lwgeom);
+       case MULTILINETYPE:
+               return encode_mline(ctx, (LWMLINE*)lwgeom);
+       case MULTIPOLYGONTYPE:
+               return encode_mpoly(ctx, (LWMPOLY*)lwgeom);
+       case COLLECTIONTYPE:
+               return encode_collection(ctx, (LWCOLLECTION*)lwgeom);
+       default:
+               lwerror("encode_geometry: '%s' geometry type not supported",
+                               lwtype_name(type));
+       }
+       return NULL;
+}
+
+static void analyze_val(struct geobuf_agg_context *ctx, double val) {
+       if (ceil(val * ctx->e) / ctx->e != val && ctx->e < MAX_PRECISION)
+               ctx->e *= 10;
+}
+
+static void analyze_pa(struct geobuf_agg_context *ctx, POINTARRAY *pa) {
+       int i;
+       POINT4D pt;
+       for (i = 0; i < pa->npoints; i++) {
+               getPoint4d_p(pa, i, &pt);
+               analyze_val(ctx, pt.x);
+               analyze_val(ctx, pt.y);
+               if (ctx->dimensions == 3)
+                       analyze_val(ctx, pt.z);
+               else if (ctx->dimensions == 4)
+                       analyze_val(ctx, pt.m);
+       }
+}
+
+static void analyze_geometry(struct geobuf_agg_context *ctx, LWGEOM *lwgeom) {
+       int i, type;
+       LWLINE *lwline;
+       LWPOLY *lwpoly;
+       LWCOLLECTION *lwcollection;
+       type = lwgeom->type;
+       switch (type)
+       {
+       case POINTTYPE:
+       case LINETYPE:
+               lwline = (LWLINE*) lwgeom;
+               analyze_pa(ctx, lwline->points);
+               break;
+       case POLYGONTYPE:
+               lwpoly = (LWPOLY*) lwgeom;
+               for (i = 0; i < lwpoly->nrings; i++)
+                       analyze_pa(ctx, lwpoly->rings[i]);
+               break;
+       case MULTIPOINTTYPE:
+       case MULTILINETYPE:
+       case MULTIPOLYGONTYPE:
+       case COLLECTIONTYPE:
+               lwcollection = (LWCOLLECTION*) lwgeom;
+               for (i = 0; i < lwcollection->ngeoms; i++)
+                       analyze_geometry(ctx, lwcollection->geoms[i]);
+               break;
+       default:
+               lwerror("analyze_geometry: '%s' geometry type not supported",
+                       lwtype_name(type));
+       }
+}
+
+static void analyze_geometry_flags(struct geobuf_agg_context *ctx, LWGEOM *lwgeom) {
+       if (!ctx->has_dimensions) {
+               if (FLAGS_GET_Z(lwgeom->flags) || FLAGS_GET_M(lwgeom->flags))
+                       ctx->dimensions = 3;
+               else if (FLAGS_GET_ZM(lwgeom->flags))
+                       ctx->dimensions = 4;
+               else
+                       ctx->dimensions = 2;
+               ctx->has_dimensions = 1;
+       }
+}
+
+/**
+ *
+ */
+static Data__Feature *encode_feature(struct geobuf_agg_context *ctx) {
+       Data__Feature *feature;
+
+       feature = palloc (sizeof (Data__Feature));
+       data__feature__init(feature);
+
+       encode_properties(ctx, feature);
+       return feature;
+}
+
+/**
+ * Initialize aggregation context.
+ */
+void geobuf_agg_init_context(struct geobuf_agg_context *ctx)
+{
+       Data *data;
+       Data__FeatureCollection *fc;
+
+       ctx->has_dimensions = 0;
+       ctx->dimensions = 2;
+       ctx->has_precision = 0;
+       ctx->precision = MAX_PRECISION;
+       ctx->e = 1;
+       ctx->features_capacity = FEATURES_CAPACITY_INITIAL;
+
+       data = palloc(sizeof(*data));
+       data__init(data);
+
+       fc = palloc(sizeof(*fc));
+       data__feature_collection__init(fc);
+
+       fc->features = palloc (ctx->features_capacity *
+               sizeof(*fc->features));
+
+       ctx->lwgeoms = palloc (ctx->features_capacity *
+               sizeof(*ctx->lwgeoms));
+
+       data->data_type_case = DATA__DATA_TYPE_FEATURE_COLLECTION;
+       data->feature_collection = fc;
+
+       ctx->data = data;
+}
+
+/**
+ * Aggregation step.
+ *
+ * Expands features array if needed by a factor of 2.
+ * Allocates a new feature, increment feature counter and
+ * encode properties into it.
+ */
+void geobuf_agg_transfn(struct geobuf_agg_context *ctx)
+{
+       LWGEOM *lwgeom;
+       bool isnull;
+       Datum datum;
+       Data__FeatureCollection *fc = ctx->data->feature_collection;
+       Data__Feature **features = fc->features;
+       Data__Feature *feature;
+       GSERIALIZED *gs;
+       if (fc->n_features >= ctx->features_capacity) {
+               size_t new_capacity = ctx->features_capacity * 2;
+               fc->features = repalloc(fc->features, new_capacity *
+                       sizeof(*fc->features));
+               ctx->lwgeoms = repalloc(ctx->lwgeoms, new_capacity *
+                       sizeof(*ctx->lwgeoms));
+               ctx->features_capacity = new_capacity;
+       }
+
+       /* inspect row and encode keys assuming static schema */
+       if (fc->n_features == 0)
+               encode_keys(ctx);
+
+       datum = GetAttributeByNum(ctx->row, ctx->geom_index + 1, &isnull);
+       if (!datum)
+               lwerror("geobuf_agg_transfn: geometry column cannot be null");
+       gs = (GSERIALIZED *) PG_DETOAST_DATUM(datum);
+       lwgeom = lwgeom_from_gserialized(gs);
+
+       feature = encode_feature(ctx);
+
+       /* inspect geometry flags assuming static schema */
+       if (fc->n_features == 0)
+               analyze_geometry_flags(ctx, lwgeom);
+
+       analyze_geometry(ctx, lwgeom);
+
+       ctx->lwgeoms[fc->n_features] = lwgeom;
+       fc->features[fc->n_features++] = feature;
+}
+
+/**
+ * Finalize aggregation.
+ *
+ * Encode into Data message and return it packed as a bytea.
+ */
+uint8_t *geobuf_agg_finalfn(struct geobuf_agg_context *ctx)
+{
+       int i;
+       Data *data;
+       Data__FeatureCollection *fc;
+
+       data = ctx->data;
+       fc = data->feature_collection;
+
+       /* check and set dimensions if not default */
+       if (ctx->dimensions != 2) {
+               data->has_dimensions = ctx->has_dimensions;
+               data->dimensions = ctx->dimensions;
+       }
+       lwdebug(3, "data->dimensions: %d", data->dimensions);
+
+       /* check and set precision if not default */
+       if (ctx->e > MAX_PRECISION)
+               ctx->e = MAX_PRECISION;
+       ctx->precision = ceil(log(ctx->e) / log(10));
+       lwdebug(3, "ctx->precision: %d", ctx->precision);
+       if (ctx->precision != 6) {
+               data->has_precision = 1;
+               data->precision = ctx->precision;
+       }
+
+       for (i = 0; i < fc->n_features; i++)
+               fc->features[i]->geometry = encode_geometry(ctx, ctx->lwgeoms[i]);
+
+       size_t len = data__get_packed_size(data);
+       uint8_t *buf = palloc(sizeof(*buf) * (len + VARHDRSZ));
+       data__pack(data, buf + VARHDRSZ);
+
+       SET_VARSIZE(buf, VARHDRSZ + len);
+
+       return buf;
+}
+
+#endif
diff --git a/postgis/geobuf.h b/postgis/geobuf.h
new file mode 100644 (file)
index 0000000..e7b50b3
--- /dev/null
@@ -0,0 +1,69 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2016-2017 Björn Harrtell <bjorn@wololo.org>
+ *
+ **********************************************************************/
+
+#ifndef GEOBUF_H_
+#define GEOBUF_H_ 1
+
+#include <stdlib.h>
+#include "postgres.h"
+#include "utils/builtins.h"
+#include "utils/array.h"
+#include "utils/typcache.h"
+#include "utils/lsyscache.h"
+#include "catalog/pg_type.h" 
+#include "executor/spi.h"
+#include "executor/executor.h"
+#include "access/htup_details.h"
+#include "access/htup.h"
+#include "../postgis_config.h"
+#include "liblwgeom.h"
+#include "lwgeom_pg.h"
+#include "lwgeom_log.h"
+
+#ifdef HAVE_LIBPROTOBUF
+
+#include "geobuf.pb-c.h"
+
+struct geobuf_agg_context {
+       char *geom_name;
+       uint32_t geom_index;
+       HeapTupleHeader row;
+       LWGEOM **lwgeoms;
+        Data *data;
+        Data__Feature *feature;
+       size_t features_capacity;
+        uint32_t e;
+        protobuf_c_boolean has_precision;
+        uint32_t precision;
+        protobuf_c_boolean has_dimensions;
+        uint32_t dimensions;
+};
+
+void geobuf_agg_init_context(struct geobuf_agg_context *ctx);
+void geobuf_agg_transfn(struct geobuf_agg_context *ctx);
+uint8_t *geobuf_agg_finalfn(struct geobuf_agg_context *ctx);
+
+#endif  /* HAVE_LIBPROTOBUF */
+
+#endif
diff --git a/postgis/geobuf.proto b/postgis/geobuf.proto
new file mode 100644 (file)
index 0000000..26b742b
--- /dev/null
@@ -0,0 +1,66 @@
+option optimize_for = LITE_RUNTIME;
+
+message Data {
+    repeated string keys = 1; // global arrays of unique keys
+
+    optional uint32 dimensions = 2 [default = 2]; // max coordinate dimensions
+    optional uint32 precision = 3 [default = 6]; // number of digits after decimal point for coordinates
+
+    oneof data_type {
+        FeatureCollection feature_collection = 4;
+        Feature feature = 5;
+        Geometry geometry = 6;
+    }
+
+    message Feature {
+        required Geometry geometry = 1;
+
+        oneof id_type {
+            string id = 11;
+            sint64 int_id = 12;
+        }
+
+        repeated Value values = 13; // unique values
+        repeated uint32 properties = 14 [packed = true]; // pairs of key/value indexes
+        repeated uint32 custom_properties = 15 [packed = true]; // arbitrary properties
+    }
+
+    message Geometry {
+        required Type type = 1;
+
+        repeated uint32 lengths = 2 [packed = true]; // coordinate structure in lengths
+        repeated sint64 coords = 3 [packed = true]; // delta-encoded integer values
+        repeated Geometry geometries = 4;
+
+        repeated Value values = 13;
+        repeated uint32 custom_properties = 15 [packed = true];
+
+        enum Type {
+            POINT = 0;
+            MULTIPOINT = 1;
+            LINESTRING = 2;
+            MULTILINESTRING = 3;
+            POLYGON = 4;
+            MULTIPOLYGON = 5;
+            GEOMETRYCOLLECTION = 6;
+        }
+    }
+
+    message FeatureCollection {
+        repeated Feature features = 1;
+
+        repeated Value values = 13;
+        repeated uint32 custom_properties = 15 [packed = true];
+    }
+
+    message Value {
+        oneof value_type {
+            string string_value = 1;
+            double double_value = 2;
+            uint64 pos_int_value = 3;
+            uint64 neg_int_value = 4;
+            bool bool_value = 5;
+            string json_value = 6;
+        }
+    }
+}
diff --git a/postgis/lwgeom_out_geobuf.c b/postgis/lwgeom_out_geobuf.c
new file mode 100644 (file)
index 0000000..30aaf5e
--- /dev/null
@@ -0,0 +1,99 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2016-2017 Björn Harrtell <bjorn@wololo.org>
+ *
+ **********************************************************************/
+
+#include "geobuf.h"
+
+/**
+ * @file
+ * Geobuf export functions
+ */
+
+#include "postgres.h"
+#include "utils/builtins.h"
+#include "executor/spi.h"
+
+#include "../postgis_config.h"
+#include "lwgeom_pg.h"
+#include "lwgeom_log.h"
+#include "liblwgeom.h"
+#include "geobuf.h"
+
+/**
+ * Process input parameters and row data into state
+ */
+PG_FUNCTION_INFO_V1(pgis_asgeobuf_transfn);
+Datum pgis_asgeobuf_transfn(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBPROTOBUF
+       lwerror("Missing libprotobuf-c");
+       PG_RETURN_NULL();
+#else
+       MemoryContext aggcontext;
+       struct geobuf_agg_context *ctx;
+
+       if (!AggCheckCallContext(fcinfo, &aggcontext))
+               lwerror("pgis_asmvt_transfn: called in non-aggregate context");
+       MemoryContextSwitchTo(aggcontext);
+
+       if (PG_ARGISNULL(0)) {
+               ctx = palloc(sizeof(*ctx));
+               if (PG_ARGISNULL(1))
+                       lwerror("pgis_asgeobuf_transfn: parameter geom_name cannot be null");
+               ctx->geom_name = text_to_cstring(PG_GETARG_TEXT_P(1));
+               geobuf_agg_init_context(ctx);
+       } else {
+               ctx = (struct geobuf_agg_context *) PG_GETARG_POINTER(0);
+       }
+
+       if (!type_is_rowtype(get_fn_expr_argtype(fcinfo->flinfo, 2)))
+               lwerror("pgis_asgeobuf_transfn: parameter row cannot be other than a rowtype");
+       ctx->row = PG_GETARG_HEAPTUPLEHEADER(2);
+
+       geobuf_agg_transfn(ctx);
+       PG_RETURN_POINTER(ctx);
+#endif
+}
+
+/**
+ * Encode final state to Geobuf
+ */
+PG_FUNCTION_INFO_V1(pgis_asgeobuf_finalfn);
+Datum pgis_asgeobuf_finalfn(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBPROTOBUF
+       lwerror("Missing libprotobuf-c");
+       PG_RETURN_NULL();
+#else
+       struct geobuf_agg_context *ctx;
+       if (!AggCheckCallContext(fcinfo, NULL))
+               lwerror("pgis_asmvt_finalfn called in non-aggregate context");
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       ctx = (struct geobuf_agg_context *) PG_GETARG_POINTER(0);
+       uint8_t *buf = geobuf_agg_finalfn(ctx);
+       PG_RETURN_BYTEA_P(buf);
+#endif
+}
index 79938eecf6abdc76bb1b66c47643d018bc4044ed..20c1a7f6d65569b4ef100141c503f55455cf2951 100644 (file)
@@ -4412,6 +4412,33 @@ CREATE OR REPLACE FUNCTION postgis_libprotobuf_version()
        AS 'MODULE_PATHNAME','postgis_libprotobuf_version'
        LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;
 
+
+-----------------------------------------------------------------------
+-- GEOBUF OUTPUT
+-- Availability: 2.4.0
+-----------------------------------------------------------------------
+
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION pgis_asgeobuf_transfn(internal, text, anyelement)
+       RETURNS internal
+       AS 'MODULE_PATHNAME', 'pgis_asgeobuf_transfn'
+       LANGUAGE c IMMUTABLE;
+
+-- Availability: 2.4.0
+CREATE OR REPLACE FUNCTION pgis_asgeobuf_finalfn(internal)
+       RETURNS bytea
+       AS 'MODULE_PATHNAME', 'pgis_asgeobuf_finalfn'
+       LANGUAGE c IMMUTABLE;
+
+-- Availability: 2.4.0
+CREATE AGGREGATE ST_AsGeobuf(text, anyelement)
+(
+       sfunc = pgis_asgeobuf_transfn,
+       stype = internal,
+       finalfunc = pgis_asgeobuf_finalfn
+);
+
+
 ------------------------------------------------------------------------
 -- GeoHash (geohash.org)
 ------------------------------------------------------------------------
index f2211834050b4a167d2237eafe16d0f4ce3b198d..c68d3f032c5b4712f16c74f0b80fa6031718c3eb 100644 (file)
@@ -242,9 +242,10 @@ endif
 
 ifeq ($(HAVE_PROTOBUF),yes)
        # protobuf-c adds:
-       # ST_AsMVT
+       # ST_AsMVT, ST_AsGeobuf
        TESTS += \
-               mvt
+               mvt \
+               geobuf
 endif
 
 ifeq ($(HAVE_SFCGAL),yes)
diff --git a/regress/geobuf.sql b/regress/geobuf.sql
new file mode 100644 (file)
index 0000000..1609760
--- /dev/null
@@ -0,0 +1,19 @@
+--set client_min_messages to DEBUG3;
+SELECT 'T1', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_MakePoint(1.1, 2.1) AS geom) AS q;
+SELECT 'T2', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT 'test' as test_str, 1 as test_pos_int, -1 as test_neg_int, 1.1 as test_numeric, 1.1::float as test_float, ST_MakeLine(ST_MakePoint(1,1), ST_MakePoint(2,2)) as geom) AS q;
+SELECT 'T3', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))') as geom) AS q;
+SELECT 'T4', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('POLYGON((0 0,0 5,5 5,5 0,0 0), (1 1,1 2,2 2,2 1,1 1))') as geom) AS q;
+SELECT 'T5', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('MULTIPOINT (10 40, 40 30, 20 20, 30 10)') as geom) AS q;
+SELECT 'T6', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))') as geom) AS q;
+SELECT 'T7', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT 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 'T8', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_GeomFromText('GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))') as geom) AS q;
+SELECT 'T9', encode(ST_AsGeobuf('geom', q), 'base64')
+    FROM (SELECT ST_MakePoint(1, 2, 3) as geom) AS q;
diff --git a/regress/geobuf_expected b/regress/geobuf_expected
new file mode 100644 (file)
index 0000000..ea1502f
--- /dev/null
@@ -0,0 +1,11 @@
+T1|GAEiCgoICgYIABoCFio=
+T2|Cgh0ZXN0X3N0cgoMdGVzdF9wb3NfaW50Cgx0ZXN0X25lZ19pbnQKDHRlc3RfbnVtZXJpYwoKdGVz
+dF9mbG9hdBgAIjoKOAoICAIaBAICAgJqBgoEdGVzdGoCGAFqAiABagUKAzEuMWoJEZqZmZmZmfE/
+cgoAAAEBAgIDAwQE
+T3|GAAiEAoOCgwIBBoIAAAAAgIAAAE=
+T4|GAAiHAoaChgIBBICBAQaEAAAAAoKAAAJAgIAAgIAAAE=
+T5|GAAiEAoOCgwIARoIFFA8EycTFBM=
+T6|GAAiGgoYChYIAxICAwQaDhQUFBQTKFBQExMUExMT
+T7|GAAiJgokCiIIBRIGAgEDAgUDGhZQUCcKMh0oRhMJACcoCR4ePCgTCQAU
+T8|GAAiGAoWChQIBiIGCAAaAggMIggIAhoECAwGCA==
+T9|EAMYACILCgkKBwgAGgMCBAY=