From 6b9abfe83d10601025fead253e3be3cb0d548474 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Sun, 14 Oct 2012 06:23:43 +0000 Subject: [PATCH] initial commit --- .gitignore | 2 + Makefile | 55 +++++++ README | 5 + dp.c | 277 ++++++++++++++++++++++++++++++++ dumppoints--1.0.sql | 12 ++ dumppoints.control | 5 + t/collection.sql | 56 +++++++ t/ctap.c | 239 +++++++++++++++++++++++++++ t/ctap.h | 25 +++ t/equivalent.sql | 383 ++++++++++++++++++++++++++++++++++++++++++++ t/linestring.sql | 36 +++++ t/multipoly.sql | 54 +++++++ t/point.sql | 25 +++ t/polygon.sql | 36 +++++ t/polyhedral.sql | 48 ++++++ t/polyrings.sql | 55 +++++++ t/tin.sql | 44 +++++ t/triangle.sql | 34 ++++ 18 files changed, 1391 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README create mode 100644 dp.c create mode 100644 dumppoints--1.0.sql create mode 100644 dumppoints.control create mode 100644 t/collection.sql create mode 100644 t/ctap.c create mode 100644 t/ctap.h create mode 100644 t/equivalent.sql create mode 100644 t/linestring.sql create mode 100644 t/multipoly.sql create mode 100644 t/point.sql create mode 100644 t/polygon.sql create mode 100644 t/polyhedral.sql create mode 100644 t/polyrings.sql create mode 100644 t/tin.sql create mode 100644 t/triangle.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d22eb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +*.so diff --git a/Makefile b/Makefile new file mode 100644 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 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 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 index 0000000..dd30410 --- /dev/null +++ b/dumppoints--1.0.sql @@ -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 index 0000000..e1e53cf --- /dev/null +++ b/dumppoints.control @@ -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 index 0000000..83e5e7c --- /dev/null +++ b/t/collection.sql @@ -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 index 0000000..9bdf9f3 --- /dev/null +++ b/t/ctap.c @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include + +#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 index 0000000..c641d2f --- /dev/null +++ b/t/ctap.h @@ -0,0 +1,25 @@ +#ifndef TAP_H_ +#define TAP_H_ 1 + +#include + +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 index 0000000..a8c0e35 --- /dev/null +++ b/t/equivalent.sql @@ -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 index 0000000..e739167 --- /dev/null +++ b/t/linestring.sql @@ -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 index 0000000..1797e0e --- /dev/null +++ b/t/multipoly.sql @@ -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 index 0000000..76e326d --- /dev/null +++ b/t/point.sql @@ -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 index 0000000..c2745ee --- /dev/null +++ b/t/polygon.sql @@ -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 index 0000000..3003b0c --- /dev/null +++ b/t/polyhedral.sql @@ -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 index 0000000..442dde2 --- /dev/null +++ b/t/polyrings.sql @@ -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 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 index 0000000..7ddc511 --- /dev/null +++ b/t/triangle.sql @@ -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 + -- 2.40.0