From: Daniel Baston Date: Wed, 18 Nov 2015 13:05:32 +0000 (+0000) Subject: add LWPOINTITERATOR (closes #3366) X-Git-Tag: 2.3.0beta1~377 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=94a8835cc824508f17bd3cc4d1e7cd01c9a93c80;p=postgis add LWPOINTITERATOR (closes #3366) git-svn-id: http://svn.osgeo.org/postgis/trunk@14400 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/liblwgeom/Makefile.in b/liblwgeom/Makefile.in index 60d7a38fa..483bcf894 100644 --- a/liblwgeom/Makefile.in +++ b/liblwgeom/Makefile.in @@ -64,6 +64,7 @@ SA_OBJS = \ lwin_geojson.o \ lwin_wkb.o \ lwin_twkb.o \ + lwiterator.o \ lwout_wkt.o \ lwout_twkb.o \ lwin_wkt_parse.o \ diff --git a/liblwgeom/cunit/Makefile.in b/liblwgeom/cunit/Makefile.in index ea283ab3e..08a0f2443 100644 --- a/liblwgeom/cunit/Makefile.in +++ b/liblwgeom/cunit/Makefile.in @@ -58,6 +58,7 @@ OBJS= \ cu_in_wkb.o \ cu_in_wkt.o \ cu_in_encoded_polyline.o \ + cu_iterator.o \ cu_varint.o \ cu_unionfind.o \ cu_tester.o diff --git a/liblwgeom/cunit/cu_iterator.c b/liblwgeom/cunit/cu_iterator.c new file mode 100644 index 000000000..7311ab0dd --- /dev/null +++ b/liblwgeom/cunit/cu_iterator.c @@ -0,0 +1,265 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "../liblwgeom_internal.h" + +char* inputs[] = +{ + "POINT (17 253)", + "POINT Z (17 253 018)", + "TRIANGLE ((0 0, 10 0, 10 10, 0 0))", + "LINESTRING (17 253, -44 28, 33 11, 26 44)", + "LINESTRING M (17 253 0, -44 28 1, 33 11 2, 26 44 3)", + "POLYGON((26426 65078,26531 65242,26075 65136,26096 65427,26426 65078))", + "MULTIPOINT ((1 1), (1 1))", + "MULTILINESTRING Z ((1 1 0, 2 2 0), (3 3 1, 4 4 1))", + "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)), ((20 20, 20 30, 30 30, 20 20)))", + "POINT EMPTY", + "LINESTRING M EMPTY", + "POLYGON Z EMPTY", + "GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION (MULTIPOINT ((14 80), (22 12)))", + "GEOMETRYCOLLECTION (POINT (3 7), LINESTRING (0 0, 14 3), GEOMETRYCOLLECTION(POINT (2 8)))", + "GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(MULTIPOINT ((2 8))))", + "GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", + "CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3)", + "COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),(4 3, 4 5, 1 4, 0 0))", + "MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4))", + "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),(4 3, 4 5, 1 4, 0 0)), LINESTRING (0.1 0.1, 0.3 0.1, 0.3 0.3, 0.1 0.1) )", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11)))", + "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)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )", + "TIN(((80 130,50 160,80 70,80 130)),((50 160,10 190,10 70,50 160)), ((80 70,50 160,10 70,80 70)),((120 160,120 190,50 160,120 160)), ((120 190,10 190,50 160,120 190)))" +}; + +static uint32_t +count_points_using_iterator(LWGEOM* g) +{ + POINT4D p; + uint32_t count = 0; + LWPOINTITERATOR* it = lwpointiterator_create(g); + + while (lwpointiterator_has_next(it)) + { + CU_ASSERT_TRUE(lwpointiterator_next(it, &p)); + count++; + } + + lwpointiterator_destroy(it); + + return count; +} + +static void +test_point_count(void) +{ + char* types_visited = lwalloc(NUMTYPES * sizeof(char)); + memset(types_visited, LW_FALSE, NUMTYPES * sizeof(char)); + + uint32_t i; + for (i = 0; i < sizeof(inputs)/sizeof(char*); i++) + { + LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE); + types_visited[lwgeom_get_type(input)] = LW_TRUE; + + uint32_t itercount = count_points_using_iterator(input); + + CU_ASSERT_EQUAL(lwgeom_count_vertices(input), itercount); + + lwgeom_free(input); + } + + /* Assert that every valid LWGEOM type has been tested */ + for (i = 1; i < NUMTYPES; i++) + { + CU_ASSERT_TRUE(types_visited[i]); + } + + lwfree(types_visited); +} + +static void +test_cannot_modify_read_only(void) +{ + LWGEOM* input = lwgeom_from_wkt(inputs[0], LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it = lwpointiterator_create(input); + + POINT4D p; + p.x = 3.2; + p.y = 4.8; + + CU_ASSERT_EQUAL(LW_FAILURE, lwpointiterator_modify_next(it, &p)); + + lwgeom_free(input); + lwpointiterator_destroy(it); +} + +static void +test_modification(void) +{ + uint32_t i; + uint32_t j = 0; + + for (i = 0; i < sizeof(inputs)/sizeof(char*); i++) + { + LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it1 = lwpointiterator_create_rw(input); + LWPOINTITERATOR* it2 = lwpointiterator_create(input);; + + while (lwpointiterator_has_next(it1)) + { + /* Make up a coordinate, assign it to the next spot in it1, + * read it from it2 to verify that it was assigned correctly. */ + POINT4D p1, p2; + p1.x = sqrt(j++); + p1.y = sqrt(j++); + p1.z = sqrt(j++); + p1.m = sqrt(j++); + + CU_ASSERT_TRUE(lwpointiterator_modify_next(it1, &p1)); + CU_ASSERT_TRUE(lwpointiterator_next(it2, &p2)); + + CU_ASSERT_EQUAL(p1.x, p2.x); + CU_ASSERT_EQUAL(p1.y, p2.y); + + if (lwgeom_has_z(input)) + CU_ASSERT_EQUAL(p1.z, p2.z); + + if (lwgeom_has_m(input)) + CU_ASSERT_EQUAL(p1.m, p2.m); + } + + lwgeom_free(input); + + lwpointiterator_destroy(it1); + lwpointiterator_destroy(it2); + } +} + +static void +test_no_memory_leaked_when_iterator_is_partially_used(void) +{ + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", LW_PARSER_CHECK_NONE); + + LWPOINTITERATOR* it = lwpointiterator_create(g); + lwpointiterator_next(it, NULL); + lwpointiterator_next(it, NULL); + + lwpointiterator_destroy(it); + lwgeom_free(g); +} + +static void +test_mixed_rw_access(void) +{ + uint32_t i = 0; + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it1 = lwpointiterator_create_rw(g); + LWPOINTITERATOR* it2 = lwpointiterator_create(g); + + /* Flip the coordinates of the 3rd point */ + while(lwpointiterator_has_next(it1)) + { + if (i == 2) + { + POINT4D p; + double tmp; + + lwpointiterator_peek(it1, &p); + tmp = p.x; + p.x = p.y; + p.y = tmp; + + lwpointiterator_modify_next(it1, &p); + } + else + { + lwpointiterator_next(it1, NULL); + } + i++; + } + CU_ASSERT_EQUAL(5, i); /* Every point was visited */ + lwpointiterator_destroy(it1); + + /* Verify that the points are as expected */ + POINT2D points[] = + { + { .x = 3, .y = 7 }, + { .x = 2, .y = 8 }, + { .x = 3, .y = 4 }, + { .x = 2, .y = 8 }, + { .x = 17, .y = 3} + }; + + for (i = 0; lwpointiterator_has_next(it2); i++) + { + POINT4D p; + + lwpointiterator_next(it2, &p); + + CU_ASSERT_EQUAL(p.x, points[i].x); + CU_ASSERT_EQUAL(p.y, points[i].y); + } + + lwpointiterator_destroy(it2); + lwgeom_free(g); +} + +static void +test_ordering(void) +{ + uint32_t i = 0; + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)), MULTIPOINT((4 4), (3 3)))", LW_PARSER_CHECK_NONE); + + POINT2D points[] = { {.x = 0, .y = 0}, + {.x = 0, .y = 10}, + {.x = 10, .y = 10}, + {.x = 0, .y = 10}, + {.x = 0, .y = 0}, + {.x = 1, .y = 1}, + {.x = 1, .y = 2}, + {.x = 2, .y = 2}, + {.x = 2, .y = 1}, + {.x = 1, .y = 1}, + {.x = 4, .y = 4}, + {.x = 3, .y = 3} + }; + + LWPOINTITERATOR* it = lwpointiterator_create(g); + POINT4D p; + + for (i = 0; lwpointiterator_has_next(it); i++) + { + CU_ASSERT_EQUAL(LW_SUCCESS, lwpointiterator_next(it, &p)); + CU_ASSERT_EQUAL(p.x, points[i].x); + CU_ASSERT_EQUAL(p.y, points[i].y); + } + + lwpointiterator_destroy(it); + lwgeom_free(g); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void iterator_suite_setup(void); +void iterator_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("iterator", NULL, NULL); + PG_ADD_TEST(suite, test_point_count); + PG_ADD_TEST(suite, test_ordering); + PG_ADD_TEST(suite, test_modification); + PG_ADD_TEST(suite, test_mixed_rw_access); + PG_ADD_TEST(suite, test_cannot_modify_read_only); + PG_ADD_TEST(suite, test_no_memory_leaked_when_iterator_is_partially_used); +} diff --git a/liblwgeom/cunit/cu_tester.c b/liblwgeom/cunit/cu_tester.c index 82af19249..ef41e82f7 100644 --- a/liblwgeom/cunit/cu_tester.c +++ b/liblwgeom/cunit/cu_tester.c @@ -38,6 +38,7 @@ extern void unionfind_suite_setup(void); extern void homogenize_suite_setup(void); extern void in_encoded_polyline_suite_setup(void); extern void in_geojson_suite_setup(void); +extern void iterator_suite_setup(void); extern void twkb_in_suite_setup(void); extern void libgeom_suite_setup(void); extern void measures_suite_setup(void); @@ -82,6 +83,7 @@ PG_SuiteSetup setupfuncs[] = #if HAVE_LIBJSON in_geojson_suite_setup, #endif + iterator_suite_setup, twkb_in_suite_setup, libgeom_suite_setup, measures_suite_setup, diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index f417c32c1..414c6e625 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -1598,6 +1598,52 @@ extern void lwgeom_swap_ordinates(LWGEOM *in, LWORD o1, LWORD o2); */ extern LWGEOM* lwgeom_flip_coordinates(LWGEOM *in); +struct LWPOINTITERATOR; +typedef struct LWPOINTITERATOR LWPOINTITERATOR; + +/** + * Create a new LWPOINTITERATOR over supplied LWGEOM* + */ +extern LWPOINTITERATOR* lwpointiterator_create(const LWGEOM* g); + +/** + * Create a new LWPOINTITERATOR over supplied LWGEOM* + * Supports modification of coordinates during iteration. + */ +extern LWPOINTITERATOR* lwpointiterator_create_rw(LWGEOM* g); + +/** + * Free all memory associated with the iterator + */ +extern void lwpointiterator_destroy(LWPOINTITERATOR* s); + +/** + * Returns LW_TRUE if there is another point available in the iterator. + */ +extern int lwpointiterator_has_next(LWPOINTITERATOR* s); + +/** + * Attempts to replace the next point int the iterator with p, and advances + * the iterator to the next point. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + * */ +extern int lwpointiterator_modify_next(LWPOINTITERATOR* s, const POINT4D* p); + +/** + * Attempts to assign the next point in the iterator to p, and advances + * the iterator to the next point. If p is NULL, the iterator will be + * advanced without reading a point. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + * */ +extern int lwpointiterator_next(LWPOINTITERATOR* s, POINT4D* p); + +/** + * Attempts to assigns the next point in the iterator to p. Does not advance. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + */ +extern int lwpointiterator_peek(LWPOINTITERATOR* s, POINT4D* p); + + /** * Convert a single hex digit into the corresponding char */ diff --git a/liblwgeom/lwiterator.c b/liblwgeom/lwiterator.c new file mode 100644 index 000000000..7d7094b46 --- /dev/null +++ b/liblwgeom/lwiterator.c @@ -0,0 +1,269 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "liblwgeom.h" +#include "lwgeom_log.h" + +struct LISTNODE +{ + struct LISTNODE* next; + void* item; +}; +typedef struct LISTNODE LISTNODE; + +/* The LWPOINTITERATOR consists of two stacks of items to process: a stack + * of geometries, and a stack of POINTARRAYs extracted from those geometries. + * The index "i" refers to the "next" point, which is found at the top of the + * pointarrays stack. + * + * When the pointarrays stack is depleted, we pull a geometry from the geometry + * stack to replenish it. + */ +struct LWPOINTITERATOR +{ + LISTNODE* geoms; + LISTNODE* pointarrays; + uint32_t i; + char allow_modification; +}; + +static LISTNODE* +prepend_node(void* g, LISTNODE* front) +{ + LISTNODE* n = lwalloc(sizeof(LISTNODE)); + n->item = g; + n->next = front; + + return n; +} + +static LISTNODE* +pop_node(LISTNODE* i) +{ + LISTNODE* next = i->next; + lwfree(i); + return next; +} + +static int +add_lwgeom_to_stack(LWPOINTITERATOR* s, LWGEOM* g) +{ + if (lwgeom_is_empty(g)) + return LW_FAILURE; + + s->geoms = prepend_node(g, s->geoms); + return LW_SUCCESS; +} + +/** Return a pointer to the first of one or more LISTNODEs holding the POINTARRAYs + * of a geometry. Will not handle GeometryCollections. + */ +static LISTNODE* +extract_pointarrays_from_lwgeom(LWGEOM* g) +{ + switch(lwgeom_get_type(g)) + { + case POINTTYPE: + return prepend_node(lwgeom_as_lwpoint(g)->point, NULL); + case LINETYPE: + return prepend_node(lwgeom_as_lwline(g)->points, NULL); + case TRIANGLETYPE: + return prepend_node(lwgeom_as_lwtriangle(g)->points, NULL); + case CIRCSTRINGTYPE: + return prepend_node(lwgeom_as_lwcircstring(g)->points, NULL); + case POLYGONTYPE: + { + LISTNODE* n = NULL; + + LWPOLY* p = lwgeom_as_lwpoly(g); + int i; + for (i = p->nrings - 1; i >= 0; i--) + { + n = prepend_node(p->rings[i], n); + } + + return n; + } + default: + lwerror("Unsupported geometry type for lwpointiterator"); + } + + return NULL; +} + +/** Remove an LWCOLLECTION from the iterator stack, and add the components of the + * LWCOLLECTIONs to the stack. + */ +static void +unroll_collection(LWPOINTITERATOR* s) +{ + int i; + LWCOLLECTION* c; + + if (!s->geoms) + { + return; + } + + c = (LWCOLLECTION*) s->geoms->item; + s->geoms = pop_node(s->geoms); + + for (i = c->ngeoms - 1; i >= 0; i--) + { + LWGEOM* g = lwcollection_getsubgeom(c, i); + + add_lwgeom_to_stack(s, g); + } +} + +/** Unroll LWCOLLECTIONs from the top of the stack, as necessary, until the element at the + * top of the stack is not a LWCOLLECTION. + */ +static void +unroll_collections(LWPOINTITERATOR* s) +{ + while(s->geoms && lwgeom_is_collection(s->geoms->item)) + { + unroll_collection(s); + } +} + +static int +lwpointiterator_advance(LWPOINTITERATOR* s) +{ + s->i += 1; + + /* We've reached the end of our current POINTARRAY. Try to see if there + * are any more POINTARRAYS on the stack. */ + if (s->pointarrays && s->i >= ((POINTARRAY*) s->pointarrays->item)->npoints) + { + s->pointarrays = pop_node(s->pointarrays); + s->i = 0; + } + + /* We don't have a current POINTARRAY. Pull a geometry from the stack, and + * decompose it into its POINTARRARYs. */ + if (!s->pointarrays) + { + LWGEOM* g; + unroll_collections(s); + + if (!s->geoms) + { + return LW_FAILURE; + } + + s->i = 0; + g = s->geoms->item; + s->pointarrays = extract_pointarrays_from_lwgeom(g); + + s->geoms = pop_node(s->geoms); + } + + if (!s->pointarrays) + { + return LW_FAILURE; + } + return LW_SUCCESS; +} + +/* Public API implementation */ + +int +lwpointiterator_peek(LWPOINTITERATOR* s, POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + return getPoint4d_p(s->pointarrays->item, s->i, p); +} + +int +lwpointiterator_has_next(LWPOINTITERATOR* s) +{ + if (s->pointarrays && s->i < ((POINTARRAY*) s->pointarrays->item)->npoints) + return LW_TRUE; + return LW_FALSE; +} + +int +lwpointiterator_next(LWPOINTITERATOR* s, POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + /* If p is NULL, just advance without reading */ + if (p && !lwpointiterator_peek(s, p)) + return LW_FAILURE; + + lwpointiterator_advance(s); + return LW_SUCCESS; +} + +int +lwpointiterator_modify_next(LWPOINTITERATOR* s, const POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + if (!s->allow_modification) + { + lwerror("Cannot write to read-only iterator"); + return LW_FAILURE; + } + + ptarray_set_point4d(s->pointarrays->item, s->i, p); + + lwpointiterator_advance(s); + return LW_SUCCESS; +} + +LWPOINTITERATOR* +lwpointiterator_create(const LWGEOM* g) +{ + LWPOINTITERATOR* it = lwpointiterator_create_rw((LWGEOM*) g); + it->allow_modification = LW_FALSE; + + return it; +} + +LWPOINTITERATOR* +lwpointiterator_create_rw(LWGEOM* g) +{ + LWPOINTITERATOR* it = lwalloc(sizeof(LWPOINTITERATOR)); + + it->geoms = NULL; + it->pointarrays = NULL; + it->i = 0; + it->allow_modification = LW_TRUE; + + add_lwgeom_to_stack(it, g); + lwpointiterator_advance(it); + + return it; +} + +void +lwpointiterator_destroy(LWPOINTITERATOR* s) +{ + while (s->geoms != NULL) + { + s->geoms = pop_node(s->geoms); + } + + while (s->pointarrays != NULL) + { + s->pointarrays = pop_node(s->pointarrays); + } + + lwfree(s); +}