]> granicus.if.org Git - postgis/commitdiff
add LWPOINTITERATOR (closes #3366)
authorDaniel Baston <dbaston@gmail.com>
Wed, 18 Nov 2015 13:05:32 +0000 (13:05 +0000)
committerDaniel Baston <dbaston@gmail.com>
Wed, 18 Nov 2015 13:05:32 +0000 (13:05 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@14400 b70326c6-7e19-0410-871a-916f4a2858ee

liblwgeom/Makefile.in
liblwgeom/cunit/Makefile.in
liblwgeom/cunit/cu_iterator.c [new file with mode: 0644]
liblwgeom/cunit/cu_tester.c
liblwgeom/liblwgeom.h.in
liblwgeom/lwiterator.c [new file with mode: 0644]

index 60d7a38fa5dd2938df2020eb1d3feae484263dc6..483bcf89453861a829016905509a74b898ab6aba 100644 (file)
@@ -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 \
index ea283ab3ec466c1633af5b13a6013b96e98cb94c..08a0f24430558d1e61353ee6fce4b64e89b824ec 100644 (file)
@@ -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 (file)
index 0000000..7311ab0
--- /dev/null
@@ -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);
+}
index 82af19249e97c6458c194417d8a5d6548ffd8c7c..ef41e82f76fef2bc79e377600f4f90fc3e4ef049 100644 (file)
@@ -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,
index f417c32c1c44ba603c04dbdab2ab65329c1ece7b..414c6e625a361fc4714d3d860c8adc7cc58c104c 100644 (file)
@@ -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 (file)
index 0000000..7d7094b
--- /dev/null
@@ -0,0 +1,269 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright 2015 Daniel Baston <dbaston@gmail.com>
+ *
+ * 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);
+}