]> pd.if.org Git - dumppoints/commitdiff
initial commit
authorNathan Wagner <nw@hydaspes.if.org>
Sun, 14 Oct 2012 06:23:43 +0000 (06:23 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Sun, 14 Oct 2012 06:23:43 +0000 (06:23 +0000)
18 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
dp.c [new file with mode: 0644]
dumppoints--1.0.sql [new file with mode: 0644]
dumppoints.control [new file with mode: 0644]
t/collection.sql [new file with mode: 0644]
t/ctap.c [new file with mode: 0644]
t/ctap.h [new file with mode: 0644]
t/equivalent.sql [new file with mode: 0644]
t/linestring.sql [new file with mode: 0644]
t/multipoly.sql [new file with mode: 0644]
t/point.sql [new file with mode: 0644]
t/polygon.sql [new file with mode: 0644]
t/polyhedral.sql [new file with mode: 0644]
t/polyrings.sql [new file with mode: 0644]
t/tin.sql [new file with mode: 0644]
t/triangle.sql [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..9d22eb4
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+*.so
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7311fa1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,55 @@
+# written by nathan wagner and placed in the public domain
+
+EXTENSION=dumppoints
+MODULES= dumppoints
+DATA= dumppoints--1.0.sql
+#DOCS=
+SHLIB_LINK= 
+MODULE_big= dumppoints
+OBJS= dp.o
+
+PG_CPPFLAGS := -I..
+PG_CONFIG= pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+
+ifeq ($(PORTNAME), darwin)
+LDFLAGS_SL += -flat_namespace -undefined suppress
+endif
+
+TESTDB=contrib_regression
+
+# doesn't create or drop regression db
+test:  clean all
+       sudo make install
+       psql -d $(TESTDB) -1 -c 'alter database $(TESTDB) set search_path to public,dp,tap'
+       pg_prove -d $(TESTDB) --ext .sql t/*.sql
+
+# newtest to create a regression db
+newtest: clean all
+       sudo make install
+       -dropdb $(TESTDB)
+       createdb $(TESTDB)
+       psql -d $(TESTDB) -1 -c 'create schema tap'
+       psql -d $(TESTDB) -1 -c 'create extension pgtap schema tap'
+       psql -d $(TESTDB) -1 -c 'drop extension if exists dumppoints'
+       psql -d $(TESTDB) -1 -c 'create extension postgis'
+       psql -d $(TESTDB) -1 -c 'create schema dp'
+       psql -d $(TESTDB) -1 -c 'create extension dumppoints schema dp'
+       psql -d $(TESTDB) -1 -c 'alter database $(TESTDB) set search_path to public,dp,tap'
+       pg_prove -d $(TESTDB) --ext .sql t/*.sql
+
+# newtest to create a regression db and drop it after
+droptest: clean all
+       sudo make install
+       -dropdb $(TESTDB)
+       createdb $(TESTDB)
+       psql -d $(TESTDB) -1 -c 'create schema tap'
+       psql -d $(TESTDB) -1 -c 'create extension pgtap schema tap'
+       psql -d $(TESTDB) -1 -c 'drop extension if exists dumppoints'
+       psql -d $(TESTDB) -1 -c 'create extension postgis'
+       psql -d $(TESTDB) -1 -c 'create schema dp'
+       psql -d $(TESTDB) -1 -c 'create extension dumppoints schema dp'
+       psql -d $(TESTDB) -1 -c 'alter database $(TESTDB) set search_path to public,dp,tap'
+       pg_prove -d $(TESTDB) --ext .sql t/*.sql
+       dropdb $(TESTDB)
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..14f30ee
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+A C implemention for ST_DumpPoints.
+
+By Nathan Wagner
+
+Public domain.
diff --git a/dp.c b/dp.c
new file mode 100644 (file)
index 0000000..d688e05
--- /dev/null
+++ b/dp.c
@@ -0,0 +1,277 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/elog.h"
+#include "utils/array.h"
+#include "utils/geo_decls.h"
+#include "utils/lsyscache.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+
+#include "liblwgeom.h"
+
+/* ST_DumpPoints for postgis.
+ * By Nathan Wagner, copyright disclaimed,
+ * this entire file is in the public domain
+ */
+
+PG_MODULE_MAGIC;
+
+struct dumpnode {
+       LWGEOM *geom;
+       int idx; /* which member geom we're working on */
+} ;
+
+/* 32 is the max depth for st_dump, so it seems reasonable
+ * to use the same here
+ */
+#define MAXDEPTH 32
+struct dumpstate {
+       LWGEOM  *root;
+       int     stacklen; /* collections/geoms on stack */
+       int     pathlen; /* polygon rings and such need extra path info */
+       struct  dumpnode stack[MAXDEPTH];
+       Datum   path[34]; /* two more than max depth, for ring and point */
+
+       /* used to cache the type attributes for integer arrays */
+       int16   typlen;
+       bool    byval;
+       char    align;
+
+       int ring; /* ring of top polygon */
+       int pt; /* point of top geom or current ring */
+};
+
+PG_FUNCTION_INFO_V1(LWGEOM_dumppoints);
+
+Datum LWGEOM_dumppoints(PG_FUNCTION_ARGS) {
+       FuncCallContext *funcctx;
+       MemoryContext oldcontext, newcontext;
+
+       GSERIALIZED *pglwgeom;
+       LWCOLLECTION *lwcoll;
+       LWGEOM *lwgeom;
+       struct dumpstate *state;
+       struct dumpnode *node;
+
+       HeapTuple tuple;
+       Datum pathpt[2]; /* used to construct the composite return value */
+       bool isnull[2] = {0,0}; /* needed to say neither value is null */
+       Datum result; /* the actual composite return value */
+
+       if (SRF_IS_FIRSTCALL()) {
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               newcontext = funcctx->multi_call_memory_ctx;
+               oldcontext = MemoryContextSwitchTo(newcontext);
+
+               /* get a local copy of what we're doing a dump points on */
+               pglwgeom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));
+               lwgeom = lwgeom_from_gserialized(pglwgeom);
+
+               /* return early if nothing to do */
+               if (!lwgeom || lwgeom_is_empty(lwgeom)) {
+                       funcctx = SRF_PERCALL_SETUP();
+                       SRF_RETURN_DONE(funcctx);
+               }
+
+               /* Create function state */
+               state = lwalloc(sizeof *state);
+               state->root = lwgeom;
+               state->stacklen = 0;
+               state->pathlen = 0;
+               state->pt = 0;
+               state->ring = 0;
+
+               funcctx->user_fctx = state;
+
+               /*
+                * Push a struct dumpnode on the state stack
+                */
+
+               state->stack[0].idx = 0;
+               state->stack[0].geom = lwgeom;
+               state->stacklen++;
+
+               /*
+                * get tuple description for return type
+                */
+               if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE) {
+                       ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                               errmsg("set-valued function called in context that cannot accept a set")));
+               }
+
+               BlessTupleDesc(funcctx->tuple_desc);
+
+               /* get and cache data for constructing int4 arrays */
+               get_typlenbyvalalign(INT4OID, &state->typlen, &state->byval, &state->align);
+
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /* stuff done on every call of the function */
+       funcctx = SRF_PERCALL_SETUP();
+       newcontext = funcctx->multi_call_memory_ctx;
+
+       /* get state */
+       state = funcctx->user_fctx;
+
+       while (1) {
+               node = &state->stack[state->stacklen-1];
+               lwgeom = node->geom;
+
+               /* need to return a point from this geometry */
+               if (!lwgeom_is_collection(lwgeom)) {
+                       /* either return a point, or pop the stack */
+                       /* TODO use a union?  would save a tiny amount of stack space.
+                        * probably not worth the bother
+                        */
+                       LWLINE  *line;
+                       LWPOLY  *poly;
+                       LWTRIANGLE      *tri;
+                       LWPOINT *lwpoint = NULL;
+                       POINT4D pt;
+
+                       /*
+                        * net result of switch should be to set lwpoint to the
+                        * next point to return, or leave at NULL if there
+                        * are no more points in the geometry
+                        */
+                       switch(lwgeom->type) {
+                               case TRIANGLETYPE:
+                                       tri = lwgeom_as_lwtriangle(lwgeom);
+                                       if (state->pt == 0) {
+                                               state->path[state->pathlen++] = Int32GetDatum(state->ring+1);
+                                       }
+                                       if (state->pt <= 3) {
+                                               getPoint4d_p(tri->points, state->pt, &pt);
+                                               lwpoint = lwpoint_make(tri->srid,
+                                                               FLAGS_GET_Z(tri->points->flags),
+                                                               FLAGS_GET_M(tri->points->flags),
+                                                               &pt);
+                                       }
+                                       if (state->pt > 3) {
+                                               state->pathlen--;
+                                       }
+                                       break;
+                               case POLYGONTYPE:
+                                       poly = lwgeom_as_lwpoly(lwgeom);
+                                       if (state->pt == poly->rings[state->ring]->npoints) {
+                                               state->pt = 0;
+                                               state->ring++;
+                                               state->pathlen--;
+                                       }
+                                       if (state->pt == 0 && state->ring < poly->nrings) {
+                                               /* handle new ring */
+                                               state->path[state->pathlen] = Int32GetDatum(state->ring+1);
+                                               state->pathlen++;
+                                       }
+                                       if (state->ring == poly->nrings) {
+                                       } else {
+                                       /* TODO should be able to directly get the point
+                                        * into the point array of a fixed lwpoint
+                                        */
+                                       /* can't get the point directly from the ptarray because
+                                        * it might be aligned wrong, so at least one memcpy
+                                        * seems unavoidable
+                                        * It might be possible to pass it directly to gserialized
+                                        * depending how that works, it might effectively be gserialized
+                                        * though a brief look at the code indicates not
+                                        */
+                                               getPoint4d_p(poly->rings[state->ring], state->pt, &pt);
+                                               lwpoint = lwpoint_make(poly->srid,
+                                                               FLAGS_GET_Z(poly->rings[state->ring]->flags),
+                                                               FLAGS_GET_M(poly->rings[state->ring]->flags),
+                                                               &pt);
+                                       }
+                                       break;
+                               case POINTTYPE:
+                                       if (state->pt == 0) lwpoint = lwgeom_as_lwpoint(lwgeom);
+                                       break;
+                               case LINETYPE:
+                               case CIRCSTRINGTYPE:
+                                       line = lwgeom_as_lwline(lwgeom);
+                                       if (line->points && state->pt <= line->points->npoints) {
+                                               lwpoint = lwline_get_lwpoint((LWLINE*)lwgeom, state->pt);
+                                       }
+                                       break;
+                               case CURVEPOLYTYPE:
+                               default:
+                                       /* TODO error? */
+                                       if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
+                                       state->pathlen--;
+                                       continue;
+                       }
+
+                       /*
+                        * At this point, lwpoint is either NULL, in which case
+                        * we need to pop the geometry stack and get the next
+                        * geometry, if amy, or lwpoint is set and we construct
+                        * a record type with the integer array of geometry
+                        * indexes and the point number, and the actual point
+                        * geometry itself
+                        */
+
+                       if (!lwpoint) {
+                               /* no point, so pop the geom and look for more */
+                               if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
+                               state->pathlen--;
+                               continue;
+                       } else {
+                               /* write address of current geom/pt */
+                               state->pt++;
+
+                               state->path[state->pathlen] = Int32GetDatum(state->pt);
+                               pathpt[0] = PointerGetDatum(construct_array(state->path, state->pathlen+1,
+                                               INT4OID, state->typlen, state->byval, state->align));
+                               
+                               pathpt[1] = PointerGetDatum(gserialized_from_lwgeom((LWGEOM*)lwpoint,0,0));
+                               
+                               tuple = heap_form_tuple(funcctx->tuple_desc, pathpt, isnull);
+                               result = HeapTupleGetDatum(tuple);
+                               SRF_RETURN_NEXT(funcctx, result);
+                       }
+               }
+
+               lwcoll = (LWCOLLECTION*)node->geom;
+
+               /* if a collection and we have more geoms */
+               if (node->idx < lwcoll->ngeoms) {
+                       /* push the next geom on the path and the stack */
+                       lwgeom = lwcoll->geoms[node->idx++];
+                       state->path[state->pathlen++] = Int32GetDatum(node->idx);
+
+                       node = &state->stack[state->stacklen++];
+                       node->idx = 0;
+                       node->geom = lwgeom;
+
+                       state->pt = 0;
+                       state->ring = 0;
+
+                       /* loop back to beginning, which will then check whatever node we just pushed */
+                       continue;
+               }
+
+               /* no more geometries in the current collection */
+               if (--state->stacklen == 0) SRF_RETURN_DONE(funcctx);
+               state->pathlen--;
+               state->stack[state->stacklen-1].idx++;
+       }
+}
+
+/*
+ * Geometry types of collection types for reference
+ */
+
+#if 0
+        case MULTIPOINTTYPE:
+        case MULTILINETYPE:
+        case MULTIPOLYGONTYPE:
+        case COLLECTIONTYPE:
+        case CURVEPOLYTYPE:
+        case COMPOUNDTYPE:
+        case MULTICURVETYPE:
+        case MULTISURFACETYPE:
+        case POLYHEDRALSURFACETYPE:
+        case TINTYPE:
+#endif
+
diff --git a/dumppoints--1.0.sql b/dumppoints--1.0.sql
new file mode 100644 (file)
index 0000000..dd30410
--- /dev/null
@@ -0,0 +1,12 @@
+-- written by nathan wagner and placed in the public domain
+
+-- we have to call a postgis function to force the
+-- backend to load the postgis shared object file,
+-- otherwise we will get unresolved symbols as this
+-- extension calls postgis functions
+select postgis_version();
+
+CREATE OR REPLACE FUNCTION ST_DumpPoints(geometry)
+        RETURNS SETOF geometry_dump
+        AS '$libdir/dumppoints', 'LWGEOM_dumppoints'
+        LANGUAGE C IMMUTABLE STRICT;
diff --git a/dumppoints.control b/dumppoints.control
new file mode 100644 (file)
index 0000000..e1e53cf
--- /dev/null
@@ -0,0 +1,5 @@
+relocatable = true
+comment = 'Improved ST_DumpPoints'
+default_version = '1.0'
+requires = 'postgis'
+
diff --git a/t/collection.sql b/t/collection.sql
new file mode 100644 (file)
index 0000000..83e5e7c
--- /dev/null
@@ -0,0 +1,56 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'GEOMETRYCOLLECTION(
+                 POINT(99 98), 
+                 LINESTRING(1 1, 3 3),
+                 POLYGON((0 0, 0 1, 1 1, 0 0)),
+                 POLYGON((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)),
+                 MULTIPOLYGON(((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)))
+               )'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+           values 
+               ('{1,1}'::int[], 'POINT(99 98)'),
+               ('{2,1}'::int[], 'POINT(1 1)'),
+               ('{2,2}'::int[], 'POINT(3 3)'),
+               ('{3,1,1}'::int[], 'POINT(0 0)'),
+               ('{3,1,2}'::int[], 'POINT(0 1)'),
+               ('{3,1,3}'::int[], 'POINT(1 1)'),
+               ('{3,1,4}'::int[], 'POINT(0 0)'),
+               ('{4,1,1}'::int[], 'POINT(0 0)'),
+               ('{4,1,2}'::int[], 'POINT(0 9)'),
+               ('{4,1,3}'::int[], 'POINT(9 9)'),
+               ('{4,1,4}'::int[], 'POINT(9 0)'),
+               ('{4,1,5}'::int[], 'POINT(0 0)'),
+               ('{4,2,1}'::int[], 'POINT(5 5)'),
+               ('{4,2,2}'::int[], 'POINT(5 6)'),
+               ('{4,2,3}'::int[], 'POINT(6 6)'),
+               ('{4,2,4}'::int[], 'POINT(5 5)'),
+               ('{5,1,1,1}'::int[], 'POINT(0 0)'),
+               ('{5,1,1,2}'::int[], 'POINT(0 9)'),
+               ('{5,1,1,3}'::int[], 'POINT(9 9)'),
+               ('{5,1,1,4}'::int[], 'POINT(9 0)'),
+               ('{5,1,1,5}'::int[], 'POINT(0 0)'),
+               ('{5,1,2,1}'::int[], 'POINT(5 5)'),
+               ('{5,1,2,2}'::int[], 'POINT(5 6)'),
+               ('{5,1,2,3}'::int[], 'POINT(6 6)'),
+               ('{5,1,2,4}'::int[], 'POINT(5 5)');
+       $want$,
+       'collection'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/ctap.c b/t/ctap.c
new file mode 100644 (file)
index 0000000..9bdf9f3
--- /dev/null
+++ b/t/ctap.c
@@ -0,0 +1,239 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <math.h>
+
+#include "ctap.h"
+
+/* global variable testnum? */
+static int test = 0; /* the test number */
+static int planned = 0;
+static int intodo = 0;
+
+void begin_todo(void) {
+       intodo = 1;
+}
+
+void end_todo(void) {
+       intodo = 0;
+}
+
+void plan(int tests) {
+       test = 0;
+       planned = tests;
+       printf("1..%d\n", tests);
+}
+
+static void print_lazy_plan(void) {
+       printf("1..%d\n", test);
+       fflush(stdout);
+}
+
+void plan_lazy(void) {
+       test = 0;
+       planned = 0;
+       atexit(print_lazy_plan);
+}
+
+void skip_all(const char *why, ...) {
+       printf("1..0");
+       if (why) {
+               va_list args;
+               printf(" # SKIP ");
+               va_start(args, why);
+               vfprintf(stdout, why, args);
+               va_end(args);
+       }
+       printf("\n");
+}
+
+static void vfmtline(int pass, const char *directive, const char *fmt, va_list args) {
+       printf("%sok %d", pass ? "" : "not ", ++test);
+       if (fmt && !directive) {
+               printf(" -");
+       }
+       if (directive) {
+               printf(" # %s", directive);
+       }
+       if (fmt) {
+               printf(" ");
+               vfprintf(stdout, fmt, args);
+       }
+       printf("\n");
+}
+
+#if 0
+static void fmtline(int pass, const char *info, const char *fmt, ...) {
+       va_list args;
+
+       va_start(args,fmt);
+       vfmtline(pass, info, fmt, args);
+       va_end(args);
+}
+#endif
+
+void okv(int pass, const char *fmt, va_list args) {
+       vfmtline(pass, intodo ? "TODO" : 0, fmt, args);
+}
+
+void ok(int pass, char *fmt, ...) {
+       va_list args;
+
+       va_start(args, fmt);
+       okv(pass, fmt, args);
+       va_end(args);
+}
+
+void ok_block(unsigned long count, int pass, const char *fmt, ...) {
+       va_list args;
+       va_list copy;
+
+       if (count == 0) {
+               return;
+       }
+
+       va_start(args, fmt);
+       while (count--) {
+               va_copy(copy, args);
+               okv(pass, fmt, copy);
+               va_end(copy);
+       }
+       va_end(args);
+}
+
+void skip(const char *why, ...) {
+       va_list args;
+
+       va_start(args, why);
+       vfmtline(1, "SKIP", why, args);
+       va_end(args);
+}
+
+void skip_block(unsigned long count, const char *why, ...) {
+       va_list args;
+       va_list copy;
+       va_start(args, why);
+
+       while (count--) {
+               va_copy(copy, args);
+               vfmtline(1, "SKIP", why, args);
+               va_end(copy);
+       }
+
+       va_end(args);
+}
+
+void bail(const char *fmt, ...) {
+       va_list args;
+       printf("Bail out!");
+       if (fmt) {
+               va_start(args, fmt);
+               vfprintf(stdout, fmt, args);
+               va_end(args);
+       }
+       printf("\n");
+       fflush(stdout);
+       exit(1);
+}
+
+void sysbail(const char *fmt, ...) {
+       va_list args;
+       printf("Bail out!");
+       if (fmt) {
+               va_start(args, fmt);
+               vfprintf(stdout, fmt, args);
+               va_end(args);
+       }
+       printf(": %s\n", strerror(errno));
+       fflush(stdout);
+       exit(1);
+}
+
+void sysdiag(const char *fmt, ...) {
+       va_list args;
+       if (!fmt) { return; }
+       printf("# ");
+       va_start(args, fmt);
+       vfprintf(stdout, fmt, args);
+       va_end(args);
+       printf(": %s\n", strerror(errno));
+}
+
+void diag(const char *fmt, ...) {
+       va_list args;
+       if (!fmt) { return; }
+       printf("# ");
+       va_start(args, fmt);
+       vfprintf(stdout, fmt, args);
+       va_end(args);
+       printf("\n");
+}
+
+void is_hex(unsigned long wanted, unsigned long seen, const char *fmt, ...) {
+       va_list args;
+       if (fmt) {
+               va_start(args, fmt);
+               okv(wanted == seen, fmt, args);
+               va_end(args);
+       } else {
+               ok(wanted == seen, NULL);
+       }
+       if (wanted != seen) {
+               diag("wanted: %ld", wanted);
+               diag("got   : %ld", seen);
+       }
+}
+
+
+void is_int(long wanted, long seen, const char *fmt, ...) {
+       va_list args;
+       if (fmt) {
+               va_start(args, fmt);
+               okv(wanted == seen, fmt, args);
+               va_end(args);
+       } else {
+               ok(wanted == seen, NULL);
+       }
+       if (wanted != seen) {
+               diag("wanted: %ld", wanted);
+               diag("got   : %ld", seen);
+       }
+}
+
+void is_double(double wanted, double seen, double eps, const char *fmt, ...) {
+       int pass;
+       va_list args;
+
+       pass = fabs(wanted - seen) <= eps;
+       if (fmt) {
+               va_start(args, fmt);
+               okv(pass, fmt, args);
+               va_end(args);
+       } else {
+               ok(wanted == seen, NULL);
+       }
+       if (!pass) {
+               diag("wanted: %f", wanted);
+               diag("got   : %f", seen);
+       }
+}
+
+void is_string(const char *wanted, const char *seen, const char *fmt, ...) {
+       int pass;
+       va_list args;
+
+       pass = !strcmp(wanted,seen);
+       if (fmt) {
+               va_start(args, fmt);
+               okv(pass, fmt, args);
+               va_end(args);
+       } else {
+               ok(wanted == seen, NULL);
+       }
+       if (!pass) {
+               diag("wanted: %s", wanted);
+               diag("got   : %s", seen);
+       }
+}
diff --git a/t/ctap.h b/t/ctap.h
new file mode 100644 (file)
index 0000000..c641d2f
--- /dev/null
+++ b/t/ctap.h
@@ -0,0 +1,25 @@
+#ifndef TAP_H_
+#define TAP_H_ 1
+
+#include <stdarg.h>
+
+void begin_todo(void);
+void end_todo(void);
+void plan(int tests);
+void plan_lazy(void);
+void skip_all(const char *why, ...);
+void okv(int pass, const char *fmt, va_list args);
+void ok(int pass, char *fmt, ...);
+void ok_block(unsigned long count, int pass, const char *fmt, ...);
+void skip(const char *why, ...);
+void skip_block(unsigned long count, const char *why, ...);
+void bail(const char *fmt, ...);
+void sysbail(const char *fmt, ...);
+void sysdiag(const char *fmt, ...);
+void diag(const char *fmt, ...);
+void is_hex(unsigned long wanted, unsigned long seen, const char *fmt, ...);
+void is_int(long wanted, long seen, const char *fmt, ...);
+void is_double(double wanted, double seen, double eps, const char *fmt, ...);
+void is_string(const char *wanted, const char *seen, const char *fmt, ...);
+
+#endif
diff --git a/t/equivalent.sql b/t/equivalent.sql
new file mode 100644 (file)
index 0000000..a8c0e35
--- /dev/null
@@ -0,0 +1,383 @@
+begin;
+       select plan(9);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POINT (0 9)'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POINT (0 9)'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'single point'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'LINESTRING (
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   )'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'LINESTRING (
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   )'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'linestring'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'polygon'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'TRIANGLE ((
+                       0 0, 
+                       0 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'TRIANGLE ((
+                       0 0, 
+                       0 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'triangle'
+);
+
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ), (
+                       1 1, 
+                       1 3, 
+                       3 2, 
+                       1 1
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ), (
+                       1 1, 
+                       1 3, 
+                       3 2, 
+                       1 1
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'polygon with rings'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'MULTIPOLYGON (((
+                       0 0, 
+                       0 3, 
+                       4 3, 
+                       4 0, 
+                       0 0
+                   )), ((
+                       2 4, 
+                       1 6, 
+                       4 5, 
+                       2 4
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   )))'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'MULTIPOLYGON (((
+                       0 0, 
+                       0 3, 
+                       4 3, 
+                       4 0, 
+                       0 0
+                   )), ((
+                       2 4, 
+                       1 6, 
+                       4 5, 
+                       2 4
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   )))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'multipolygon'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'POLYHEDRALSURFACE (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       1 0 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'POLYHEDRALSURFACE (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       1 0 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $have$,
+       'polyhedralsurface'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'TIN (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'TIN (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $have$,
+       'tin'
+);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'GEOMETRYCOLLECTION(
+                 POINT(99 98), 
+                 LINESTRING(1 1, 3 3),
+                 POLYGON((0 0, 0 1, 1 1, 0 0)),
+                 POLYGON((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)),
+                 MULTIPOLYGON(((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)))
+               )'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (public.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'GEOMETRYCOLLECTION(
+                 POINT(99 98), 
+                 LINESTRING(1 1, 3 3),
+                 POLYGON((0 0, 0 1, 1 1, 0 0)),
+                 POLYGON((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)),
+                 MULTIPOLYGON(((0 0, 0 9, 9 9, 9 0, 0 0), (5 5, 5 6, 6 6, 5 5)))
+               )'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       'collection'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/linestring.sql b/t/linestring.sql
new file mode 100644 (file)
index 0000000..e739167
--- /dev/null
@@ -0,0 +1,36 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'LINESTRING (
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   )'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+               values 
+               ('{1}'::int[], 'POINT(0 0)'),
+               ('{2}'::int[], 'POINT(0 9)'),
+               ('{3}'::int[], 'POINT(9 9)'),
+               ('{4}'::int[], 'POINT(9 0)'),
+               ('{5}'::int[], 'POINT(0 0)');
+       $want$,
+       'linestring'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/multipoly.sql b/t/multipoly.sql
new file mode 100644 (file)
index 0000000..1797e0e
--- /dev/null
@@ -0,0 +1,54 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'MULTIPOLYGON (((
+                       0 0, 
+                       0 3, 
+                       4 3, 
+                       4 0, 
+                       0 0
+                   )), ((
+                       2 4, 
+                       1 6, 
+                       4 5, 
+                       2 4
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   )))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+           values 
+               ('{1,1,1}'::int[], 'POINT(0 0)'),
+               ('{1,1,2}'::int[], 'POINT(0 3)'),
+               ('{1,1,3}'::int[], 'POINT(4 3)'),
+               ('{1,1,4}'::int[], 'POINT(4 0)'),
+               ('{1,1,5}'::int[], 'POINT(0 0)'),
+               ('{2,1,1}'::int[], 'POINT(2 4)'),
+               ('{2,1,2}'::int[], 'POINT(1 6)'),
+               ('{2,1,3}'::int[], 'POINT(4 5)'),
+               ('{2,1,4}'::int[], 'POINT(2 4)'),
+               ('{2,2,1}'::int[], 'POINT(7 6)'),
+               ('{2,2,2}'::int[], 'POINT(6 8)'),
+               ('{2,2,3}'::int[], 'POINT(8 8)'),
+               ('{2,2,4}'::int[], 'POINT(7 6)');
+       $want$,
+       'multipolygon'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/point.sql b/t/point.sql
new file mode 100644 (file)
index 0000000..76e326d
--- /dev/null
@@ -0,0 +1,25 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $want$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POINT (0 9)'::geometry AS geom
+           ) AS g
+         ) j;
+       $want$,
+       $have$
+       values ('{1}'::int[], 'POINT(0 9)');
+       $have$,
+       'single point'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/polygon.sql b/t/polygon.sql
new file mode 100644 (file)
index 0000000..c2745ee
--- /dev/null
@@ -0,0 +1,36 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+           values 
+               ('{1,1}'::int[], 'POINT(0 0)'),
+               ('{1,2}'::int[], 'POINT(0 9)'),
+               ('{1,3}'::int[], 'POINT(9 9)'),
+               ('{1,4}'::int[], 'POINT(9 0)'),
+               ('{1,5}'::int[], 'POINT(0 0)');
+       $want$,
+       'polygon'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/polyhedral.sql b/t/polyhedral.sql
new file mode 100644 (file)
index 0000000..3003b0c
--- /dev/null
@@ -0,0 +1,48 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'POLYHEDRALSURFACE (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       1 0 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $have$,
+       $want$
+               values
+               ('{1,1,1}'::int[], 'POINT(0 0 0)'),
+               ('{1,1,2}'::int[], 'POINT(0 0 1)'),
+               ('{1,1,3}'::int[], 'POINT(0 1 1)'),
+               ('{1,1,4}'::int[], 'POINT(0 1 0)'),
+               ('{1,1,5}'::int[], 'POINT(0 0 0)'),
+               ('{2,1,1}'::int[], 'POINT(0 0 0)'),
+               ('{2,1,2}'::int[], 'POINT(0 1 0)'),
+               ('{2,1,3}'::int[], 'POINT(1 1 0)'),
+               ('{2,1,4}'::int[], 'POINT(1 0 0)'),
+               ('{2,1,5}'::int[], 'POINT(0 0 0)');
+       $want$,
+       'polyhedralsurface'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/polyrings.sql b/t/polyrings.sql
new file mode 100644 (file)
index 0000000..442dde2
--- /dev/null
@@ -0,0 +1,55 @@
+begin;
+       select plan(1);
+
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'POLYGON ((
+                       0 0, 
+                       0 9, 
+                       9 9, 
+                       9 0, 
+                       0 0
+                   ), (
+                       1 1, 
+                       1 3, 
+                       3 2, 
+                       1 1
+                   ), (
+                       7 6, 
+                       6 8, 
+                       8 8, 
+                       7 6
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+           values 
+               ('{1,1}'::int[], 'POINT(0 0)'),
+               ('{1,2}'::int[], 'POINT(0 9)'),
+               ('{1,3}'::int[], 'POINT(9 9)'),
+               ('{1,4}'::int[], 'POINT(9 0)'),
+               ('{1,5}'::int[], 'POINT(0 0)'),
+               ('{2,1}'::int[], 'POINT(1 1)'),
+               ('{2,2}'::int[], 'POINT(1 3)'),
+               ('{2,3}'::int[], 'POINT(3 2)'),
+               ('{2,4}'::int[], 'POINT(1 1)'),
+               ('{3,1}'::int[], 'POINT(7 6)'),
+               ('{3,2}'::int[], 'POINT(6 8)'),
+               ('{3,3}'::int[], 'POINT(8 8)'),
+               ('{3,4}'::int[], 'POINT(7 6)');
+       $want$,
+       'polygon with rings'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/tin.sql b/t/tin.sql
new file mode 100644 (file)
index 0000000..edb5759
--- /dev/null
+++ b/t/tin.sql
@@ -0,0 +1,44 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsEWKT(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+          FROM
+            (SELECT 
+              'TIN (((
+                       0 0 0, 
+                       0 0 1, 
+                       0 1 0, 
+                       0 0 0
+                   )), ((
+                       0 0 0, 
+                       0 1 0, 
+                       1 1 0, 
+                       0 0 0
+                   ))
+                   )'::geometry AS geom
+          ) AS g
+         ) j;
+       $have$,
+       $have$
+            values 
+               ('{1,1,1}'::int[], 'POINT(0 0 0)'),
+               ('{1,1,2}'::int[], 'POINT(0 0 1)'),
+               ('{1,1,3}'::int[], 'POINT(0 1 0)'),
+               ('{1,1,4}'::int[], 'POINT(0 0 0)'),
+               ('{2,1,1}'::int[], 'POINT(0 0 0)'),
+               ('{2,1,2}'::int[], 'POINT(0 1 0)'),
+               ('{2,1,3}'::int[], 'POINT(1 1 0)'),
+               ('{2,1,4}'::int[], 'POINT(0 0 0)');
+       $have$,
+       'tin'
+);
+
+select finish();
+
+rollback;
+\q
+
diff --git a/t/triangle.sql b/t/triangle.sql
new file mode 100644 (file)
index 0000000..7ddc511
--- /dev/null
@@ -0,0 +1,34 @@
+begin;
+       select plan(1);
+
+select results_eq(
+       $have$
+       SELECT path, ST_AsText(geom) 
+       FROM (
+         SELECT (dp.ST_DumpPoints(g.geom)).* 
+         FROM
+           (SELECT 
+              'TRIANGLE ((
+                       0 0, 
+                       0 9, 
+                       9 0, 
+                       0 0
+                   ))'::geometry AS geom
+           ) AS g
+         ) j;
+       $have$,
+       $want$
+           values 
+               ('{1,1}'::int[], 'POINT(0 0)'),
+               ('{1,2}'::int[], 'POINT(0 9)'),
+               ('{1,3}'::int[], 'POINT(9 0)'),
+               ('{1,4}'::int[], 'POINT(0 0)');
+       $want$,
+       'triangle'
+);
+
+select finish();
+
+rollback;
+\q
+