--- /dev/null
+# 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)
--- /dev/null
+A C implemention for ST_DumpPoints.
+
+By Nathan Wagner
+
+Public domain.
--- /dev/null
+#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
+
--- /dev/null
+-- 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;
--- /dev/null
+relocatable = true
+comment = 'Improved ST_DumpPoints'
+default_version = '1.0'
+requires = 'postgis'
+
--- /dev/null
+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
+
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+