]> granicus.if.org Git - postgis/commitdiff
Implement ST_AddEdgeModFace in C
authorSandro Santilli <strk@keybit.net>
Fri, 17 Jul 2015 16:41:51 +0000 (16:41 +0000)
committerSandro Santilli <strk@keybit.net>
Fri, 17 Jul 2015 16:41:51 +0000 (16:41 +0000)
Add callbacks to:
 - get nodes and edges within box2d,
   edges by node or face, nodes by face.
 - insert faces.
 - update nodes, faces and edges.
 - update TopoGeometries after face split.
 - get edges in a ring

Also fixes installation and de-installation of liblwgeom_topo.h

Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8)

git-svn-id: http://svn.osgeo.org/postgis/trunk@13808 b70326c6-7e19-0410-871a-916f4a2858ee

liblwgeom/Makefile.in
liblwgeom/liblwgeom_topo.h
liblwgeom/liblwgeom_topo_internal.h
liblwgeom/lwgeom_topo.c
topology/postgis_topology.c
topology/sql/sqlmm.sql.in

index 9a91fef061912646fb739f42db7a83b515a028f8..a218f0511d2181ab392adfb7d811beb56ecf9c56 100644 (file)
@@ -139,10 +139,12 @@ uninstall: uninstall-liblwgeom
 install-liblwgeom: liblwgeom.la
        $(LIBTOOL) --mode=install $(INSTALL) liblwgeom.la "$(DESTDIR)$(libdir)/liblwgeom.la"
        $(INSTALL) -m 0644 liblwgeom.h "$(DESTDIR)$(includedir)/liblwgeom.h"
+       $(INSTALL) -m 0644 liblwgeom_topo.h "$(DESTDIR)$(includedir)/liblwgeom_topo.h"
 
 uninstall-liblwgeom:
        $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/liblwgeom.la"
        $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/liblwgeom.h"
+       $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/liblwgeom_topo.h"
 
 # Make all objects depend upon postgis_config.h and postgis_svn_revision.h
 $(LT_OBJS): ../postgis_config.h ../postgis_svn_revision.h $(SA_HEADERS)
index 9cfc1ac20f8b813df8de9fb7af1cbae293a71717..6cade652987cb5d9750484052494b55aa4455baf 100644 (file)
@@ -13,8 +13,6 @@
 #ifndef LIBLWGEOM_TOPO_H
 #define LIBLWGEOM_TOPO_H 1
 
-#include "../postgis_config.h"
-
 #include "liblwgeom.h"
 
 /* INT64 */
@@ -84,6 +82,7 @@ LWT_ISO_FACE;
 /** Face fields */
 #define LWT_COL_FACE_FACE_ID         1<<0
 #define LWT_COL_FACE_MBR             1<<1
+#define LWT_COL_FACE_ALL            (1<<2)-1
 
 typedef enum LWT_SPATIALTYPE_T {
   LWT_PUNTAL = 0,
@@ -348,12 +347,15 @@ typedef struct LWT_BE_CALLBACKS_T {
    * @param topo the topology to act upon
    * @param ids an array of element identifiers
    * @param numelems input/output parameter, pass number of edge identifiers
-   *                 in the input array, gets number of node in output array.
+   *                 in the input array, gets number of node in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
    * @param fields fields to be filled in the returned structure, see
    *               LWT_COL_FACE_* macros
    *
-   * @return an array of faces
-   *         or NULL on error (@see lastErrorMessage)
+   * @return an array of faces or NULL in the following cases:
+   *         - none found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
    */
   LWT_ISO_FACE* (*getFaceById) (
       const LWT_BE_TOPOLOGY* topo,
@@ -414,6 +416,268 @@ typedef struct LWT_BE_CALLBACKS_T {
       const LWT_ISO_EDGE* sel_edge, int sel_fields
   );
 
+  /**
+   * Get nodes within a 2D bounding box
+   *
+   * @param topo the topology to act upon
+   * @param box the query box
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_NODE_* macros
+   * @param limit max number of nodes to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of nodes or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  LWT_ISO_NODE* (*getNodeWithinBox2D) (
+      const LWT_BE_TOPOLOGY* topo,
+      const GBOX* box,
+      int* numelems, int fields, int limit
+  );
+
+  /**
+   * Get edges within a 2D bounding box
+   *
+   * @param topo the topology to act upon
+   * @param box the query box
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_EDGE_* macros
+   * @param limit max number of edges to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of edges or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  LWT_ISO_EDGE* (*getEdgeWithinBox2D) (
+      const LWT_BE_TOPOLOGY* topo,
+      const GBOX* box,
+      int* numelems, int fields, int limit
+  );
+
+  /**
+   * Get edges that start or end on any of the given node identifiers
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of node identifiers
+   * @param numelems input/output parameter, pass number of node identifiers
+   *                 in the input array, gets number of edges in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_EDGE_* macros
+   *
+   * @return an array of edges that are incident to a node
+   *         or NULL in the following cases:
+   *         - no edge found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  LWT_ISO_EDGE* (*getEdgeByNode) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Update nodes selected by fields match/mismatch
+   *
+   * @param topo the topology to act upon
+   * @param sel_node an LWT_ISO_NODE object with selecting fields set.
+   * @param sel_fields fields used to select nodes to be updated,
+   *                   see LWT_COL_NODE_* macros
+   * @param upd_node an LWT_ISO_NODE object with updated fields set.
+   * @param upd_fields fields to be updated for the selected nodes,
+   *                   see LWT_COL_NODE_* macros
+   * @param exc_node an LWT_ISO_NODE object with exclusion fields set,
+   *                 can be NULL if no exlusion condition exists.
+   * @param exc_fields fields used for excluding nodes from the update,
+   *                   see LWT_COL_NODE_* macros
+   *
+   * @return number of nodes being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateNodes) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_NODE* sel_node, int sel_fields,
+      const LWT_ISO_NODE* upd_node, int upd_fields,
+      const LWT_ISO_NODE* exc_node, int exc_fields
+  );
+
+  /**
+   * Update TopoGeometry objects after a face split event
+   *
+   * @param topo the topology to act upon
+   * @param split_face identifier of the face that was splitted.
+   * @param new_face1 identifier of the first new face that was created
+   *        as a result of face splitting.
+   * @param new_face2 identifier of the second new face that was created
+   *        as a result of face splitting, or -1 if the old face was
+   *        modified rather than replaced.
+   *
+        * @return 1 on success, 0 on error
+   *
+   */
+  int (*updateTopoGeomFaceSplit) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2
+  );
+
+  /**
+   * Insert faces
+   *
+   * Insert face primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param faces the faces to insert. Those with a node_id set to -1
+   *              it will be replaced to an automatically assigned identifier
+   * @param nelems number of elements in the faces array
+   *
+   * @return number of inserted faces, or -1 (@see lastErrorMessage)
+   */
+  int (*insertFaces) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_FACE* faces,
+      int numelems
+  );
+
+  /**
+   * Update faces by id
+   *
+   * @param topo the topology to act upon
+   * @param faces an array of LWT_ISO_FACE object with selecting id
+   *              and setting mbr.
+   * @param numfaces number of faces in the "faces" array
+   *
+   * @return number of faces being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateFacesById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_FACE* faces, int numfaces
+  );
+
+  /*
+   * Get the ordered list edge visited by a side walk
+   *
+   * The walk starts from the side of an edge and stops when
+   * either the max number of visited edges OR the starting
+   * position is reached. The output list never includes a
+   * duplicated signed edge identifier.
+   *
+   * It is expected that the walk uses the "next_left" and "next_right"
+   * attributes of ISO edges to perform the walk (rather than recomputing
+   * the turns at each node).
+   *
+   * @param topo the topology to operate on
+   * @param edge walk start position and direction:
+   *             abs value identifies the edge, sign expresses
+   *             side (left if positive, right if negative)
+   *             and direction (forward if positive, backward if negative).
+   * @param numedges output parameter, gets the number of edges visited
+   * @param limit max edges to return (to avoid an infinite loop in case
+   *              of a corrupted topology). 0 is for unlimited.
+   *              The function is expected to error out if the limit is hit.
+   *
+   * @return an array of signed edge identifiers (positive edges being
+   *         walked in their direction, negative ones in opposite) or
+   *         NULL on error (@see lastErroMessage)
+   */
+  LWT_ELEMID* (*getRingEdges) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ELEMID edge, int *numedges, int limit
+  );
+
+  /**
+   * Update edges by id
+   *
+   * @param topo the topology to act upon
+   * @param edges an array of LWT_ISO_EDGE object with selecting id
+   *              and updating fields.
+   * @param numedges number of edges in the "edges" array
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see LWT_COL_EDGE_* macros
+   *
+   * @return number of edges being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateEdgesById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_EDGE* edges, int numedges,
+      int upd_fields
+  );
+
+  /**
+   * Get edges that have any of the given faces on the left or right side
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of face identifiers
+   * @param numelems input/output parameter, pass number of face identifiers
+   *                 in the input array, gets number of edges in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_EDGE_* macros
+   *
+   * @return an array of edges identifiers or NULL in the following cases:
+   *         - no edge found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  LWT_ISO_EDGE* (*getEdgeByFace) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get isolated nodes contained in any of the given faces
+   *
+   * @param topo the topology to act upon
+   * @param faces an array of face identifiers
+   * @param numelems input/output parameter, pass number of face
+   *                 identifiers in the input array, gets number of
+   *                 nodes in output array if the return is not null,
+   *                 otherwise see @return section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_NODE_* macros
+   *
+   * @return an array of nodes or NULL in the following cases:
+   *         - no nod found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1, @see lastErrorMessage)
+   */
+  LWT_ISO_NODE* (*getNodeByFace) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* faces, int* numelems, int fields
+  );
+
+  /**
+   * Update nodes by id
+   *
+   * @param topo the topology to act upon
+   * @param nodes an array of LWT_ISO_EDGE objects with selecting id
+   *              and updating fields.
+   * @param numnodes number of nodes in the "nodes" array
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see LWT_COL_NODE_* macros
+   *
+   * @return number of nodes being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateNodesById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_NODE* nodes, int numnodes,
+      int upd_fields
+  );
+
 } LWT_BE_CALLBACKS;
 
 
@@ -773,12 +1037,17 @@ LWT_ELEMID lwt_AddIsoEdge(LWT_TOPOLOGY* topo,
  * @param start_node identifier of the starting node
  * @param end_node identifier of the ending node
  * @param geom the edge geometry
- * @return ID of the newly added edge
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (curve being simple and valid, start/end nodes
+ *                    consistency actual face containement)
+ *
+ * @return ID of the newly added edge or null on error
+ *            (@see lastErrorMessage)
  *
  */
 LWT_ELEMID lwt_AddEdgeModFace(LWT_TOPOLOGY* topo,
                               LWT_ELEMID start_node, LWT_ELEMID end_node,
-                              LWLINE *geom);
+                              LWLINE *geom, int skipChecks);
 
 /**
  * Add a new edge possibly splitting a face (replacing with two new faces)
index c6fc38d5ea1d2d79bcc8907af21ade6ba511ab23..3c2176d7c22ce0af991ac4ce106d969fa72ddf34 100644 (file)
@@ -38,6 +38,8 @@ int lwt_be_freeTopology(LWT_TOPOLOGY *topo);
 
 LWT_ISO_NODE* lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt, double dist, int* numelems, int fields, int limit);
 
+LWT_ISO_NODE* lwt_be_getNodeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, int* numelems, int fields);
+
 int lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt);
 int lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems);
 
@@ -60,6 +62,12 @@ LWT_ELEMID lwt_be_getFaceContainingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt);
 
 int lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2);
 
+LWT_ISO_NODE* lwt_be_getNodeWithinBox2D( const LWT_TOPOLOGY* topo,
+      const GBOX* box, int* numelems, int fields, int limit);
+
+LWT_ISO_EDGE* lwt_be_getEdgeWithinBox2D( const LWT_TOPOLOGY* topo,
+      const GBOX* box, int* numelems, int fields, int limit);
+
 /************************************************************************
  *
  * Internal objects
index 892244ba6753ceb2778d5788682a98153e6197c5..1bf3d3b4bfb5b50dba45d0deb838c0d9e62f3a0a 100644 (file)
@@ -8,11 +8,22 @@
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU General Public Licence. See the COPYING file.
  *
+ **********************************************************************
+ *
+ * Topology extension for liblwgeom.
+ * Initially funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8)
+ *
  **********************************************************************/
 
 
+#include "../postgis_config.h"
+
+#define POSTGIS_DEBUG_LEVEL 1
+#include "lwgeom_log.h"
+
 #include "liblwgeom_internal.h"
 #include "liblwgeom_topo_internal.h"
+#include "lwgeom_geos.h"
 
 #include <stdio.h>
 #include <errno.h>
@@ -107,6 +118,13 @@ lwt_be_freeTopology(LWT_TOPOLOGY *topo)
   CBT0(topo, freeTopology);
 }
 
+LWT_ISO_NODE*
+lwt_be_getNodeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getNodeById, ids, numelems, fields);
+}
+
 LWT_ISO_NODE*
 lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
                                double dist, int* numelems, int fields,
@@ -115,12 +133,34 @@ lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
   CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit);
 }
 
+LWT_ISO_NODE*
+lwt_be_getNodeWithinBox2D( const LWT_TOPOLOGY* topo,
+                           const GBOX* box, int* numelems, int fields,
+                           int limit )
+{
+  CBT4(topo, getNodeWithinBox2D, box, numelems, fields, limit);
+}
+
+LWT_ISO_EDGE*
+lwt_be_getEdgeWithinBox2D( const LWT_TOPOLOGY* topo,
+                           const GBOX* box, int* numelems, int fields,
+                           int limit )
+{
+  CBT4(topo, getEdgeWithinBox2D, box, numelems, fields, limit);
+}
+
 int
 lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems)
 {
   CBT2(topo, insertNodes, node, numelems);
 }
 
+static int
+lwt_be_insertFaces(LWT_TOPOLOGY* topo, LWT_ISO_FACE* face, int numelems)
+{
+  CBT2(topo, insertFaces, face, numelems);
+}
+
 LWT_ELEMID
 lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo)
 {
@@ -134,6 +174,34 @@ lwt_be_getEdgeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
   CBT3(topo, getEdgeById, ids, numelems, fields);
 }
 
+static LWT_ISO_FACE*
+lwt_be_getFaceById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getFaceById, ids, numelems, fields);
+}
+
+static LWT_ISO_EDGE*
+lwt_be_getEdgeByNode(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getEdgeByNode, ids, numelems, fields);
+}
+
+static LWT_ISO_EDGE*
+lwt_be_getEdgeByFace(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getEdgeByFace, ids, numelems, fields);
+}
+
+static LWT_ISO_NODE*
+lwt_be_getNodeByFace(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getNodeByFace, ids, numelems, fields);
+}
+
 LWT_ISO_EDGE*
 lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
                                double dist, int* numelems, int fields,
@@ -160,6 +228,42 @@ lwt_be_updateEdges(LWT_TOPOLOGY* topo,
                           exc_edge, exc_fields);
 }
 
+static int
+lwt_be_updateNodes(LWT_TOPOLOGY* topo,
+  const LWT_ISO_NODE* sel_node, int sel_fields,
+  const LWT_ISO_NODE* upd_node, int upd_fields,
+  const LWT_ISO_NODE* exc_node, int exc_fields
+)
+{
+  CBT6(topo, updateNodes, sel_node, sel_fields,
+                          upd_node, upd_fields,
+                          exc_node, exc_fields);
+}
+
+static int
+lwt_be_updateFacesById(LWT_TOPOLOGY* topo,
+  const LWT_ISO_FACE* faces, int numfaces
+)
+{
+  CBT2(topo, updateFacesById, faces, numfaces);
+}
+
+static int
+lwt_be_updateEdgesById(LWT_TOPOLOGY* topo,
+  const LWT_ISO_EDGE* edges, int numedges, int upd_fields
+)
+{
+  CBT3(topo, updateEdgesById, edges, numedges, upd_fields);
+}
+
+static int
+lwt_be_updateNodesById(LWT_TOPOLOGY* topo,
+  const LWT_ISO_NODE* nodes, int numnodes, int upd_fields
+)
+{
+  CBT3(topo, updateNodesById, nodes, numnodes, upd_fields);
+}
+
 int
 lwt_be_deleteEdges(LWT_TOPOLOGY* topo,
   const LWT_ISO_EDGE* sel_edge, int sel_fields
@@ -181,6 +285,20 @@ lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_EL
   CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2);
 }
 
+static int
+lwt_be_updateTopoGeomFaceSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2)
+{
+  CBT3(topo, updateTopoGeomFaceSplit, split_face, new_face1, new_face2);
+}
+
+static LWT_ELEMID*
+lwt_be_getRingEdges( LWT_TOPOLOGY* topo,
+                     LWT_ELEMID edge, int *numedges, int limit )
+{
+  CBT3(topo, getRingEdges, edge, numedges, limit);
+}
+
+
 /* wrappers of backend wrappers... */
 
 int
@@ -207,6 +325,22 @@ lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt)
   return exists;
 }
 
+/************************************************************************
+ *
+ * Utility functions
+ *
+ ************************************************************************/
+
+static void
+_lwt_release_edges(LWT_ISO_EDGE *edges, int num_edges)
+{
+  int i;
+  for ( i=0; i<num_edges; ++i ) {
+    lwline_release(edges[i].geom);
+  }
+  lwfree(edges);
+}
+
 /************************************************************************
  *
  * API implementation
@@ -515,7 +649,6 @@ lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
   const LWGEOM *oldedge_geom;
   const LWGEOM *newedge_geom;
   LWT_ISO_EDGE newedges[2];
-  //LWT_ISO_EDGE newedge1, newedge2;
   LWT_ISO_EDGE seledge, updedge;
   int ret;
 
@@ -665,7 +798,7 @@ lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
     return -1;
   }
 
-  /* Update TopoGeometries composition -- TODO */
+  /* Update TopoGeometries composition */
   ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedges[0].edge_id, newedges[1].edge_id);
   if ( ! ret ) {
     lwcollection_release(split_col);
@@ -678,3 +811,1323 @@ lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
   /* return new node id */
   return node.node_id;
 }
+
+/* Data structure used by AddEdgeX functions */
+typedef struct edgeend_t {
+  /* Signed identifier of next clockwise edge (+outgoing,-incoming) */
+  LWT_ELEMID nextCW;
+  /* Identifier of face between myaz and next CW edge */
+  LWT_ELEMID cwFace;
+  /* Signed identifier of next counterclockwise edge (+outgoing,-incoming) */
+  LWT_ELEMID nextCCW;
+  /* Identifier of face between myaz and next CCW edge */
+  LWT_ELEMID ccwFace;
+  int was_isolated;
+  double myaz; /* azimuth of edgeend geometry */
+} edgeend;
+
+/* 
+ * Find the first edge encountered going counterclockwise
+ * around a node, starting from the given azimuth, and take
+ * note of the face on the given azimut's side.
+ *
+ * @param topo the topology to act upon
+ * @param node the identifier of the node to analyze
+ * @param data input (myaz) / output (nextCW, nextCCW) parameter
+ * @param other edgeend, if also incident to given node (closed edge).
+ * @return number of incident edges found
+ *
+ */
+static int
+_lwt_FindAdjacentEdges( LWT_TOPOLOGY* topo, LWT_ELEMID node, edgeend *data,
+                        edgeend *other )
+{
+  LWT_ISO_EDGE *edges;
+  int numedges = 1;
+  int i;
+  double minaz, maxaz;
+  double az, azdif;
+
+  data->nextCW = data->nextCCW = 0;
+  data->cwFace = data->ccwFace = -1;
+
+  if ( other ) {
+    azdif = other->myaz - data->myaz;
+    if ( azdif < 0 ) azdif += 2 * M_PI;
+    minaz = maxaz = azdif;
+    /* TODO: set nextCW/nextCCW/cwFace/ccwFace to other->something ? */
+    LWDEBUGF(1, "Other edge end has cwFace=%d and ccwFace=%d",
+                other->cwFace, other->ccwFace);
+  } else {
+    minaz = maxaz = -1;
+  }
+
+  LWDEBUGF(1, "Looking for edges incident to node %lld "
+              "and adjacent to azimuth %g", node, data->myaz);
+
+  /* Get incident edges */
+  edges = lwt_be_getEdgeByNode( topo, &node, &numedges, LWT_COL_EDGE_ALL );
+  if ( numedges == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return 0;
+  }
+
+  LWDEBUGF(1, "getEdgeByNode returned %d edges, minaz=%g, maxaz=%g",
+              numedges, minaz, maxaz);
+
+  /* For each incident edge-end (1 or 2): */
+  for ( i = 0; i < numedges; ++i )
+  {
+    LWT_ISO_EDGE *edge = &(edges[i]);
+    LWGEOM *g = lwline_as_lwgeom(edge->geom);
+    /* NOTE: remove_repeated_points call could be replaced by
+     * some other mean to pick two distinct points for endpoints */
+    LWGEOM *cleangeom = lwgeom_remove_repeated_points( g, 0 );
+    POINT2D p1, p2;
+    POINTARRAY *pa = lwgeom_as_lwline(cleangeom)->points;
+
+    if ( pa->npoints < 2 ) {
+      lwgeom_free(cleangeom);
+      lwerror("corrupted topology: edge %lld does not have two distinct points", edge->edge_id);
+      return -1;
+    }
+
+    if ( edge->start_node == node ) {
+      getPoint2d_p(pa, 0, &p1);
+      getPoint2d_p(pa, 1, &p2);
+      LWDEBUGF(1, "edge %lld starts on node %lld, edgeend is %g,%g-%g,%g",
+                  edge->edge_id, node, p1.x, p1.y, p2.x, p2.y);
+      if ( ! azimuth_pt_pt(&p1, &p2, &az) ) {
+        lwgeom_free(cleangeom);
+        lwerror("error computing azimuth of edge %d first edgeend [%g,%g-%g,%g]",
+                edge->edge_id, p1.x, p1.y, p2.x, p2.y);
+        return -1;
+      }
+      azdif = az - data->myaz;
+      LWDEBUGF(1, "azimuth of edge %lld: %g (diff: %g)", edge->edge_id, az, azdif);
+
+      if ( azdif < 0 ) azdif += 2 * M_PI;
+      if ( minaz == -1 ) {
+        minaz = maxaz = azdif;
+        data->nextCW = data->nextCCW = edge->edge_id; /* outgoing */
+        data->cwFace = edge->face_left;
+        data->ccwFace = edge->face_right;
+        LWDEBUGF(1, "new nextCW and nextCCW edge is %lld, "
+                    "outgoing, "
+                    "with face_left %lld and face_right %lld "
+                    "(face_right is new ccwFace, face_left is new cwFace)",
+                    edge->edge_id, edge->face_left,
+                    edge->face_right);
+      } else {
+        if ( azdif < minaz ) {
+          data->nextCW = edge->edge_id; /* outgoing */
+          data->cwFace = edge->face_left;
+          LWDEBUGF(1, "new nextCW edge is %lld, "
+                      "outgoing, "
+                      "with face_left %lld and face_right %lld "
+                      "(previous had minaz=%g, face_left is new cwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, minaz);
+          minaz = azdif;
+        }
+        else if ( azdif > maxaz ) {
+          data->nextCCW = edge->edge_id; /* outgoing */
+          data->ccwFace = edge->face_right;
+          LWDEBUGF(1, "new nextCCW edge is %lld, "
+                      "outgoing, "
+                      "with face_left %lld and face_right %lld "
+                      "(previous had maxaz=%g, face_right is new ccwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, maxaz);
+          maxaz = azdif;
+        }
+      }
+    }
+
+    if ( edge->end_node == node ) {
+      getPoint2d_p(pa, pa->npoints-1, &p1);
+      getPoint2d_p(pa, pa->npoints-2, &p2);
+      LWDEBUGF(1, "edge %lld ends on node %lld, edgeend is %g,%g-%g,%g",
+                  edge->edge_id, node, p1.x, p1.y, p2.x, p2.y);
+      if ( ! azimuth_pt_pt(&p1, &p2, &az) ) {
+        lwgeom_free(cleangeom);
+        lwerror("error computing azimuth of edge %d last edgeend [%g,%g-%g,%g]",
+                edge->edge_id, p1.x, p1.y, p2.x, p2.y);
+        return -1;
+      }
+      azdif = az - data->myaz;
+      LWDEBUGF(1, "azimuth of edge %lld: %g (diff: %g)", edge->edge_id, az, azdif);
+      if ( azdif < 0 ) azdif += 2 * M_PI;
+      if ( minaz == -1 ) {
+        minaz = maxaz = azdif;
+        data->nextCW = data->nextCCW = -edge->edge_id; /* incoming */
+        data->cwFace = edge->face_right;
+        data->ccwFace = edge->face_left;
+        LWDEBUGF(1, "new nextCW and nextCCW edge is %lld, "
+                    "incoming, "
+                    "with face_left %lld and face_right %lld "
+                    "(face_right is new cwFace, face_left is new ccwFace)",
+                    edge->edge_id, edge->face_left,
+                    edge->face_right);
+      } else {
+        if ( azdif < minaz ) {
+          data->nextCW = -edge->edge_id; /* incoming */
+          data->cwFace = edge->face_right;
+          LWDEBUGF(1, "new nextCW edge is %lld, "
+                      "incoming, "
+                      "with face_left %lld and face_right %lld "
+                      "(previous had minaz=%g, face_right is new cwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, minaz);
+          minaz = azdif;
+        }
+        else if ( azdif > maxaz ) {
+          data->nextCCW = -edge->edge_id; /* incoming */
+          data->ccwFace = edge->face_left;
+          LWDEBUGF(1, "new nextCCW edge is %lld, "
+                      "outgoing from start point, "
+                      "with face_left %lld and face_right %lld "
+                      "(previous had maxaz=%g, face_left is new ccwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, maxaz);
+          maxaz = azdif;
+        }
+      }
+    }
+
+    lwgeom_free(cleangeom);
+    lwline_free(edge->geom);
+  }
+  if ( edges ) lwfree(edges); /* there might be none */
+
+  LWDEBUGF(1, "edges adjacent to azimuth %g (incident to node %lld): "
+           "CW:%lld(%g) CCW:%lld(%g)",
+           data->myaz, node, data->nextCW, minaz,
+           data->nextCCW, maxaz);
+
+  if ( numedges && data->cwFace != data->ccwFace )
+  {
+    if ( data->cwFace != -1 && data->ccwFace != -1 ) {
+      lwerror("Corrupted topology: adjacent edges %lld and %lld "
+              "bind different face (%lld and %lld)",
+              numedges, data->nextCW, data->nextCCW,
+              data->cwFace, data->ccwFace);
+      return -1;
+    }
+  }
+
+  /* Return number of incident edges found */
+  return numedges;
+}
+
+/*
+ * Get a point internal to the line and write it into the "ip"
+ * parameter
+ *
+ * return 0 on failure (line is empty or collapsed), 1 otherwise
+ */
+static int
+_lwt_GetInteriorEdgePoint(const LWLINE* edge, POINT2D* ip)
+{
+  int i;
+  POINT2D fp, lp, tp;
+  POINTARRAY *pa = edge->points;
+
+  if ( pa->npoints < 2 ) return 0; /* empty or structurally collapsed */
+
+  getPoint2d_p(pa, 0, &fp); /* save first point */
+  getPoint2d_p(pa, pa->npoints-1, &lp); /* save last point */
+  for (i=1; i<pa->npoints-1; ++i)
+  {
+    getPoint2d_p(pa, i, &tp); /* pick next point */
+    if ( p2d_same(&tp, &fp) ) continue; /* equal to startpoint */
+    if ( p2d_same(&tp, &lp) ) continue; /* equal to endpoint */
+    /* this is a good one, neither same of start nor of end point */
+    *ip = tp;
+    return 1; /* found */
+  }
+
+  /* no distinct vertex found */
+
+  /* interpolate if start point != end point */
+
+  if ( p2d_same(&fp, &lp) ) return 0; /* no distinct points in edge */
+  ip->x = fp.x + ( (lp.x - fp.x) * 0.5 );
+  ip->y = fp.y + ( (lp.y - fp.y) * 0.5 );
+
+  return 1;
+}
+
+/*
+ * Add a split face by walking on the edge side.
+ *
+ * @param topo the topology to act upon
+ * @param sedge edge id and walking side and direction
+ *              (forward,left:positive backward,right:negative)
+ * @param face the face in which the edge identifier is known to be
+ * @param mbr_only do not create a new face but update MBR of the current
+ *
+ * @return:
+ *    -1: if mbr_only was requested
+ *     0: if the edge does not form a ring
+ *    -1: if it is impossible to create a face on the requested side
+ *        ( new face on the side is the universe )
+ *    -2: error
+ *   >0 : id of newly added face
+ */
+static LWT_ELEMID
+_lwt_AddFaceSplit( LWT_TOPOLOGY* topo,
+                   LWT_ELEMID sedge, LWT_ELEMID face,
+                   int mbr_only )
+{
+  int numedges, numfaceedges, i, j;
+  int newface_outside;
+  int num_signed_edge_ids;
+  LWT_ELEMID *signed_edge_ids;
+  LWT_ELEMID *edge_ids;
+  LWT_ISO_EDGE *edges;
+  LWT_ISO_EDGE *ring_edges;
+  LWT_ISO_EDGE *forward_edges = NULL;
+  int forward_edges_count = 0;
+  LWT_ISO_EDGE *backward_edges = NULL;
+  int backward_edges_count = 0;
+
+  signed_edge_ids = lwt_be_getRingEdges(topo, sedge,
+                                        &num_signed_edge_ids, 0);
+  if ( ! signed_edge_ids ) {
+    lwerror("Backend error (no ring edges for edge %lld): %s",
+            sedge, lwt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  LWDEBUGF(1, "getRingEdges returned %d edges", num_signed_edge_ids);
+
+  /* You can't get to the other side of an edge forming a ring */
+  for (i=0; i<num_signed_edge_ids; ++i) {
+    if ( signed_edge_ids[i] == -sedge ) {
+      /* No split here */
+      LWDEBUG(1, "not a ring");
+      lwfree( signed_edge_ids );
+      return 0;
+    }
+  }
+
+  LWDEBUGF(1, "Edge %lld splitted face %lld (mbr_only:%d)",
+           sedge, face, mbr_only);
+
+  /* Construct a polygon using edges of the ring */
+  numedges = 0;
+  edge_ids = lwalloc(sizeof(LWT_ELEMID)*num_signed_edge_ids);
+  for (i=0; i<num_signed_edge_ids; ++i) {
+    int absid = llabs(signed_edge_ids[i]);
+    int found = 0;
+    /* Do not add the same edge twice */
+    for (j=0; j<numedges; ++j) {
+      if ( edge_ids[j] == absid ) {
+        found = 1;
+        break;
+      }
+    }
+    if ( ! found ) edge_ids[numedges++] = absid;
+  }
+  i = numedges;
+  ring_edges = lwt_be_getEdgeById(topo, edge_ids, &i,
+                                  LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM);
+  if ( i == -1 )
+  {
+    lwfree( signed_edge_ids );
+    /* ring_edges should be NULL */
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  else if ( i != numedges )
+  {
+    lwfree( signed_edge_ids );
+    _lwt_release_edges(ring_edges, numedges);
+    lwerror("Unexpected error: %d edges found when expecting %d", i, numedges);
+    return -2;
+  }
+
+  /* Should now build a polygon with those edges, in the order
+   * given by GetRingEdges.
+   */
+  POINTARRAY *pa = NULL;
+  for ( i=0; i<num_signed_edge_ids; ++i )
+  {
+    LWT_ELEMID eid = signed_edge_ids[i];
+    LWDEBUGF(1, "Edge %d in ring of edge %lld is edge %lld", i, sedge, eid);
+    LWT_ISO_EDGE *edge = NULL;
+    POINTARRAY *epa;
+    for ( j=0; j<numedges; ++j )
+    {
+      if ( ring_edges[j].edge_id == llabs(eid) )
+      {
+        edge = &(ring_edges[j]);
+        break;
+      }
+    }
+    if ( edge == NULL )
+    {
+      lwfree( signed_edge_ids );
+      _lwt_release_edges(ring_edges, numedges);
+      lwerror("missing edge that was found in ring edges loop");
+      return -2;
+    }
+    epa = ptarray_clone_deep(edge->geom->points);
+    if ( eid < 0 ) ptarray_reverse(epa);
+
+    pa = pa ? ptarray_merge(pa, epa) : epa;
+  }
+  POINTARRAY **points = lwalloc(sizeof(POINTARRAY*));
+  points[0] = pa;
+  /* NOTE: the ring may very well have collapsed components,
+   *       which would make it topologically invalid
+   */
+  LWPOLY* shell = lwpoly_construct(0, 0, 1, points);
+
+  int isccw = ptarray_isccw(pa);
+  LWDEBUGF(1, "Ring of edge %lld is %sclockwise", sedge, isccw ? "counter" : "");
+  const GBOX* shellbox = lwgeom_get_bbox(lwpoly_as_lwgeom(shell));
+
+  if ( face == 0 )
+  {
+    /* Edge splitted the universe face */
+    if ( ! isccw )
+    {
+      /* Face on the left side of this ring is the universe face.
+       * Next call (for the other side) should create the split face
+       */
+      LWDEBUG(1, "The left face of this clockwise ring is the universe, "
+                 "won't create a new face there");
+      return -1;
+    }
+  }
+
+  if ( mbr_only && face != 0 )
+  {
+    if ( isccw )
+    {{
+      LWT_ISO_FACE updface;
+      updface.face_id = face;
+      updface.mbr = (GBOX *)shellbox; /* const cast, we won't free it, later */
+      int ret = lwt_be_updateFacesById( topo, &updface, 1 );
+      if ( ret == -1 )
+      {
+        lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != 1 )
+      {
+        lwerror("Unexpected error: %d faces found when expecting 1", ret);
+        return -2;
+      }
+    }}
+    return -1; /* mbr only was requested */
+  }
+
+  LWT_ISO_FACE *oldface = NULL;
+  LWT_ISO_FACE newface;
+  newface.face_id = -1;
+  if ( face != 0 && ! isccw)
+  {{
+    /* Face created an hole in an outer face */
+    int nfaces = 1;
+    oldface = lwt_be_getFaceById(topo, &face, &nfaces, LWT_COL_FACE_ALL);
+    if ( nfaces == -1 )
+    {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -2;
+    }
+    if ( nfaces != 1 )
+    {
+      lwerror("Unexpected error: %d faces found when expecting 1", nfaces);
+      return -2;
+    }
+    newface.mbr = oldface->mbr;
+  }}
+  else
+  {
+    newface.mbr = (GBOX *)shellbox; /* const cast, we won't free it, later */
+  }
+
+  /* Insert the new face */
+  int ret = lwt_be_insertFaces( topo, &newface, 1 );
+  if ( ret == -1 )
+  {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  if ( ret != 1 )
+  {
+    lwerror("Unexpected error: %d faces inserted when expecting 1", ret);
+    return -2;
+  }
+  if ( oldface ) {
+    lwfree(oldface); /* NOTE: oldface.mbr is owned by shell */
+  }
+
+  /* Update side location of new face edges */
+
+  /* We want the new face to be on the left, if possible */
+  if ( face != 0 && ! isccw ) { /* ring is clockwise in a real face */
+    /* face shrinked, must update all non-contained edges and nodes */
+    LWDEBUG(1, "New face is on the outside of the ring, updating rings in former shell");
+    newface_outside = 1;
+    /* newface is outside */
+  } else {
+    LWDEBUG(1, "New face is on the inside of the ring, updating forward edges in new ring");
+    newface_outside = 0;
+    /* newface is inside */
+  }
+
+  /* Update edges bounding the old face */
+  /* (1) fetch all edges where left_face or right_face is = oldface */
+  int fields = LWT_COL_EDGE_EDGE_ID |
+               LWT_COL_EDGE_FACE_LEFT |
+               LWT_COL_EDGE_FACE_RIGHT |
+               LWT_COL_EDGE_GEOM
+               ;
+  numfaceedges = 1;
+  edges = lwt_be_getEdgeByFace( topo, &face, &numfaceedges, fields );
+  if ( numfaceedges == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  LWDEBUGF(1, "lwt_be_getEdgeByFace returned %d edges", numfaceedges);
+  GEOSGeometry *shellgg = 0;
+  const GEOSPreparedGeometry* prepshell = 0;
+  shellgg = LWGEOM2GEOS( lwpoly_as_lwgeom(shell), 0);
+  if ( ! shellgg ) {
+    lwpoly_free(shell);
+    lwfree(signed_edge_ids);
+    _lwt_release_edges(ring_edges, numedges);
+    _lwt_release_edges(edges, numfaceedges);
+    lwerror("Could not convert shell geometry to GEOS: %s", lwgeom_geos_errmsg);
+    return -2;
+  }
+  prepshell = GEOSPrepare( shellgg );
+  if ( ! prepshell ) {
+    GEOSGeom_destroy(shellgg);
+    lwpoly_free(shell);
+    lwfree(signed_edge_ids);
+    _lwt_release_edges(ring_edges, numedges);
+    _lwt_release_edges(edges, numfaceedges);
+    lwerror("Could not prepare shell geometry: %s", lwgeom_geos_errmsg);
+    return -2;
+  }
+
+  if ( numfaceedges )
+  {
+    forward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges);
+    forward_edges_count = 0;
+    backward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges);
+    backward_edges_count = 0;
+
+    /* (2) loop over the results and: */
+    for ( i=0; i<numfaceedges; ++i )
+    {
+      LWT_ISO_EDGE *e = &(edges[i]);
+      int found = 0;
+      int contains;
+      GEOSGeometry *egg;
+      LWPOINT *epgeom;
+      POINT2D ep;
+
+      /* (2.1) skip edges whose ID is in the list of boundary edges ? */
+      for ( j=0; j<num_signed_edge_ids; ++j )
+      {
+        int seid = signed_edge_ids[j];
+        if ( seid == e->edge_id )
+        {
+          /* IDEA: remove entry from signed_edge_ids ? */
+          LWDEBUGF(1, "Edge %d is a forward edge of the new ring", e->edge_id);
+          forward_edges[forward_edges_count].edge_id = e->edge_id;
+          forward_edges[forward_edges_count++].face_left = newface.face_id;
+          found++;
+          if ( found == 2 ) break;
+        }
+        else if ( -seid == e->edge_id )
+        {
+          /* IDEA: remove entry from signed_edge_ids ? */
+          LWDEBUGF(1, "Edge %d is a backward edge of the new ring", e->edge_id);
+          backward_edges[backward_edges_count].edge_id = e->edge_id;
+          backward_edges[backward_edges_count++].face_right = newface.face_id;
+          found++;
+          if ( found == 2 ) break;
+        }
+      }
+      if ( found ) continue;
+
+      /* We need to check only a single point
+       * (to avoid collapsed elements of the shell polygon
+       * giving false positive).
+       * The point but must not be an endpoint.
+       */
+      if ( ! _lwt_GetInteriorEdgePoint(e->geom, &ep) )
+      {
+        GEOSPreparedGeom_destroy(prepshell);
+        GEOSGeom_destroy(shellgg);
+        lwfree(signed_edge_ids);
+        lwpoly_free(shell);
+        lwfree(forward_edges); /* contents owned by ring_edges */
+        lwfree(backward_edges); /* contents owned by ring_edges */
+        _lwt_release_edges(ring_edges, numedges);
+        _lwt_release_edges(edges, numfaceedges);
+        lwerror("Could not find interior point for edge %d: %s",
+                e->edge_id, lwgeom_geos_errmsg);
+        return -2;
+      }
+
+      epgeom = lwpoint_make2d(0, ep.x, ep.y);
+      egg = LWGEOM2GEOS( lwpoint_as_lwgeom(epgeom) , 0);
+      lwpoint_release(epgeom);
+      if ( ! egg ) {
+        GEOSPreparedGeom_destroy(prepshell);
+        GEOSGeom_destroy(shellgg);
+        lwfree(signed_edge_ids);
+        lwpoly_free(shell);
+        lwfree(forward_edges); /* contents owned by ring_edges */
+        lwfree(backward_edges); /* contents owned by ring_edges */
+        _lwt_release_edges(ring_edges, numedges);
+        _lwt_release_edges(edges, numfaceedges);
+        lwerror("Could not convert edge geometry to GEOS: %s",
+                lwgeom_geos_errmsg);
+        return -2;
+      }
+      /* IDEA: can be optimized by computing this on our side rather
+       *       than on GEOS (saves conversion of big edges) */
+      /* IDEA: check that bounding box shortcut is taken, or use
+       *       shellbox to do it here */
+      contains = GEOSPreparedContains( prepshell, egg );
+      GEOSGeom_destroy(egg);
+      if ( contains == 2 )
+      {
+        GEOSPreparedGeom_destroy(prepshell);
+        GEOSGeom_destroy(shellgg);
+        lwfree(signed_edge_ids);
+        lwpoly_free(shell);
+        lwfree(forward_edges); /* contents owned by ring_edges */
+        lwfree(backward_edges); /* contents owned by ring_edges */
+        _lwt_release_edges(ring_edges, numedges);
+        _lwt_release_edges(edges, numfaceedges);
+        lwerror("GEOS exception on PreparedContains: %s", lwgeom_geos_errmsg);
+        return -2;
+      }
+      LWDEBUGF(1, "Edge %d %scontained in new ring", e->edge_id,
+                  (contains?"":"not "));
+
+      /* (2.2) skip edges (NOT, if newface_outside) contained in shell */
+      if ( newface_outside )
+      {
+        if ( contains )
+        {
+          LWDEBUGF(1, "Edge %d contained in an hole of the new face",
+                      e->edge_id);
+          continue;
+        }
+      }
+      else
+      {
+        if ( ! contains )
+        {
+          LWDEBUGF(1, "Edge %d not contained in the face shell",
+                      e->edge_id);
+          continue;
+        }
+      }
+
+      /* (2.3) push to forward_edges if left_face = oface */
+      if ( e->face_left == face )
+      {
+        LWDEBUGF(1, "Edge %d has new face on the left side", e->edge_id);
+        forward_edges[forward_edges_count].edge_id = e->edge_id;
+        forward_edges[forward_edges_count++].face_left = newface.face_id;
+      }
+
+      /* (2.4) push to backward_edges if right_face = oface */
+      if ( e->face_right == face )
+      {
+        LWDEBUGF(1, "Edge %d has new face on the right side", e->edge_id);
+        backward_edges[backward_edges_count].edge_id = e->edge_id;
+        backward_edges[backward_edges_count++].face_right = newface.face_id;
+      }
+    }
+
+    /* Update forward edges */
+    if ( forward_edges_count )
+    {
+      ret = lwt_be_updateEdgesById(topo, forward_edges,
+                                   forward_edges_count,
+                                   LWT_COL_EDGE_FACE_LEFT);
+      if ( ret == -1 )
+      {
+        lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != forward_edges_count )
+      {
+        lwerror("Unexpected error: %d edges updated when expecting %d",
+                ret, forward_edges_count);
+        return -2;
+      }
+    }
+
+    /* Update backward edges */
+    if ( backward_edges_count )
+    {
+      ret = lwt_be_updateEdgesById(topo, backward_edges,
+                                   backward_edges_count,
+                                   LWT_COL_EDGE_FACE_RIGHT);
+      if ( ret == -1 )
+      {
+        lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != backward_edges_count )
+      {
+        lwerror("Unexpected error: %d edges updated when expecting %d",
+                ret, backward_edges_count);
+        return -2;
+      }
+    }
+
+    lwfree(forward_edges);
+    lwfree(backward_edges);
+
+  }
+
+  _lwt_release_edges(ring_edges, numedges);
+  _lwt_release_edges(edges, numfaceedges);
+
+  /* Update isolated nodes which are now in new face */
+  int numisonodes = 1;
+  fields = LWT_COL_NODE_NODE_ID | LWT_COL_NODE_GEOM;
+  LWT_ISO_NODE *nodes = lwt_be_getNodeByFace(topo, &face,
+                                             &numisonodes, fields);
+  if ( numisonodes == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  if ( numisonodes ) {
+    LWT_ISO_NODE *updated_nodes = lwalloc(sizeof(LWT_ISO_NODE)*numisonodes);
+    int nodes_to_update = 0;
+    for (i=0; i<numisonodes; ++i)
+    {
+      LWT_ISO_NODE *n = &(nodes[i]);
+      GEOSGeometry *ngg;
+      ngg = LWGEOM2GEOS( lwpoint_as_lwgeom(n->geom), 0 );
+      int contains;
+      if ( ! ngg ) {
+        if ( prepshell ) GEOSPreparedGeom_destroy(prepshell);
+        if ( shellgg ) GEOSGeom_destroy(shellgg);
+        lwfree(signed_edge_ids);
+        lwpoly_free(shell);
+        lwerror("Could not convert node geometry to GEOS: %s",
+                lwgeom_geos_errmsg);
+        return -2;
+      }
+      contains = GEOSPreparedContains( prepshell, ngg );
+      GEOSGeom_destroy(ngg);
+      if ( contains == 2 )
+      {
+        if ( prepshell ) GEOSPreparedGeom_destroy(prepshell);
+        if ( shellgg ) GEOSGeom_destroy(shellgg);
+        lwfree(signed_edge_ids);
+        lwpoly_free(shell);
+        lwerror("GEOS exception on PreparedContains: %s", lwgeom_geos_errmsg);
+        return -2;
+      }
+      LWDEBUGF(1, "Node %d is %scontained in new ring, newface is %s",
+                  n->node_id, contains ? "" : "not ",
+                  newface_outside ? "outside" : "inside" );
+      if ( newface_outside )
+      {
+        if ( contains )
+        {
+          LWDEBUGF(1, "Node %d contained in an hole of the new face",
+                      n->node_id);
+          continue;
+        }
+      }
+      else
+      {
+        if ( ! contains )
+        {
+          LWDEBUGF(1, "Node %d not contained in the face shell",
+                      n->node_id);
+          continue;
+        }
+      }
+      lwpoint_release(n->geom);
+      updated_nodes[nodes_to_update].node_id = n->node_id;
+      updated_nodes[nodes_to_update++].containing_face =
+                                       newface.face_id;
+      LWDEBUGF(1, "Node %d will be updated", n->node_id);
+    }
+    lwfree(nodes);
+    if ( nodes_to_update )
+    {
+      int ret = lwt_be_updateNodesById(topo, updated_nodes,
+                                       nodes_to_update,
+                                       LWT_COL_NODE_CONTAINING_FACE);
+      if ( ret == -1 ) {
+        lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+    }
+    lwfree(updated_nodes);
+  }
+
+  GEOSPreparedGeom_destroy(prepshell);
+  GEOSGeom_destroy(shellgg);
+  lwfree(signed_edge_ids);
+  lwpoly_free(shell);
+
+  return newface.face_id;
+}
+
+LWT_ELEMID
+lwt_AddEdgeModFace( LWT_TOPOLOGY* topo,
+                    LWT_ELEMID start_node, LWT_ELEMID end_node,
+                    LWLINE *geom, int skipChecks )
+{
+  LWT_ISO_EDGE newedge;
+  LWGEOM *cleangeom;
+  edgeend span; /* start point analisys */
+  edgeend epan; /* end point analisys */
+  POINT2D p1, pn, p2;
+  POINTARRAY *pa;
+  LWT_ELEMID *node_ids;
+  const LWPOINT *start_node_geom = NULL;
+  const LWPOINT *end_node_geom = NULL;
+  int num_nodes;
+  int num_edges;
+  LWT_ISO_NODE *endpoints;
+  int i;
+  int prev_left;
+  int prev_right;
+  LWT_ISO_EDGE seledge;
+  LWT_ISO_EDGE updedge;
+
+  initGEOS(lwnotice, lwgeom_geos_error);
+
+  if ( ! skipChecks )
+  {
+    /* curve must be simple */
+    if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) )
+    {
+      lwerror("SQL/MM Spatial exception - curve not simple");
+      return -1;
+    }
+  }
+
+  newedge.start_node = start_node;
+  newedge.end_node = end_node;
+  newedge.geom = geom;
+  newedge.face_left = -1;
+  newedge.face_right = -1;
+  cleangeom = lwgeom_remove_repeated_points( lwline_as_lwgeom(geom), 0 );
+
+  pa = lwgeom_as_lwline(cleangeom)->points;
+  if ( pa->npoints < 2 ) {
+    lwerror("Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+
+  /* Initialize endpoint info (some of that ) */
+  span.cwFace = span.ccwFace =
+  epan.cwFace = epan.ccwFace = -1;
+
+  /* Compute azimut of first edge end on start node */
+  getPoint2d_p(pa, 0, &p1);
+  getPoint2d_p(pa, 1, &pn);
+  if ( p2d_same(&p1, &pn) ) {
+    /* Can still happen, for 2-point lines */
+    lwerror("Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+  if ( ! azimuth_pt_pt(&p1, &pn, &span.myaz) ) {
+    lwerror("error computing azimuth of first edgeend [%g,%g-%g,%g]",
+            p1.x, p1.y, pn.x, pn.y);
+    return -1;
+  }
+  lwnotice("edge's start node is %g,%g", p1.x, p1.y);
+
+  /* Compute azimuth of last edge end on end node */
+  getPoint2d_p(pa, pa->npoints-1, &p2);
+  getPoint2d_p(pa, pa->npoints-2, &pn);
+  if ( ! azimuth_pt_pt(&p2, &pn, &epan.myaz) ) {
+    lwerror("error computing azimuth of last edgeend [%g,%g-%g,%g]",
+            p2.x, p2.y, pn.x, pn.y);
+    return -1;
+  }
+  lwnotice("edge's end node is %g,%g", p2.x, p2.y);
+
+  /*
+   * Check endpoints existance, match with Curve geometry
+   * and get face information (if any)
+   */
+
+  if ( start_node != end_node ) {
+    num_nodes = 2;
+    node_ids = lwalloc(sizeof(LWT_ELEMID)*num_nodes);
+    node_ids[0] = start_node;
+    node_ids[1] = end_node;
+  } else {
+    num_nodes = 1;
+    node_ids = lwalloc(sizeof(LWT_ELEMID)*num_nodes);
+    node_ids[0] = start_node;
+  }
+
+  endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes, LWT_COL_NODE_ALL );
+  if ( ! endpoints ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<num_nodes; ++i )
+  {
+    LWT_ISO_NODE* node = &(endpoints[i]);
+    if ( node->containing_face != -1 )
+    {
+      if ( newedge.face_left == -1 )
+      {
+        newedge.face_left = newedge.face_right = node->containing_face;
+      }
+      else if ( newedge.face_left != node->containing_face )
+      {
+        lwerror("SQL/MM Spatial exception - geometry crosses an edge"
+                " (endnodes in faces %lld and %lld)",
+                newedge.face_left, node->containing_face);
+      }
+    }
+
+    lwnotice("Node %d, with geom %p (looking for %d and %d)", node->node_id, node->geom, start_node, end_node);
+    if ( node->node_id == start_node ) {
+      start_node_geom = node->geom;
+    } 
+    if ( node->node_id == end_node ) {
+      end_node_geom = node->geom;
+    } 
+  }
+
+  if ( ! skipChecks )
+  {
+    if ( ! start_node_geom )
+    {
+      lwerror("SQL/MM Spatial exception - non-existent node");
+      return -1;
+    }
+    else
+    {
+      pa = start_node_geom->point;
+      getPoint2d_p(pa, 0, &pn);
+      if ( ! p2d_same(&pn, &p1) )
+      {
+        lwerror("SQL/MM Spatial exception"
+                " - start node not geometry start point."
+                //" - start node not geometry start point (%g,%g != %g,%g).", pn.x, pn.y, p1.x, p1.y
+        );
+        return -1;
+      }
+    }
+
+    if ( ! end_node_geom )
+    {
+      lwerror("SQL/MM Spatial exception - non-existent node");
+      return -1;
+    }
+    else
+    {
+      pa = end_node_geom->point;
+      getPoint2d_p(pa, 0, &pn);
+      if ( ! p2d_same(&pn, &p2) )
+      {
+        lwerror("SQL/MM Spatial exception"
+                " - end node not geometry end point."
+                //" - end node not geometry end point (%g,%g != %g,%g).", pn.x, pn.y, p2.x, p2.y
+        );
+        return -1;
+      }
+    }
+
+    LWT_ISO_EDGE *edges;
+    LWT_ISO_NODE *nodes;
+    const GBOX *edgebox;
+    GEOSGeometry *edgegg;
+    const GEOSPreparedGeometry* prepared_edge;
+
+    edgegg = LWGEOM2GEOS( lwline_as_lwgeom(geom), 0);
+    if ( ! edgegg ) {
+      lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
+      return -1;
+    }
+    prepared_edge = GEOSPrepare( edgegg );
+    if ( ! prepared_edge ) {
+      lwerror("Could not prepare edge geometry: %s", lwgeom_geos_errmsg);
+      return -1;
+    }
+    edgebox = lwgeom_get_bbox( lwline_as_lwgeom(geom) );
+
+    /* loop over each node within the edge's gbox */
+    nodes = lwt_be_getNodeWithinBox2D( topo, edgebox, &num_nodes, LWT_COL_NODE_ALL, 0 );
+    lwnotice("lwt_be_getNodeWithinBox2D returned %d nodes", num_nodes);
+    if ( num_nodes == -1 ) {
+      GEOSPreparedGeom_destroy(prepared_edge);
+      GEOSGeom_destroy(edgegg);
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    for ( i=0; i<num_nodes; ++i )
+    {
+      LWT_ISO_NODE* node = &(nodes[i]);
+      GEOSGeometry *nodegg;
+      int contains;
+      if ( node->node_id == start_node ) continue;
+      if ( node->node_id == end_node ) continue;
+      /* check if the edge contains this node (not on boundary) */
+      nodegg = LWGEOM2GEOS( lwpoint_as_lwgeom(node->geom) , 0);
+      /* ST_RelateMatch(rec.relate, 'T********') */
+      contains = GEOSPreparedContains( prepared_edge, nodegg );
+                 GEOSGeom_destroy(nodegg);
+           if (contains == 2)
+      {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        lwfree(nodes);
+        lwerror("GEOS exception on PreparedContains: %s", lwgeom_geos_errmsg);
+        return -1;
+      }
+      if ( contains )
+      {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        lwfree(nodes);
+        lwerror("SQL/MM Spatial exception - geometry crosses a node");
+        return -1;
+      }
+    }
+    if ( nodes ) lwfree(nodes); /* may be NULL if num_nodes == 0 */
+
+    /* loop over each edge within the edge's gbox */
+    edges = lwt_be_getEdgeWithinBox2D( topo, edgebox, &num_edges, LWT_COL_EDGE_ALL, 0 );
+    LWDEBUGF(1, "lwt_be_getEdgeWithinBox2D returned %d edges", num_edges);
+    if ( num_edges == -1 ) {
+      GEOSPreparedGeom_destroy(prepared_edge);
+      GEOSGeom_destroy(edgegg);
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    for ( i=0; i<num_edges; ++i )
+    {
+      LWT_ISO_EDGE* edge = &(edges[i]);
+      GEOSGeometry *eegg;
+      char *relate;
+      int match;
+
+      if ( ! edge->geom ) {
+        lwerror("Edge %d has NULL geometry!", edge->edge_id);
+        return -1;
+      }
+
+      eegg = LWGEOM2GEOS( lwline_as_lwgeom(edge->geom), 0 );
+      if ( ! eegg ) {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
+        return -1;
+      }
+
+      LWDEBUGF(2, "Edge %d converted to GEOS", edge->edge_id);
+
+      /* check if the edge crosses our edge (not boundary-boundary) */
+
+      relate = GEOSRelateBoundaryNodeRule(eegg, edgegg, 2);
+      if ( ! relate ) {
+        GEOSGeom_destroy(eegg);
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        lwerror("GEOSRelateBoundaryNodeRule error: %s", lwgeom_geos_errmsg);
+        return -1;
+      }
+
+      LWDEBUGF(2, "Edge %d relate pattern is %s", edge->edge_id, relate);
+
+      match = GEOSRelatePatternMatch(relate, "F********");
+      if ( match ) {
+        if ( match == 2 ) {
+          GEOSPreparedGeom_destroy(prepared_edge);
+          GEOSGeom_destroy(edgegg);
+          GEOSGeom_destroy(eegg);
+          GEOSFree(relate);
+          lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg);
+          return -1;
+        }
+        else continue; /* no interior intersection */
+      }
+
+      match = GEOSRelatePatternMatch(relate, "1FFF*FFF2");
+      if ( match ) {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        GEOSGeom_destroy(eegg);
+        GEOSFree(relate);
+        if ( match == 2 ) {
+          lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg);
+        } else {
+          lwerror("SQL/MM Spatial exception - coincident edge %lld",
+                  edge->edge_id);
+        }
+        return -1;
+      }
+
+      match = GEOSRelatePatternMatch(relate, "1********");
+      if ( match ) {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        GEOSGeom_destroy(eegg);
+        GEOSFree(relate);
+        if ( match == 2 ) {
+          lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg);
+        } else {
+          lwerror("Spatial exception - geometry intersects edge %lld",
+                  edge->edge_id);
+        }
+        return -1;
+      }
+
+      match = GEOSRelatePatternMatch(relate, "T********");
+      if ( match ) {
+        GEOSPreparedGeom_destroy(prepared_edge);
+        GEOSGeom_destroy(edgegg);
+        GEOSGeom_destroy(eegg);
+        GEOSFree(relate);
+        if ( match == 2 ) {
+          lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg);
+        } else {
+          lwerror("SQL/MM Spatial exception - geometry crosses edge %lld",
+                  edge->edge_id);
+        }
+        return -1;
+      }
+
+      LWDEBUGF(2, "Edge %d analisys completed, it does no harm", edge->edge_id);
+
+      GEOSFree(relate);
+      GEOSGeom_destroy(eegg);
+    }
+    if ( edges ) lwfree(edges); /* would be NULL if num_edges was 0 */
+
+    GEOSPreparedGeom_destroy(prepared_edge);
+    GEOSGeom_destroy(edgegg);
+
+  } /* ! skipChecks */
+
+  /*
+   * All checks passed, time to prepare the new edge
+   */
+
+  newedge.edge_id = lwt_be_getNextEdgeId( topo );
+  if ( newedge.edge_id == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Find adjacent edges to each endpoint */
+  int isclosed = start_node == end_node;
+  int found;
+  found = _lwt_FindAdjacentEdges( topo, start_node, &span,
+                                  isclosed ? &epan : NULL );
+  if ( found ) {
+    span.was_isolated = 0;
+    newedge.next_right = span.nextCW ? span.nextCW : -newedge.edge_id;
+    prev_left = span.nextCCW ? -span.nextCCW : newedge.edge_id;
+    LWDEBUGF(1, "New edge %d is connected on start node, "
+                "next_right is %d, prev_left is %d",
+                newedge.edge_id, newedge.next_right, prev_left);
+    if ( newedge.face_right == -1 ) {
+      newedge.face_right = span.cwFace;
+    }
+    if ( newedge.face_left == -1 ) {
+      newedge.face_left = span.ccwFace;
+    }
+  } else {
+    span.was_isolated = 1;
+    newedge.next_right = isclosed ? -newedge.edge_id : newedge.edge_id;
+    prev_left = isclosed ? newedge.edge_id : -newedge.edge_id;
+    LWDEBUGF(1, "New edge %d is isolated on start node, "
+                "next_right is %d, prev_left is %d",
+                newedge.edge_id, newedge.next_right, prev_left);
+  }
+
+  found = _lwt_FindAdjacentEdges( topo, end_node, &epan,
+                                  isclosed ? &span : NULL );
+  if ( found ) {
+    epan.was_isolated = 0;
+    newedge.next_left = epan.nextCW ? epan.nextCW : newedge.edge_id;
+    prev_right = epan.nextCCW ? -epan.nextCCW : -newedge.edge_id;
+    LWDEBUGF(1, "New edge %d is connected on end node, "
+                "next_left is %d, prev_right is %d",
+                newedge.edge_id, newedge.next_left, prev_right);
+    if ( newedge.face_right == -1 ) {
+      newedge.face_right = span.ccwFace;
+    }
+    if ( newedge.face_left == -1 ) {
+      newedge.face_left = span.cwFace;
+    }
+  } else {
+    epan.was_isolated = 1;
+    newedge.next_left = isclosed ? newedge.edge_id : -newedge.edge_id;
+    prev_right = isclosed ? -newedge.edge_id : newedge.edge_id;
+    LWDEBUGF(1, "New edge %d is isolated on end node, "
+                "next_left is %d, prev_right is %d",
+                newedge.edge_id, newedge.next_left, prev_right);
+  }
+
+  /*
+   * If we don't have faces setup by now we must have encountered
+   * a malformed topology (no containing_face on isolated nodes, no
+   * left/right faces on adjacent edges or mismatching values)
+   */
+  if ( newedge.face_left != newedge.face_right )
+  {
+    lwerror("Left(%lld)/right(%lld) faces mismatch: invalid topology ?",
+      newedge.face_left, newedge.face_right);
+    return -1;
+  }
+  else if ( newedge.face_left == -1 )
+  {
+    lwerror("Could not derive edge face from linked primitives: invalid topology ?");
+    return -1;
+  }
+
+  /*
+   * Insert the new edge, and update all linking
+   */
+
+  int ret = lwt_be_insertEdges(topo, &newedge, 1);
+  if ( ret == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    lwerror("Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  int updfields;
+
+  /* Link prev_left to us
+   * (if it's not us already) */
+  if ( llabs(prev_left) != newedge.edge_id )
+  {
+    if ( prev_left > 0 )
+    {
+      /* its next_left_edge is us */
+      updfields = LWT_COL_EDGE_NEXT_LEFT;
+      updedge.next_left = newedge.edge_id;
+      seledge.edge_id = prev_left;
+    }
+    else
+    {
+      /* its next_right_edge is us */
+      updfields = LWT_COL_EDGE_NEXT_RIGHT;
+      updedge.next_right = newedge.edge_id;
+      seledge.edge_id = -prev_left;
+    }
+
+    ret = lwt_be_updateEdges(topo,
+        &seledge, LWT_COL_EDGE_EDGE_ID,
+        &updedge, updfields,
+        NULL, 0);
+    if ( ret == -1 ) {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* Link prev_right to us 
+   * (if it's not us already) */
+  if ( llabs(prev_right) != newedge.edge_id )
+  {
+    if ( prev_right > 0 )
+    {
+      /* its next_left_edge is -us */
+      updfields = LWT_COL_EDGE_NEXT_LEFT;
+      updedge.next_left = -newedge.edge_id;
+      seledge.edge_id = prev_right;
+    }
+    else
+    {
+      /* its next_right_edge is -us */
+      updfields = LWT_COL_EDGE_NEXT_RIGHT;
+      updedge.next_right = -newedge.edge_id;
+      seledge.edge_id = -prev_right;
+    }
+
+    ret = lwt_be_updateEdges(topo,
+        &seledge, LWT_COL_EDGE_EDGE_ID,
+        &updedge, updfields,
+        NULL, 0);
+    if ( ret == -1 ) {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* NOT IN THE SPECS...
+   * set containing_face = null for start_node and end_node
+   * if they where isolated
+   *
+   */
+  LWT_ISO_NODE updnode, selnode;
+  updnode.containing_face = -1;
+  if ( span.was_isolated )
+  {
+    selnode.node_id = start_node;
+    ret = lwt_be_updateNodes(topo,
+        &selnode, LWT_COL_NODE_NODE_ID,
+        &updnode, LWT_COL_NODE_CONTAINING_FACE,
+        NULL, 0);
+    if ( ret == -1 ) {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+  if ( epan.was_isolated )
+  {
+    selnode.node_id = end_node;
+    ret = lwt_be_updateNodes(topo,
+        &selnode, LWT_COL_NODE_NODE_ID,
+        &updnode, LWT_COL_NODE_CONTAINING_FACE,
+        NULL, 0);
+    if ( ret == -1 ) {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* Check face splitting */
+  int newface = _lwt_AddFaceSplit( topo, newedge.edge_id,
+                                   newedge.face_left, 0 );
+  if ( newface == 0 ) return newedge.edge_id; /* no split */
+
+  if ( newface < 0 )
+  {
+    /* face on the left is the universe face */
+    /* must be forming a maximal ring in universal face */
+    newface = _lwt_AddFaceSplit( topo, -newedge.edge_id,
+                                 newedge.face_left, 0 );
+    if ( newface < 0 ) return newedge.edge_id; /* no split */
+  }
+  else
+  {
+    _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 1 );
+  }
+
+  /*
+   * Update topogeometries, if needed
+   */
+  ret = lwt_be_updateTopoGeomFaceSplit(topo, newedge.face_left,
+                                       newface, -1);
+
+  return newedge.edge_id;
+}
index 40cdf97381561ac97e4d295fa58300e7b44e186e..382bf2d39da60c4471f76a166cfd82adf4a0c7fa 100644 (file)
@@ -20,7 +20,9 @@
 
 #include "../postgis_config.h"
 
+#include "liblwgeom_internal.h" /* for gbox_clone */
 #include "liblwgeom_topo.h"
+#define POSTGIS_DEBUG_LEVEL 1
 #include "lwgeom_log.h"
 #include "lwgeom_pg.h"
 
@@ -45,7 +47,18 @@ LWT_BE_IFACE* be_iface;
 #define MAXERRLEN 256
 struct LWT_BE_DATA_T {
   char lastErrorMsg[MAXERRLEN];
+  /*
+   * This flag will need to be set to false
+   * at top-level function enter and set true
+   * whenever an callback changes the data
+   * in the database.
+   * It will be used by SPI_execute calls to
+   * make sure to see any data change occurring
+   * doring operations.
+   */
+  bool data_changed;
 };
+
 LWT_BE_DATA be_data;
 
 struct LWT_BE_TOPOLOGY_T {
@@ -85,7 +98,7 @@ cb_lastErrorMessage(const LWT_BE_DATA* be)
 static LWT_BE_TOPOLOGY*
 cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
 {
-       int spi_result;
+  int spi_result;
   StringInfoData sqldata;
   StringInfo sql = &sqldata;
   Datum dat;
@@ -93,9 +106,9 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
   LWT_BE_TOPOLOGY *topo;
 
   initStringInfo(sql);
-  appendStringInfo(sql, "SELECT * FROM topology.topology "
+  appendStringInfo(sql, "SELECT id,srid FROM topology.topology "
                         "WHERE name = '%s'", name);
-  spi_result = SPI_execute(sql->data, true, 0);
+  spi_result = SPI_execute(sql->data, !be->data_changed, 0);
   if ( spi_result != SPI_OK_SELECT ) {
     pfree(sqldata.data);
                cberror(be, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
@@ -127,10 +140,17 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
   }
   topo->id = DatumGetInt32(dat);
 
-  topo->srid = 0; /* needed ? */
+  dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull);
+  if ( isnull ) {
+               cberror(be, "Topology '%s' has null SRID", name);
+    return NULL;
+  }
+  topo->srid = DatumGetInt32(dat);
+
   topo->precision = 0; /* needed ? */
 
-  lwpgnotice("cb_loadTopologyByName: topo '%s' has id %d", name, topo->id);
+  POSTGIS_DEBUGF(1, "cb_loadTopologyByName: topo '%s' has id %d, srid %d",
+             name, topo->id, topo->srid);
 
   return topo;
 }
@@ -183,33 +203,58 @@ addEdgeFields(StringInfo str, int fields, int fullEdgeData)
   }
 }
 
-/* Add edge values for an insert, in text form */
+/* Add edge values in text form, include the parens */
 static void
-addEdgeValues(StringInfo str, LWT_ISO_EDGE *edge, int fullEdgeData)
+addEdgeValues(StringInfo str, const LWT_ISO_EDGE *edge, int fields, int fullEdgeData)
 {
   size_t hexewkb_size;
   char *hexewkb;
+  const char *sep = "";
 
-  if ( edge->edge_id != -1 ) appendStringInfo(str, "(%lld", edge->edge_id);
-  else appendStringInfoString(str, "(DEFAULT");
-
-  appendStringInfo(str, ",%lld", edge->start_node);
-  appendStringInfo(str, ",%lld", edge->end_node);
-  appendStringInfo(str, ",%lld", edge->face_left);
-  appendStringInfo(str, ",%lld", edge->face_right);
-  appendStringInfo(str, ",%lld", edge->next_left);
-  if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_left));
-  appendStringInfo(str, ",%lld", edge->next_right);
-  if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_right));
-
-  if ( edge->geom ) {
-    hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom),
-                                WKB_EXTENDED, &hexewkb_size);
-    appendStringInfo(str, ",'%s'::geometry)", hexewkb);
-    lwfree(hexewkb);
-  } else {
-    appendStringInfoString(str, ",null)");
+  appendStringInfoChar(str, '(');
+  if ( fields & LWT_COL_EDGE_EDGE_ID ) {
+    if ( edge->edge_id != -1 ) appendStringInfo(str, "%lld", edge->edge_id);
+    else appendStringInfoString(str, "DEFAULT");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_START_NODE ) {
+    appendStringInfo(str, "%s%lld", sep, edge->start_node);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_END_NODE ) {
+    appendStringInfo(str, "%s%lld", sep, edge->end_node);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
+    appendStringInfo(str, "%s%lld", sep, edge->face_left);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
+    appendStringInfo(str, "%s%lld", sep, edge->face_right);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
+    appendStringInfo(str, "%s%lld", sep, edge->next_left);
+    if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_left));
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
+    appendStringInfo(str, "%s%lld", sep, edge->next_right);
+    if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_right));
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_GEOM )
+  {
+    if ( edge->geom ) {
+      hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom),
+                                  WKB_EXTENDED, &hexewkb_size);
+      appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
+      lwfree(hexewkb);
+    } else {
+      appendStringInfo(str, "%snull", sep);
+    }
   }
+  appendStringInfoChar(str, ')');
 }
 
 enum UpdateType {
@@ -297,6 +342,56 @@ addEdgeUpdate(StringInfo str, const LWT_ISO_EDGE* edge, int fields,
   }
 }
 
+static void
+addNodeUpdate(StringInfo str, const LWT_ISO_NODE* node, int fields,
+              int fullNodeData, enum UpdateType updType)
+{
+  const char *sep = "";
+  const char *sep1;
+  const char *op;
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  switch (updType)
+  {
+    case updSet:
+      op = "=";
+      sep1 = ",";
+      break;
+    case updSel:
+      op = "=";
+      sep1 = " AND ";
+      break;
+    case updNot:
+    default:
+      op = "!=";
+      sep1 = " AND ";
+      break;
+  }
+
+  if ( fields & LWT_COL_NODE_NODE_ID ) {
+    appendStringInfoString(str, "node_id ");
+    appendStringInfo(str, "%s %lld", op, node->node_id);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_NODE_CONTAINING_FACE ) {
+    appendStringInfo(str, "%scontaining_face %s", sep, op);
+    if ( node->containing_face != -1 ) {
+      appendStringInfo(str, "%lld", node->containing_face);
+    } else {
+      appendStringInfoString(str, "NULL");
+    }
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_NODE_GEOM ) {
+    appendStringInfo(str, "%sgeom", sep);
+    hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
+                                WKB_EXTENDED, &hexewkb_size);
+    appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
+    lwfree(hexewkb);
+  }
+}
+
 static void
 addNodeFields(StringInfo str, int fields)
 {
@@ -315,25 +410,68 @@ addNodeFields(StringInfo str, int fields)
   }
 }
 
+static void
+addFaceFields(StringInfo str, int fields)
+{
+  const char *sep = "";
+
+  if ( fields & LWT_COL_FACE_FACE_ID ) {
+    appendStringInfoString(str, "face_id");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_FACE_MBR ) {
+    appendStringInfo(str, "%smbr", sep);
+    sep = ",";
+  }
+}
+
 /* Add node values for an insert, in text form */
 static void
-addNodeValues(StringInfo str, LWT_ISO_NODE *node)
+addNodeValues(StringInfo str, const LWT_ISO_NODE *node, int fields)
 {
   size_t hexewkb_size;
   char *hexewkb;
+  const char *sep = "";
 
-  if ( node->node_id != -1 ) appendStringInfo(str, "(%lld", node->node_id);
-  else appendStringInfoString(str, "(DEFAULT");
+  appendStringInfoChar(str, '(');
+
+  if ( fields & LWT_COL_NODE_NODE_ID ) {
+    if ( node->node_id != -1 ) appendStringInfo(str, "%lld", node->node_id);
+    else appendStringInfoString(str, "DEFAULT");
+    sep = ",";
+  }
 
-  if ( node->containing_face != -1 )
-    appendStringInfo(str, ",%lld", node->containing_face);
-  else appendStringInfoString(str, ",null");
+  if ( fields & LWT_COL_NODE_CONTAINING_FACE ) {
+    if ( node->containing_face != -1 )
+      appendStringInfo(str, "%s%lld", sep, node->containing_face);
+    else appendStringInfo(str, "%snull", sep);
+  }
 
-  if ( node->geom ) {
-    hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
-                                WKB_EXTENDED, &hexewkb_size);
-    appendStringInfo(str, ",'%s'::geometry)", hexewkb);
-    lwfree(hexewkb);
+  if ( fields & LWT_COL_NODE_GEOM ) {
+    if ( node->geom ) {
+      hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
+                                  WKB_EXTENDED, &hexewkb_size);
+      appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb);
+      lwfree(hexewkb);
+    } else {
+      appendStringInfo(str, "%snull", sep);
+    }
+  }
+
+  appendStringInfoChar(str, ')');
+}
+
+/* Add face values for an insert, in text form */
+static void
+addFaceValues(StringInfo str, LWT_ISO_FACE *face, int srid)
+{
+  if ( face->face_id != -1 ) appendStringInfo(str, "(%lld", face->face_id);
+  else appendStringInfoString(str, "(DEFAULT");
+
+  if ( face->mbr ) {
+    appendStringInfo(str, ",ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d))",
+              face->mbr->xmin, face->mbr->ymin,
+              face->mbr->xmax, face->mbr->ymax, srid);
   } else {
     appendStringInfoString(str, ",null)");
   }
@@ -344,36 +482,101 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
 {
   bool isnull;
   Datum dat;
+  int val;
   GSERIALIZED *geom;
   int colno = 0;
 
+  POSTGIS_DEBUGF(2, "fillEdgeFields: got %d atts and fields %x",
+                    rowdesc->natts, fields);
+
   if ( fields & LWT_COL_EDGE_EDGE_ID ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->edge_id = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL edge_id");
+      edge->edge_id = -1;
+    }
+    val = DatumGetInt32(dat);
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (edge_id)"
+                      " has int32 val of %d",
+                      colno, val);
+    edge->edge_id = val;
+
   }
   if ( fields & LWT_COL_EDGE_START_NODE ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->start_node = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL start_node");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->start_node = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (start_node)"
+                      " has int32 val of %d", colno, val);
   }
   if ( fields & LWT_COL_EDGE_END_NODE ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->end_node = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL end_node");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->end_node = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (end_node)"
+                      " has int32 val of %d", colno, val);
   }
   if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->face_left = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL face_left");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->face_left = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_left)"
+                      " has int32 val of %d", colno, val);
+  }
+#if POSTGIS_DEBUG_LEVEL > 1
+  else {
+    edge->face_left = 6767; /* debugging */
   }
+#endif
   if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->face_right = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL face_right");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->face_right = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_right)"
+                      " has int32 val of %d", colno, val);
+  }
+#if POSTGIS_DEBUG_LEVEL > 1
+  else {
+    edge->face_right = 6767; /* debugging */
   }
+#endif
   if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->next_left = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL next_left");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->next_left = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_left)"
+                      " has int32 val of %d", colno, val);
   }
   if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
-    edge->next_right = DatumGetInt32(dat);
+    if ( isnull ) {
+      lwpgwarning("Found edge with NULL next_right");
+      edge->start_node = -1;
+    }
+    val = DatumGetInt32(dat);
+    edge->next_right = val;
+    POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_right)"
+                      " has int32 val of %d", colno, val);
   }
   if ( fields & LWT_COL_EDGE_GEOM ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
@@ -381,10 +584,15 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
       geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat);
       edge->geom = lwgeom_as_lwline(lwgeom_from_gserialized(geom));
     } else {
-      lwpgnotice("Found edge with NULL geometry !");
+      lwpgwarning("Found edge with NULL geometry !");
       edge->geom = NULL;
     }
   }
+#if POSTGIS_DEBUG_LEVEL > 1
+  else {
+    edge->geom = (void*)0x67676767; /* debugging */
+  }
+#endif
 }
 
 static void
@@ -404,7 +612,7 @@ fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
     if ( isnull ) node->containing_face = -1;
     else node->containing_face = DatumGetInt32(dat);
   }
-  if ( fields & LWT_COL_EDGE_GEOM ) {
+  if ( fields & LWT_COL_NODE_GEOM ) {
     dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
     if ( ! isnull ) {
       geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat);
@@ -414,6 +622,51 @@ fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
       node->geom = NULL;
     }
   }
+#if POSTGIS_DEBUG_LEVEL > 1
+  else {
+    node->geom = (void*)0x67676767; /* debugging */
+  }
+#endif
+}
+
+static void
+fillFaceFields(LWT_ISO_FACE* face, HeapTuple row, TupleDesc rowdesc, int fields)
+{
+  bool isnull;
+  Datum dat;
+  GSERIALIZED *geom;
+  LWGEOM *g;
+  const GBOX *box;
+  int colno = 0;
+
+  if ( fields & LWT_COL_FACE_FACE_ID ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    face->face_id = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_FACE_MBR ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    if ( ! isnull ) {
+      /* NOTE: this is a geometry of which we want to take (and clone) the BBOX */
+      geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat);
+      g = lwgeom_from_gserialized(geom);
+      box = lwgeom_get_bbox(g);
+      if ( box ) {
+        face->mbr = gbox_clone(box);
+      } else {
+        lwpgnotice("Found face with EMPTY MBR !");
+        face->mbr = NULL;
+      }
+    } else {
+      /* NOTE: perfectly fine for universe face */
+      POSTGIS_DEBUG(1, "Found face with NULL MBR");
+      face->mbr = NULL;
+    }
+  }
+#if POSTGIS_DEBUG_LEVEL > 1
+  else {
+    face->mbr = (void*)0x67676767; /* debugging */
+  }
+#endif
 }
 
 /* return 0 on failure (null) 1 otherwise */
@@ -450,7 +703,9 @@ cb_getEdgeById(const LWT_BE_TOPOLOGY* topo,
     appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
   }
   appendStringInfoString(sql, ")");
-  spi_result = SPI_execute(sql->data, true, *numelems);
+  POSTGIS_DEBUGF(1, "cb_getEdgeById query: %s", sql->data);
+
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
   if ( spi_result != SPI_OK_SELECT ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
          *numelems = -1; return NULL;
@@ -474,71 +729,48 @@ cb_getEdgeById(const LWT_BE_TOPOLOGY* topo,
 }
 
 static LWT_ISO_EDGE*
-cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
-      const LWPOINT* pt, double dist, int* numelems,
-      int fields, int limit)
+cb_getEdgeByNode(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
 {
   LWT_ISO_EDGE *edges;
        int spi_result;
-  int elems_requested = limit;
-  size_t hexewkb_size;
-  char *hexewkb;
 
   StringInfoData sqldata;
   StringInfo sql = &sqldata;
   int i;
 
   initStringInfo(sql);
-  if ( elems_requested == -1 ) {
-    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
-  } else {
-    appendStringInfoString(sql, "SELECT ");
-    addEdgeFields(sql, fields, 0);
-  }
+  appendStringInfoString(sql, "SELECT ");
+  addEdgeFields(sql, fields, 0);
   appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
-  // TODO: use binary cursor here ?
-  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
-  if ( dist ) {
-    appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist);
-  } else {
-    appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb);
+  appendStringInfoString(sql, " WHERE start_node IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
   }
-  lwfree(hexewkb);
-  if ( elems_requested == -1 ) {
-    appendStringInfoString(sql, ")");
-  } else if ( elems_requested > 0 ) {
-    appendStringInfo(sql, " LIMIT %d", elems_requested);
+  appendStringInfoString(sql, ") OR end_node IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
   }
-  lwpgnotice("cb_getEdgeWithinDistance2D: query is: %s", sql->data);
-  spi_result = SPI_execute(sql->data, true, limit >= 0 ? limit : 0);
+  appendStringInfoString(sql, ")");
+
+  POSTGIS_DEBUGF(1, "cb_getEdgeByNode query: %s", sql->data);
+  POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
+
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
   if ( spi_result != SPI_OK_SELECT ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
          *numelems = -1; return NULL;
   }
   pfree(sqldata.data);
 
-  lwpgnotice("cb_getEdgeWithinDistance2D: edge query "
-             "(limited by %d) returned %d rows",
-             elems_requested, SPI_processed);
+  POSTGIS_DEBUGF(1, "cb_getEdgeByNode: edge query returned %d rows", SPI_processed);
   *numelems = SPI_processed;
   if ( ! SPI_processed ) {
     return NULL;
   }
 
-  if ( elems_requested == -1 )
-  {
-    /* This was an EXISTS query */
-    {
-      Datum dat;
-      bool isnull, exists;
-      dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
-      exists = DatumGetBool(dat);
-      *numelems = exists ? 1 : 0;
-      lwpgnotice("cb_getEdgeWithinDistance2D: exists ? %d", *numelems);
-    }
-    return NULL;
-  }
-
   edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
   for ( i=0; i<SPI_processed; ++i )
   {
@@ -549,49 +781,387 @@ cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
   return edges;
 }
 
-static LWT_ISO_NODE*
-cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
-      const LWPOINT* pt, double dist, int* numelems,
-      int fields, int limit)
+static LWT_ISO_EDGE*
+cb_getEdgeByFace(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
 {
-  LWT_ISO_NODE *nodes;
+  LWT_ISO_EDGE *edges;
        int spi_result;
-  size_t hexewkb_size;
-  char *hexewkb;
+
   StringInfoData sqldata;
   StringInfo sql = &sqldata;
-  int elems_requested = limit;
   int i;
 
   initStringInfo(sql);
-  if ( elems_requested == -1 ) {
-    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
-  } else {
-    appendStringInfoString(sql, "SELECT ");
-    if ( fields ) addNodeFields(sql, fields);
-    else {
-      lwpgwarning("liblwgeom-topo invoked 'getNodeWithinDistance2D' "
-                  "backend callback with limit=%d and no fields",
-                  elems_requested);
-      appendStringInfo(sql, "*");
-    }
+  appendStringInfoString(sql, "SELECT ");
+  addEdgeFields(sql, fields, 0);
+  appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
+  appendStringInfoString(sql, " WHERE left_face IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
   }
-  appendStringInfo(sql, " FROM \"%s\".node", topo->name);
-  // TODO: use binary cursor here ?
-  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
-  if ( dist ) {
-    appendStringInfo(sql, " WHERE ST_DWithin(geom, '%s'::geometry, dist)",
-                     hexewkb);
-  } else {
-    appendStringInfo(sql, " WHERE ST_Within(geom, '%s'::geometry)", hexewkb);
+  appendStringInfoString(sql, ") OR right_face IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
   }
-  lwfree(hexewkb);
-  if ( elems_requested == -1 ) {
-    appendStringInfoString(sql, ")");
+  appendStringInfoString(sql, ")");
+
+  POSTGIS_DEBUGF(1, "cb_getEdgeByFace query: %s", sql->data);
+  POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
+
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  POSTGIS_DEBUGF(1, "cb_getEdgeByFace: edge query returned %d rows", SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return edges;
+}
+
+static LWT_ISO_FACE*
+cb_getFacesById(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
+{
+  LWT_ISO_FACE *faces;
+       int spi_result;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "SELECT ");
+  addFaceFields(sql, fields);
+  appendStringInfo(sql, " FROM \"%s\".face", topo->name);
+  appendStringInfoString(sql, " WHERE face_id IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
+  }
+  appendStringInfoString(sql, ")");
+
+  POSTGIS_DEBUGF(1, "cb_getFaceById query: %s", sql->data);
+  POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
+
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  POSTGIS_DEBUGF(1, "cb_getFaceById: face query returned %d rows", SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  faces = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillFaceFields(&faces[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return faces;
+}
+
+static LWT_ELEMID*
+cb_getRingEdges(const LWT_BE_TOPOLOGY* topo,
+      LWT_ELEMID edge, int* numelems, int limit)
+{
+  LWT_ELEMID *edges;
+       int spi_result;
+  TupleDesc rowdesc;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "WITH RECURSIVE edgering AS ( "
+    "SELECT %lld as signed_edge_id, edge_id, next_left_edge, next_right_edge "
+    "FROM \"%s\".edge_data WHERE edge_id = %lld UNION "
+    "SELECT CASE WHEN "
+    "p.signed_edge_id < 0 THEN p.next_right_edge ELSE p.next_left_edge END, "
+    "e.edge_id, e.next_left_edge, e.next_right_edge "
+    "FROM \"%s\".edge_data e, edgering p WHERE "
+    "e.edge_id = CASE WHEN p.signed_edge_id < 0 THEN "
+    "abs(p.next_right_edge) ELSE abs(p.next_left_edge) END ) "
+    "SELECT * FROM edgering",
+    edge, topo->name, llabs(edge), topo->name);
+  if ( limit ) {
+    ++limit; /* so we know if we hit it */
+    appendStringInfo(sql, " LIMIT %d", limit);
+  }
+
+  POSTGIS_DEBUGF(1, "cb_getRingEdges query (limit %d): %s", limit, sql->data);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  POSTGIS_DEBUGF(1, "cb_getRingEdges: edge query returned %d rows", SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+  if ( limit && SPI_processed == limit )
+  {
+    cberror(topo->be_data, "Max traversing limit hit: %d", limit-1);
+         *numelems = -1; return NULL;
+  }
+
+  edges = palloc( sizeof(LWT_ELEMID) * SPI_processed );
+  rowdesc = SPI_tuptable->tupdesc;
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    bool isnull;
+    Datum dat;
+    int32 val;
+    dat = SPI_getbinval(row, rowdesc, 1, &isnull);
+    if ( isnull ) {
+      lwfree(edges);
+      cberror(topo->be_data, "Found edge with NULL edge_id");
+      *numelems = -1; return NULL;
+    }
+    val = DatumGetInt32(dat);
+    edges[i] = val;
+    POSTGIS_DEBUGF(1, "Component %d in ring of edge %lld is edge %d",
+                   i, edge, val);
+  }
+
+  return edges;
+}
+
+static LWT_ISO_NODE*
+cb_getNodeById(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
+{
+  LWT_ISO_NODE *nodes;
+       int spi_result;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "SELECT ");
+  addNodeFields(sql, fields);
+  appendStringInfo(sql, " FROM \"%s\".node", topo->name);
+  appendStringInfoString(sql, " WHERE node_id IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
+  }
+  appendStringInfoString(sql, ")");
+  POSTGIS_DEBUGF(1, "cb_getNodeById query: %s", sql->data);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getNodeById: edge query returned %d rows", SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  nodes = palloc( sizeof(LWT_ISO_NODE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return nodes;
+}
+
+static LWT_ISO_NODE*
+cb_getNodeByFace(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
+{
+  LWT_ISO_NODE *nodes;
+       int spi_result;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "SELECT ");
+  addNodeFields(sql, fields);
+  appendStringInfo(sql, " FROM \"%s\".node", topo->name);
+  appendStringInfoString(sql, " WHERE containing_face IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
+  }
+  appendStringInfoString(sql, ")");
+  POSTGIS_DEBUGF(1, "cb_getNodeByFace query: %s", sql->data);
+  POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getNodeByFace: edge query returned %d rows", SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  nodes = palloc( sizeof(LWT_ISO_NODE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return nodes;
+}
+
+static LWT_ISO_EDGE*
+cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt, double dist, int* numelems,
+      int fields, int limit)
+{
+  LWT_ISO_EDGE *edges;
+       int spi_result;
+  int elems_requested = limit;
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
+  } else {
+    appendStringInfoString(sql, "SELECT ");
+    addEdgeFields(sql, fields, 0);
+  }
+  appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
+  // TODO: use binary cursor here ?
+  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
+  if ( dist ) {
+    appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist);
+  } else {
+    appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb);
+  }
+  lwfree(hexewkb);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, ")");
   } else if ( elems_requested > 0 ) {
     appendStringInfo(sql, " LIMIT %d", elems_requested);
   }
-  spi_result = SPI_execute(sql->data, true, limit >= 0 ? limit : 0);
+  lwpgnotice("cb_getEdgeWithinDistance2D: query is: %s", sql->data);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getEdgeWithinDistance2D: edge query "
+             "(limited by %d) returned %d rows",
+             elems_requested, SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  if ( elems_requested == -1 )
+  {
+    /* This was an EXISTS query */
+    {
+      Datum dat;
+      bool isnull, exists;
+      dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
+      exists = DatumGetBool(dat);
+      *numelems = exists ? 1 : 0;
+      lwpgnotice("cb_getEdgeWithinDistance2D: exists ? %d", *numelems);
+    }
+    return NULL;
+  }
+
+  edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return edges;
+}
+
+static LWT_ISO_NODE*
+cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt, double dist, int* numelems,
+      int fields, int limit)
+{
+  LWT_ISO_NODE *nodes;
+       int spi_result;
+  size_t hexewkb_size;
+  char *hexewkb;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int elems_requested = limit;
+  int i;
+
+  initStringInfo(sql);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
+  } else {
+    appendStringInfoString(sql, "SELECT ");
+    if ( fields ) addNodeFields(sql, fields);
+    else {
+      lwpgwarning("liblwgeom-topo invoked 'getNodeWithinDistance2D' "
+                  "backend callback with limit=%d and no fields",
+                  elems_requested);
+      appendStringInfo(sql, "*");
+    }
+  }
+  appendStringInfo(sql, " FROM \"%s\".node", topo->name);
+  // TODO: use binary cursor here ?
+  hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size);
+  if ( dist ) {
+    appendStringInfo(sql, " WHERE ST_DWithin(geom, '%s'::geometry, dist)",
+                     hexewkb);
+  } else {
+    appendStringInfo(sql, " WHERE ST_Within(geom, '%s'::geometry)", hexewkb);
+  }
+  lwfree(hexewkb);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, ")");
+  } else if ( elems_requested > 0 ) {
+    appendStringInfo(sql, " LIMIT %d", elems_requested);
+  }
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
   if ( spi_result != SPI_OK_SELECT ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
             spi_result, sql->data);
@@ -647,10 +1217,12 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo,
   for ( i=0; i<numelems; ++i ) {
     if ( i ) appendStringInfoString(sql, ",");
     // TODO: prepare and execute ?
-    addNodeValues(sql, &nodes[i]);
+    addNodeValues(sql, &nodes[i], LWT_COL_NODE_ALL);
   }
   appendStringInfoString(sql, " RETURNING node_id");
 
+  POSTGIS_DEBUGF(1, "cb_insertNodes query: %s", sql->data);
+
   spi_result = SPI_execute(sql->data, false, numelems);
   if ( spi_result != SPI_OK_INSERT_RETURNING ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
@@ -659,6 +1231,8 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo,
   }
   pfree(sqldata.data);
 
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
   if ( SPI_processed != numelems ) {
                cberror(topo->be_data, "processed %d rows, expected %d",
             SPI_processed, numelems);
@@ -674,55 +1248,110 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo,
       SPI_tuptable->tupdesc, LWT_COL_NODE_NODE_ID);
   }
 
-  return 1;
+  return 1;
+}
+
+static int
+cb_insertEdges( const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_EDGE* edges, int numelems )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+  int needsEdgeIdReturn = 0;
+
+  initStringInfo(sql);
+  /* NOTE: we insert into "edge", on which an insert rule is defined */
+  appendStringInfo(sql, "INSERT INTO \"%s\".edge_data (", topo->name);
+  addEdgeFields(sql, LWT_COL_EDGE_ALL, 1);
+  appendStringInfoString(sql, ") VALUES ");
+  for ( i=0; i<numelems; ++i ) {
+    if ( i ) appendStringInfoString(sql, ",");
+    // TODO: prepare and execute ?
+    addEdgeValues(sql, &edges[i], LWT_COL_EDGE_ALL, 1);
+    if ( edges[i].edge_id == -1 ) needsEdgeIdReturn = 1;
+  }
+  if ( needsEdgeIdReturn ) appendStringInfoString(sql, " RETURNING edge_id");
+
+  POSTGIS_DEBUGF(1, "cb_insertEdges query (%d elems): %s", numelems, sql->data);
+  spi_result = SPI_execute(sql->data, false, numelems);
+  if ( spi_result != ( needsEdgeIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
+  {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+  POSTGIS_DEBUGF(1, "cb_insertEdges query processed %d rows", SPI_processed);
+  if ( SPI_processed != numelems ) {
+               cberror(topo->be_data, "processed %d rows, expected %d",
+            SPI_processed, numelems);
+         return -1;
+  }
+
+  if ( needsEdgeIdReturn )
+  {
+    /* Set node_id for items that need it */
+    for ( i=0; i<SPI_processed; ++i )
+    {
+      if ( edges[i].edge_id != -1 ) continue;
+      fillEdgeFields(&edges[i], SPI_tuptable->vals[i],
+        SPI_tuptable->tupdesc, LWT_COL_EDGE_EDGE_ID);
+    }
+  }
+
+  return SPI_processed;
 }
 
 static int
-cb_insertEdges( const LWT_BE_TOPOLOGY* topo,
-      LWT_ISO_EDGE* edges, int numelems )
+cb_insertFaces( const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_FACE* faces, int numelems )
 {
        int spi_result;
   StringInfoData sqldata;
   StringInfo sql = &sqldata;
   int i;
-  int needsEdgeIdReturn = 0;
+  int needsFaceIdReturn = 0;
 
   initStringInfo(sql);
-  /* NOTE: we insert into "edge", on which an insert rule is defined */
-  appendStringInfo(sql, "INSERT INTO \"%s\".edge_data (", topo->name);
-  addEdgeFields(sql, LWT_COL_EDGE_ALL, 1);
+  appendStringInfo(sql, "INSERT INTO \"%s\".face (", topo->name);
+  addFaceFields(sql, LWT_COL_FACE_ALL);
   appendStringInfoString(sql, ") VALUES ");
   for ( i=0; i<numelems; ++i ) {
     if ( i ) appendStringInfoString(sql, ",");
     // TODO: prepare and execute ?
-    addEdgeValues(sql, &edges[i], 1);
-    if ( edges[i].edge_id == -1 ) needsEdgeIdReturn = 1;
+    addFaceValues(sql, &faces[i], topo->srid);
+    if ( faces[i].face_id == -1 ) needsFaceIdReturn = 1;
   }
-  if ( needsEdgeIdReturn ) appendStringInfoString(sql, " RETURNING edge_id");
+  if ( needsFaceIdReturn ) appendStringInfoString(sql, " RETURNING face_id");
 
+  POSTGIS_DEBUGF(1, "cb_insertFaces query (%d elems): %s", numelems, sql->data);
   spi_result = SPI_execute(sql->data, false, numelems);
-  if ( spi_result != ( needsEdgeIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
+  if ( spi_result != ( needsFaceIdReturn ? SPI_OK_INSERT_RETURNING : SPI_OK_INSERT ) )
   {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
             spi_result, sql->data);
          return -1;
   }
   pfree(sqldata.data);
-
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+  POSTGIS_DEBUGF(1, "cb_insertFaces query processed %d rows", SPI_processed);
   if ( SPI_processed != numelems ) {
                cberror(topo->be_data, "processed %d rows, expected %d",
             SPI_processed, numelems);
          return -1;
   }
 
-  if ( needsEdgeIdReturn )
+  if ( needsFaceIdReturn )
   {
     /* Set node_id for items that need it */
     for ( i=0; i<SPI_processed; ++i )
     {
-      if ( edges[i].edge_id != -1 ) continue;
-      fillEdgeFields(&edges[i], SPI_tuptable->vals[i],
-        SPI_tuptable->tupdesc, LWT_COL_EDGE_EDGE_ID);
+      if ( faces[i].face_id != -1 ) continue;
+      fillFaceFields(&faces[i], SPI_tuptable->vals[i],
+        SPI_tuptable->tupdesc, LWT_COL_FACE_FACE_ID);
     }
   }
 
@@ -751,7 +1380,7 @@ cb_updateEdges( const LWT_BE_TOPOLOGY* topo,
     addEdgeUpdate( sql, exc_edge, exc_fields, 1, updNot );
   }
 
-  /* lwpgnotice("cb_updateEdges: %s", sql->data); */
+  POSTGIS_DEBUGF(1, "cb_updateEdges query: %s", sql->data);
 
   spi_result = SPI_execute( sql->data, false, 0 );
   if ( spi_result != SPI_OK_UPDATE )
@@ -762,11 +1391,239 @@ cb_updateEdges( const LWT_BE_TOPOLOGY* topo,
   }
   pfree(sqldata.data);
 
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
   lwpgnotice("cb_updateEdges: update query processed %d rows", SPI_processed);
 
   return SPI_processed;
 }
 
+static int
+cb_updateNodes( const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_NODE* sel_node, int sel_fields,
+      const LWT_ISO_NODE* upd_node, int upd_fields,
+      const LWT_ISO_NODE* exc_node, int exc_fields )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "UPDATE \"%s\".node SET ", topo->name);
+  addNodeUpdate( sql, upd_node, upd_fields, 1, updSet );
+  if ( exc_node || sel_node ) appendStringInfoString(sql, " WHERE ");
+  if ( sel_node ) {
+    addNodeUpdate( sql, sel_node, sel_fields, 1, updSel );
+    if ( exc_node ) appendStringInfoString(sql, " AND ");
+  }
+  if ( exc_node ) {
+    addNodeUpdate( sql, exc_node, exc_fields, 1, updNot );
+  }
+
+  POSTGIS_DEBUGF(1, "cb_updateNodes: %s", sql->data);
+
+  spi_result = SPI_execute( sql->data, false, 0 );
+  if ( spi_result != SPI_OK_UPDATE )
+  {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  POSTGIS_DEBUGF(1, "cb_updateNodes: update query processed %d rows", SPI_processed);
+
+  return SPI_processed;
+}
+
+static int
+cb_updateNodesById( const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_NODE* nodes, int numnodes, int fields )
+{
+  int i;
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  const char *sep = "";
+  const char *sep1 = ",";
+
+  if ( ! fields ) {
+               cberror(topo->be_data,
+            "updateNodesById callback called with no update fields!");
+         return -1;
+  }
+
+  POSTGIS_DEBUGF(1, "cb_updateNodesById got %d nodes to update"
+                    " (fields:%d)",
+                    numnodes, fields);
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "WITH newnodes(node_id,");
+  addNodeFields(sql, fields);
+  appendStringInfoString(sql, ") AS ( VALUES ");
+  for (i=0; i<numnodes; ++i) {
+    const LWT_ISO_NODE* node = &(nodes[i]);
+    if ( i ) appendStringInfoString(sql, ",");
+    addNodeValues(sql, node, LWT_COL_NODE_NODE_ID|fields);
+  }
+  appendStringInfo(sql, " ) UPDATE \"%s\".node n SET ", topo->name);
+
+  /* TODO: turn the following into a function */
+  if ( fields & LWT_COL_NODE_NODE_ID ) {
+    appendStringInfo(sql, "%snode_id = o.node_id", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_NODE_CONTAINING_FACE ) {
+    appendStringInfo(sql, "%scontaining_face = o.containing_face", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_NODE_GEOM ) {
+    appendStringInfo(sql, "%sgeom = o.geom", sep);
+  }
+
+  appendStringInfo(sql, " FROM newnodes o WHERE n.node_id = o.node_id");
+
+  POSTGIS_DEBUGF(1, "cb_updateNodesById query: %s", sql->data);
+
+  spi_result = SPI_execute( sql->data, false, 0 );
+  if ( spi_result != SPI_OK_UPDATE )
+  {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  POSTGIS_DEBUGF(1, "cb_updateNodesById: update query processed %d rows", SPI_processed);
+
+  return SPI_processed;
+}
+
+static int
+cb_updateFacesById( const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_FACE* faces, int numfaces )
+{
+  int i;
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "WITH newfaces AS ( SELECT ");
+  for (i=0; i<numfaces; ++i) {
+    const LWT_ISO_FACE* face = &(faces[i]);
+    appendStringInfo(sql,
+      "%lld id, ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d) mbr",
+      face->face_id, face->mbr->xmin, face->mbr->ymin,
+      face->mbr->xmax, face->mbr->ymax, topo->srid);
+  }
+  appendStringInfo(sql, ") UPDATE \"%s\".face o SET mbr = i.mbr "
+                        "FROM newfaces i WHERE o.face_id = i.id",
+                        topo->name);
+
+  POSTGIS_DEBUGF(1, "cb_updateFacesById query: %s", sql->data);
+
+  spi_result = SPI_execute( sql->data, false, 0 );
+  if ( spi_result != SPI_OK_UPDATE )
+  {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  POSTGIS_DEBUGF(1, "cb_updateFacesById: update query processed %d rows", SPI_processed);
+
+  return SPI_processed;
+}
+
+static int
+cb_updateEdgesById( const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_EDGE* edges, int numedges, int fields )
+{
+  int i;
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  const char *sep = "";
+  const char *sep1 = ",";
+
+  if ( ! fields ) {
+               cberror(topo->be_data,
+            "updateEdgesById callback called with no update fields!");
+         return -1;
+  }
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "WITH newedges(edge_id,");
+  addEdgeFields(sql, fields, 0);
+  appendStringInfoString(sql, ") AS ( VALUES ");
+  for (i=0; i<numedges; ++i) {
+    const LWT_ISO_EDGE* edge = &(edges[i]);
+    if ( i ) appendStringInfoString(sql, ",");
+    addEdgeValues(sql, edge, fields|LWT_COL_EDGE_EDGE_ID, 0);
+  }
+  appendStringInfo(sql, ") UPDATE \"%s\".edge_data e SET ", topo->name);
+
+  /* TODO: turn the following into a function */
+  if ( fields & LWT_COL_EDGE_START_NODE ) {
+    appendStringInfo(sql, "%sstart_node = o.start_node", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_END_NODE ) {
+    appendStringInfo(sql, "%send_node = o.end_node", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
+    appendStringInfo(sql, "%sleft_face = o.left_face", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
+    appendStringInfo(sql, "%sright_face = o.right_face", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
+    appendStringInfo(sql,
+      "%snext_left_edge = o.next_left_edge, "
+      "abs_next_left_edge = abs(o.next_left_edge)", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
+    appendStringInfo(sql,
+      "%snext_right_edge = o.next_right_edge, "
+      "abs_next_right_edge = abs(o.next_right_edge)", sep);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_GEOM ) {
+    appendStringInfo(sql, "%sgeom = o.geom", sep);
+  }
+
+  appendStringInfo(sql, " FROM newedges o WHERE e.edge_id = o.edge_id");
+
+  POSTGIS_DEBUGF(1, "cb_updateEdgesById query: %s", sql->data);
+
+  spi_result = SPI_execute( sql->data, false, 0 );
+  if ( spi_result != SPI_OK_UPDATE )
+  {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  POSTGIS_DEBUGF(1, "cb_updateEdgesById: update query processed %d rows", SPI_processed);
+
+  return SPI_processed;
+}
+
 static int
 cb_deleteEdges( const LWT_BE_TOPOLOGY* topo,
       const LWT_ISO_EDGE* sel_edge, int sel_fields )
@@ -779,7 +1636,7 @@ cb_deleteEdges( const LWT_BE_TOPOLOGY* topo,
   appendStringInfo(sql, "DELETE FROM \"%s\".edge_data WHERE ", topo->name);
   addEdgeUpdate( sql, sel_edge, sel_fields, 0, updSel );
 
-  /* lwpgnotice("cb_deleteEdges: %s", sql->data); */
+  POSTGIS_DEBUGF(1, "cb_deleteEdges: %s", sql->data);
 
   spi_result = SPI_execute( sql->data, false, 0 );
   if ( spi_result != SPI_OK_DELETE )
@@ -790,50 +1647,163 @@ cb_deleteEdges( const LWT_BE_TOPOLOGY* topo,
   }
   pfree(sqldata.data);
 
-  lwpgnotice("cb_deleteEdges: delete query processed %d rows", SPI_processed);
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  POSTGIS_DEBUGF(1, "cb_deleteEdges: delete query processed %d rows", SPI_processed);
+
+  return SPI_processed;
+}
+
+static LWT_ELEMID
+cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  bool isnull;
+  Datum dat;
+  LWT_ELEMID edge_id;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')",
+    topo->name);
+  spi_result = SPI_execute(sql->data, false, 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return -1;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed ) topo->be_data->data_changed = true;
+
+  if ( SPI_processed != 1 ) {
+               cberror(topo->be_data, "processed %d rows, expected 1", SPI_processed);
+         return -1;
+  }
+
+  dat = SPI_getbinval( SPI_tuptable->vals[0],
+                       SPI_tuptable->tupdesc, 1, &isnull );
+  if ( isnull ) {
+               cberror(topo->be_data, "nextval for edge_id returned null");
+         return -1;
+  }
+  edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */
+  return edge_id;
+}
+
+static int
+cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
+  LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i, ntopogeoms;
+  const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
+
+  initStringInfo(sql);
+  if ( new_edge2 == -1 ) {
+    appendStringInfo(sql, "SELECT %s", proj);
+  } else {
+    appendStringInfoString(sql, "DELETE");
+  }
+  appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
+    "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
+    "AND abs(r.element_id) = %lld AND r.element_type = 2",
+    topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge );
+  if ( new_edge2 != -1 ) {
+    appendStringInfo(sql, " RETURNING %s", proj);
+  }
+
+  spi_result = SPI_execute(sql->data, new_edge2 == -1 ? !topo->be_data->data_changed : false, 0);
+  if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+            spi_result, sql->data);
+         return 0;
+  }
+
+  if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
+  {
+    topo->be_data->data_changed = true;
+  }
+
+  ntopogeoms = SPI_processed;
+  for ( i=0; i<ntopogeoms; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    TupleDesc tdesc = SPI_tuptable->tupdesc;
+    int negate;
+    int element_id;
+    int topogeo_id;
+    int layer_id;
+    int element_type;
+
+    if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) ) {
+                 cberror(topo->be_data,
+        "unexpected null element_id in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
+    negate = ( element_id < 0 );
+
+    if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) ) {
+                 cberror(topo->be_data,
+        "unexpected null topogeo_id in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
 
-  return SPI_processed;
-}
+    if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) ) {
+                 cberror(topo->be_data,
+        "unexpected null layer_id in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
 
-static LWT_ELEMID
-cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo )
-{
-       int spi_result;
-  StringInfoData sqldata;
-  StringInfo sql = &sqldata;
-  bool isnull;
-  Datum dat;
-  LWT_ELEMID edge_id;
+    if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) ) {
+                 cberror(topo->be_data,
+        "unexpected null element_type in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
 
-  initStringInfo(sql);
-  appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')",
-    topo->name);
-  spi_result = SPI_execute(sql->data, false, 0);
-  if ( spi_result != SPI_OK_SELECT ) {
-               cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
-            spi_result, sql->data);
-         return -1;
+    resetStringInfo(sql);
+    appendStringInfo(sql,
+      "INSERT INTO \"%s\".relation VALUES ("
+      "%d,%d,%lld,%d)", topo->name,
+      topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type);
+    spi_result = SPI_execute(sql->data, false, 0);
+    if ( spi_result != SPI_OK_INSERT ) {
+      cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+              spi_result, sql->data);
+      return 0;
+    }
+    if ( SPI_processed ) topo->be_data->data_changed = true;
+    if ( new_edge2 != -1 ) {
+      resetStringInfo(sql);
+      appendStringInfo(sql,
+        "INSERT INTO FROM \"%s\".relation VALUES ("
+        "%d,%d,%lld,%d", topo->name,
+        topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type);
+      spi_result = SPI_execute(sql->data, false, 0);
+      if ( spi_result != SPI_OK_INSERT ) {
+        cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
+                spi_result, sql->data);
+        return 0;
+      }
+      if ( SPI_processed ) topo->be_data->data_changed = true;
+    }
   }
-  pfree(sqldata.data);
 
-  if ( SPI_processed != 1 ) {
-               cberror(topo->be_data, "processed %d rows, expected 1", SPI_processed);
-         return -1;
-  }
+  POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms);
 
-  dat = SPI_getbinval( SPI_tuptable->vals[0],
-                       SPI_tuptable->tupdesc, 1, &isnull );
-  if ( isnull ) {
-               cberror(topo->be_data, "nextval for edge_id returned null");
-         return -1;
-  }
-  edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */
-  return edge_id;
+  return 1;
 }
 
 static int
-cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
-  LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 )
+cb_updateTopoGeomFaceSplit ( const LWT_BE_TOPOLOGY* topo,
+  LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 )
 {
        int spi_result;
   StringInfoData sqldata;
@@ -841,26 +1811,38 @@ cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
   int i, ntopogeoms;
   const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type";
 
+  POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit signalled "
+                    "split of face %lld into %lld and %lld",
+                    split_face, new_face1, new_face2);
+
   initStringInfo(sql);
-  if ( new_edge2 == -1 ) {
+  if ( new_face2 == -1 ) {
     appendStringInfo(sql, "SELECT %s", proj);
   } else {
     appendStringInfoString(sql, "DELETE");
   }
   appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE "
     "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id "
-    "AND abs(r.element_id) = %lld AND r.element_type = 2",
-    topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge );
-  if ( new_edge2 != -1 ) {
+    "AND abs(r.element_id) = %lld AND r.element_type = 3",
+    topo->name, (new_face2 == -1 ? "," : "USING" ), topo->id, split_face );
+  if ( new_face2 != -1 ) {
     appendStringInfo(sql, " RETURNING %s", proj);
   }
 
-  spi_result = SPI_execute(sql->data, new_edge2 == -1, 0);
-  if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) {
+  POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
+
+  spi_result = SPI_execute(sql->data, new_face2 == -1 ? !topo->be_data->data_changed : false, 0);
+  if ( spi_result != ( new_face2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
             spi_result, sql->data);
          return 0;
   }
+
+  if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed )
+  {
+    topo->be_data->data_changed = true;
+  }
+
   ntopogeoms = SPI_processed;
   for ( i=0; i<ntopogeoms; ++i )
   {
@@ -905,29 +1887,37 @@ cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo,
     appendStringInfo(sql,
       "INSERT INTO \"%s\".relation VALUES ("
       "%d,%d,%lld,%d)", topo->name,
-      topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type);
+      topogeo_id, layer_id, negate ? -new_face1 : new_face1, element_type);
+
+    POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
+
     spi_result = SPI_execute(sql->data, false, 0);
     if ( spi_result != SPI_OK_INSERT ) {
       cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
               spi_result, sql->data);
       return 0;
     }
-    if ( new_edge2 != -1 ) {
+    if ( SPI_processed ) topo->be_data->data_changed = true;
+    if ( new_face2 != -1 ) {
       resetStringInfo(sql);
       appendStringInfo(sql,
-        "INSERT INTO FROM \"%s\".relation VALUES ("
-        "%d,%d,%lld,%d", topo->name,
-        topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type);
+        "INSERT INTO \"%s\".relation VALUES ("
+        "%d,%d,%lld,%d)", topo->name,
+        topogeo_id, layer_id, negate ? -new_face2 : new_face2, element_type);
+
+      POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data);
+
       spi_result = SPI_execute(sql->data, false, 0);
       if ( spi_result != SPI_OK_INSERT ) {
         cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
                 spi_result, sql->data);
         return 0;
       }
+      if ( SPI_processed ) topo->be_data->data_changed = true;
     }
   }
 
-  lwpgnotice("cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms);
+  POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit: updated %d topogeoms", ntopogeoms);
 
   return 1;
 }
@@ -955,7 +1945,7 @@ cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt )
       topo->name, hexewkb, topo->name, hexewkb);
   lwfree(hexewkb);
 
-  spi_result = SPI_execute(sql->data, true, 1);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 1);
   if ( spi_result != SPI_OK_SELECT ) {
                cberror(topo->be_data, "unexpected return (%d) from query execution: %s",
             spi_result, sql->data);
@@ -977,12 +1967,149 @@ cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt )
   return face_id;
 }
 
-LWT_BE_CALLBACKS be_callbacks = {
+static LWT_ISO_NODE* 
+cb_getNodeWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box,
+                     int* numelems, int fields, int limit )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+  int elems_requested = limit;
+  LWT_ISO_NODE* nodes;
+
+  initStringInfo(sql);
+
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
+  } else {
+    appendStringInfoString(sql, "SELECT ");
+    addNodeFields(sql, fields);
+  }
+  appendStringInfo(sql, " FROM \"%s\".node WHERE geom && "
+                        "ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d)",
+                        topo->name, box->xmin, box->ymin,
+                        box->xmax, box->ymax, topo->srid);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, ")");
+  } else if ( elems_requested > 0 ) {
+    appendStringInfo(sql, " LIMIT %d", elems_requested);
+  }
+  lwpgnotice("cb_getNodeWithinBox2D: query is: %s", sql->data);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getNodeWithinBox2D: edge query "
+             "(limited by %d) returned %d rows",
+             elems_requested, SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  if ( elems_requested == -1 )
+  {
+    /* This was an EXISTS query */
+    {
+      Datum dat;
+      bool isnull, exists;
+      dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
+      exists = DatumGetBool(dat);
+      *numelems = exists ? 1 : 0;
+      lwpgnotice("cb_getNodeWithinBox2D: exists ? %d", *numelems);
+    }
+    return NULL;
+  }
+
+  nodes = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillNodeFields(&nodes[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return nodes;
+}
+
+static LWT_ISO_EDGE* 
+cb_getEdgeWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box,
+                     int* numelems, int fields, int limit )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+  int elems_requested = limit;
+  LWT_ISO_EDGE* edges;
+
+  initStringInfo(sql);
+
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1");
+  } else {
+    appendStringInfoString(sql, "SELECT ");
+    addEdgeFields(sql, fields, 0);
+  }
+  appendStringInfo(sql, " FROM \"%s\".edge WHERE geom && "
+                        "ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d)",
+                        topo->name, box->xmin, box->ymin,
+                        box->xmax, box->ymax, topo->srid);
+  if ( elems_requested == -1 ) {
+    appendStringInfoString(sql, ")");
+  } else if ( elems_requested > 0 ) {
+    appendStringInfo(sql, " LIMIT %d", elems_requested);
+  }
+  lwpgnotice("cb_getEdgeWithinBox2D: query is: %s", sql->data);
+  spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+               cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         *numelems = -1; return NULL;
+  }
+  pfree(sqldata.data);
+
+  lwpgnotice("cb_getEdgeWithinBox2D: edge query "
+             "(limited by %d) returned %d rows",
+             elems_requested, SPI_processed);
+  *numelems = SPI_processed;
+  if ( ! SPI_processed ) {
+    return NULL;
+  }
+
+  if ( elems_requested == -1 )
+  {
+    /* This was an EXISTS query */
+    {
+      Datum dat;
+      bool isnull, exists;
+      dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
+      exists = DatumGetBool(dat);
+      *numelems = exists ? 1 : 0;
+      lwpgnotice("cb_getEdgeWithinBox2D: exists ? %d", *numelems);
+    }
+    return NULL;
+  }
+
+  edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed );
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    HeapTuple row = SPI_tuptable->vals[i];
+    fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields);
+  }
+
+  return edges;
+}
+
+
+static LWT_BE_CALLBACKS be_callbacks = {
     cb_lastErrorMessage,
     NULL, /* createTopology */
-    cb_loadTopologyByName, /* loadTopologyByName */
-    cb_freeTopology, /* freeTopology */
-    NULL, /* getNodeById */
+    cb_loadTopologyByName,
+    cb_freeTopology,
+    cb_getNodeById,
     cb_getNodeWithinDistance2D,
     cb_insertNodes,
     cb_getEdgeById,
@@ -990,10 +2117,22 @@ LWT_BE_CALLBACKS be_callbacks = {
     cb_getNextEdgeId,
     cb_insertEdges,
     cb_updateEdges,
-    NULL, /* getFacesById */
+    cb_getFacesById,
     cb_getFaceContainingPoint,
     cb_updateTopoGeomEdgeSplit,
-    cb_deleteEdges
+    cb_deleteEdges,
+    cb_getNodeWithinBox2D,
+    cb_getEdgeWithinBox2D,
+    cb_getEdgeByNode,
+    cb_updateNodes,
+    cb_updateTopoGeomFaceSplit,
+    cb_insertFaces,
+    cb_updateFacesById,
+    cb_getRingEdges,
+    cb_updateEdgesById,
+    cb_getEdgeByFace,
+    cb_getNodeByFace,
+    cb_updateNodesById
 };
 
 
@@ -1077,6 +2216,7 @@ Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS)
     lwpgerror("Could not connect to SPI");
     PG_RETURN_NULL();
   }
+  be_data.data_changed = false;
 
   topo = lwt_LoadTopology(be_iface, toponame);
   pfree(toponame);
@@ -1142,6 +2282,7 @@ Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS)
     lwpgerror("Could not connect to SPI");
     PG_RETURN_NULL();
   }
+  be_data.data_changed = false;
 
   topo = lwt_LoadTopology(be_iface, toponame);
   pfree(toponame);
@@ -1218,6 +2359,7 @@ Datum ST_AddIsoNode(PG_FUNCTION_ARGS)
     lwpgerror("Could not connect to SPI");
     PG_RETURN_NULL();
   }
+  be_data.data_changed = false;
 
   topo = lwt_LoadTopology(be_iface, toponame);
   pfree(toponame);
@@ -1243,3 +2385,70 @@ Datum ST_AddIsoNode(PG_FUNCTION_ARGS)
   SPI_finish();
   PG_RETURN_INT32(node_id);
 }
+
+/*  ST_AddEdgeModFace(atopology, snode, enode, line) */
+Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_AddEdgeModFace);
+Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS)
+{
+  text* toponame_text;
+  char* toponame;
+  LWT_ELEMID startnode_id, endnode_id;
+  int edge_id;
+  GSERIALIZED *geom;
+  LWGEOM *lwgeom;
+  LWLINE *line;
+  LWT_TOPOLOGY *topo;
+
+  if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) ) {
+    lwpgerror("SQL/MM Spatial exception - null argument");
+    PG_RETURN_NULL();
+  }
+
+  toponame_text = PG_GETARG_TEXT_P(0);
+  toponame = text2cstring(toponame_text);
+       PG_FREE_IF_COPY(toponame_text, 0);
+
+  startnode_id = PG_GETARG_INT32(1) ;
+  endnode_id = PG_GETARG_INT32(2) ;
+
+  geom = PG_GETARG_GSERIALIZED_P(3);
+  lwgeom = lwgeom_from_gserialized(geom);
+  line = lwgeom_as_lwline(lwgeom);
+  if ( ! line ) {
+    lwgeom_free(lwgeom);
+         PG_FREE_IF_COPY(geom, 2);
+    lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry");
+    PG_RETURN_NULL();
+  }
+
+  if ( SPI_OK_CONNECT != SPI_connect() ) {
+    lwpgerror("Could not connect to SPI");
+    PG_RETURN_NULL();
+  }
+  be_data.data_changed = false;
+
+  topo = lwt_LoadTopology(be_iface, toponame);
+  pfree(toponame);
+  if ( ! topo ) {
+    /* should never reach this point, as lwerror would raise an exception */
+    SPI_finish();
+    PG_RETURN_NULL();
+  }
+
+  POSTGIS_DEBUG(1, "Calling lwt_AddEdgeModFace");
+  edge_id = lwt_AddEdgeModFace(topo, startnode_id, endnode_id, line, 0);
+  POSTGIS_DEBUG(1, "lwt_AddEdgeModFace returned");
+  lwgeom_free(lwgeom);
+  PG_FREE_IF_COPY(geom, 3);
+  lwt_FreeTopology(topo);
+
+  if ( edge_id == -1 ) {
+    /* should never reach this point, as lwerror would raise an exception */
+    SPI_finish();
+    PG_RETURN_NULL();
+  }
+
+  SPI_finish();
+  PG_RETURN_INT32(edge_id);
+}
index 3bc9232cfe9a0150982c79baf53d4763107a9611..b6f3f2d64b6ce1063239004fda84484f61682813 100644 (file)
@@ -3116,621 +3116,8 @@ LANGUAGE 'plpgsql' VOLATILE;
 --
 CREATE OR REPLACE FUNCTION topology.ST_AddEdgeModFace(atopology varchar, anode integer, anothernode integer, acurve geometry)
   RETURNS INTEGER AS
-$$
-DECLARE
-  rec RECORD;
-  rrec RECORD;
-  i INTEGER;
-  topoid INTEGER;
-  az FLOAT8;
-  span RECORD; -- start point analysis data
-  epan RECORD; --   end point analysis data
-  fan RECORD; -- face analisys
-  newedge RECORD; -- informations about new edge
-  sql TEXT;
-  newfaces INTEGER[];
-  newface INTEGER;
-BEGIN
-
-  --
-  -- All args required
-  -- 
-  IF atopology IS NULL
-    OR anode IS NULL
-    OR anothernode IS NULL
-    OR acurve IS NULL
-  THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - null argument';
-  END IF;
-
-  --
-  -- Acurve must be a LINESTRING
-  --
-  IF substring(geometrytype(acurve), 1, 4) != 'LINE'
-  THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - invalid curve';
-  END IF;
-  
-  --
-  -- Curve must be simple
-  --
-  IF NOT ST_IsSimple(acurve) THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - curve not simple';
-  END IF;
-
-  --
-  -- Get topology id
-  --
-  BEGIN
-    SELECT id FROM topology.topology
-      INTO STRICT topoid WHERE name = atopology;
-    EXCEPTION
-      WHEN NO_DATA_FOUND THEN
-        RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name';
-  END;
-
-  -- Initialize new edge info (will be filled up more later)
-  SELECT anode as start_node, anothernode as end_node, acurve as geom,
-    NULL::int as next_left_edge, NULL::int as next_right_edge,
-    NULL::int as left_face, NULL::int as right_face, NULL::int as edge_id,
-    NULL::int as prev_left_edge, NULL::int as prev_right_edge, -- convenience
-    anode = anothernode as isclosed, -- convenience
-    false as start_node_isolated, -- convenience
-    false as end_node_isolated, -- convenience
-    NULL::geometry as start_node_geom, -- convenience
-    NULL::geometry as end_node_geom, -- convenience
-    ST_RemoveRepeatedPoints(acurve) as cleangeom -- convenience
-  INTO newedge;
-
-  -- Compute azimut of first edge end on start node
-  SELECT null::int AS nextCW, null::int AS nextCCW,
-         null::float8 AS minaz, null::float8 AS maxaz,
-         false AS was_isolated,
-         ST_Azimuth(ST_StartPoint(newedge.cleangeom),
-                    ST_PointN(newedge.cleangeom, 2)) AS myaz
-  INTO span;
-  IF span.myaz IS NULL THEN
-    RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)';
-  END IF;
-
-  -- Compute azimuth of last edge end on end node
-  SELECT null::int AS nextCW, null::int AS nextCCW,
-         null::float8 AS minaz, null::float8 AS maxaz,
-         false AS was_isolated,
-         ST_Azimuth(ST_EndPoint(newedge.cleangeom),
-                    ST_PointN(newedge.cleangeom,
-                              ST_NumPoints(newedge.cleangeom)-1)) AS myaz
-  INTO epan;
-  IF epan.myaz IS NULL THEN
-    RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)';
-  END IF;
-
-
-  -- 
-  -- Check endpoints existance, match with Curve geometry
-  -- and get face information (if any)
-  --
-  i := 0;
-  FOR rec IN EXECUTE 'SELECT node_id, containing_face, geom FROM '
-    || quote_ident(atopology)
-    || '.node WHERE node_id IN ( '
-    || anode || ',' || anothernode
-    || ')'
-  LOOP
-    IF rec.containing_face IS NOT NULL THEN
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-      RAISE DEBUG  'containing_face for node %:%',
-        rec.node_id, rec.containing_face;
-#endif
-      IF newedge.left_face IS NULL THEN
-        newedge.left_face := rec.containing_face;
-        newedge.right_face := rec.containing_face;
-      ELSE
-        IF newedge.left_face != rec.containing_face THEN
-          RAISE EXCEPTION
-            'SQL/MM Spatial exception - geometry crosses an edge (endnodes in faces % and %)', newedge.left_face, rec.containing_face;
-        END IF;
-      END IF;
-    END IF;
-
-    IF rec.node_id = anode THEN
-      newedge.start_node_geom = rec.geom;
-    END IF;
-
-    IF rec.node_id = anothernode THEN
-      newedge.end_node_geom = rec.geom;
-    END IF;
-
-    i := i + 1;
-  END LOOP;
-
-  IF newedge.start_node_geom IS NULL
-  THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent node';
-  ELSIF NOT ST_Equals(newedge.start_node_geom, ST_StartPoint(acurve))
-  THEN
-    RAISE EXCEPTION
-      'SQL/MM Spatial exception - start node not geometry start point.';
-  END IF;
-
-  IF newedge.end_node_geom IS NULL
-  THEN
-    RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent node';
-  ELSIF NOT ST_Equals(newedge.end_node_geom, ST_EndPoint(acurve))
-  THEN
-    RAISE EXCEPTION
-      'SQL/MM Spatial exception - end node not geometry end point.';
-  END IF;
-
-  --
-  -- Check if this geometry crosses any node
-  --
-  FOR rec IN EXECUTE
-    'SELECT node_id, ST_Relate(geom, $1, 2) as relate FROM '
-    || quote_ident(atopology)
-    || '.node WHERE geom && $1'
-    USING acurve
-  LOOP
-    IF ST_RelateMatch(rec.relate, 'T********') THEN
-      RAISE EXCEPTION 'SQL/MM Spatial exception - geometry crosses a node';
-    END IF;
-  END LOOP;
-
-  --
-  -- Check if this geometry has any interaction with any existing edge
-  --
-  FOR rec IN EXECUTE 'SELECT edge_id, ST_Relate(geom, $1, 2) as im FROM '
-    || quote_ident(atopology)
-    || '.edge_data WHERE geom && $1'
-    USING acurve
-  LOOP
-
-    --RAISE DEBUG 'IM=%',rec.im;
-
-    IF ST_RelateMatch(rec.im, 'F********') THEN
-      CONTINUE; -- no interior intersection
-    END IF;
-
-    IF ST_RelateMatch(rec.im, '1FFF*FFF2') THEN
-      RAISE EXCEPTION
-        'SQL/MM Spatial exception - coincident edge %', rec.edge_id;
-    END IF;
-
-    -- NOT IN THE SPECS: geometry touches an edge
-    IF ST_RelateMatch(rec.im, '1********') THEN
-      RAISE EXCEPTION
-        'Spatial exception - geometry intersects edge %', rec.edge_id;
-    END IF;
-
-    IF ST_RelateMatch(rec.im, 'T********') THEN
-      RAISE EXCEPTION
-        'SQL/MM Spatial exception - geometry crosses edge %', rec.edge_id;
-    END IF;
-
-  END LOOP;
-
-  ---------------------------------------------------------------
-  --
-  -- All checks passed, time to prepare the new edge
-  --
-  ---------------------------------------------------------------
-
-  EXECUTE 'SELECT nextval(' || quote_literal(
-      quote_ident(atopology) || '.edge_data_edge_id_seq') || ')'
-  INTO STRICT newedge.edge_id;
-
-
-  -- Find links on start node -- {
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'My start-segment azimuth: %', span.myaz;
-#endif
-
-  sql :=
-    'SELECT edge_id, -1 AS end_node, start_node, left_face, right_face, '
-    || 'ST_RemoveRepeatedPoints(geom) as geom FROM '
-    || quote_ident(atopology)
-    || '.edge_data WHERE start_node = $1'
-    || ' UNION SELECT edge_id, end_node, -1, left_face, right_face, '
-    || 'ST_RemoveRepeatedPoints(geom) FROM '
-    || quote_ident(atopology)
-    || '.edge_data WHERE end_node = $1';
-  IF newedge.isclosed THEN
-    -- pretend we start elsewhere
-    sql := sql || ' UNION SELECT $2, $3, -1, 0, 0, $4';
-  END IF;
-  i := 0;
-  FOR rec IN EXECUTE sql USING
-    anode, newedge.edge_id, newedge.end_node, newedge.cleangeom
-  LOOP -- incident edges {
-
-    i := i + 1;
-
-    IF rec.start_node = anode THEN
-      --
-      -- Edge starts at our node, we compute
-      -- azimuth from node to its second point
-      --
-      az := ST_Azimuth(ST_StartPoint(rec.geom), ST_PointN(rec.geom, 2));
-
-    ELSE
-      --
-      -- Edge ends at our node, we compute
-      -- azimuth from node to its second-last point
-      --
-      az := ST_Azimuth(ST_EndPoint(rec.geom),
-                       ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1));
-      rec.edge_id := -rec.edge_id;
-
-    END IF;
-
-    IF az IS NULL THEN
-      RAISE EXCEPTION 'Invalid edge % found (no two distinct nodes exist)',
-        rec.edge_id;
-    END IF;
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG 'Edge % - az % (%) - fl:% fr:%',
-      rec.edge_id, az, az - span.myaz, rec.left_face, rec.right_face;
-#endif
-
-    az = az - span.myaz;
-    IF az < 0 THEN
-      az := az + 2*PI();
-    END IF;
-
-    -- RAISE DEBUG ' normalized az %', az;
-
-    IF span.maxaz IS NULL OR az > span.maxaz THEN
-      span.maxaz := az;
-      span.nextCCW := rec.edge_id;
-      IF abs(rec.edge_id) != newedge.edge_id THEN
-        IF rec.edge_id < 0 THEN
-          -- TODO: check for mismatch ?
-          newedge.left_face := rec.left_face;
-        ELSE
-          -- TODO: check for mismatch ?
-          newedge.left_face := rec.right_face;
-        END IF;
-      END IF;
-    END IF;
-
-    IF span.minaz IS NULL OR az < span.minaz THEN
-      span.minaz := az;
-      span.nextCW := rec.edge_id;
-      IF abs(rec.edge_id) != newedge.edge_id THEN
-        IF rec.edge_id < 0 THEN
-          -- TODO: check for mismatch ?
-          newedge.right_face := rec.right_face;
-        ELSE
-          -- TODO: check for mismatch ?
-          newedge.right_face := rec.left_face;
-        END IF;
-      END IF;
-    END IF;
-
-    --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', span.nextCW, span.minaz, span.nextCCW, span.maxaz;
-
-  END LOOP; -- incident edges }
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'span ROW_COUNT: %', i;
-#endif
-  IF newedge.isclosed THEN
-    IF i < 2 THEN span.was_isolated = true; END IF;
-  ELSE
-    IF i < 1 THEN span.was_isolated = true; END IF;
-  END IF;
-
-  IF span.nextCW IS NULL THEN
-    -- This happens if the destination node is isolated
-    newedge.next_right_edge := newedge.edge_id;
-    newedge.prev_left_edge := -newedge.edge_id;
-  ELSE
-    newedge.next_right_edge := span.nextCW;
-    newedge.prev_left_edge := -span.nextCCW;
-  END IF;
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'edge:%', newedge.edge_id;
-#endif
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG ' left:%, next:%, prev:%',
-    newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge;
-#endif
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG ' right:%, next:%, prev:%',
-    newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge;
-#endif
-
-  -- } start_node analysis
-
-
-  -- Find links on end_node {
-      
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'My end-segment azimuth: %', epan.myaz;
-#endif
-
-  sql :=
-    'SELECT edge_id, -1 as end_node, start_node, left_face, right_face, '
-    || 'ST_RemoveRepeatedPoints(geom) as geom FROM '
-    || quote_ident(atopology)
-    || '.edge_data WHERE start_node = $1'
-    || 'UNION SELECT edge_id, end_node, -1, left_face, right_face, '
-    || 'ST_RemoveRepeatedPoints(geom) FROM '
-    || quote_ident(atopology)
-    || '.edge_data WHERE end_node = $1';
-  IF newedge.isclosed THEN
-    -- pretend we end elsewhere (-1)
-    sql := sql || ' UNION SELECT $2, -1, $3, 0, 0, $4';
-  END IF;
-  i := 0;
-  FOR rec IN EXECUTE sql USING
-    anothernode, newedge.edge_id, newedge.start_node, newedge.cleangeom
-  LOOP -- incident edges {
-
-    i := i + 1;
-
-    IF rec.start_node = anothernode THEN
-      --
-      -- Edge starts at our node, we compute
-      -- azimuth from node to its second point
-      --
-      az := ST_Azimuth(ST_StartPoint(rec.geom),
-                       ST_PointN(rec.geom, 2));
-
-    ELSE
-      --
-      -- Edge ends at our node, we compute
-      -- azimuth from node to its second-last point
-      --
-      az := ST_Azimuth(ST_EndPoint(rec.geom),
-        ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1));
-      rec.edge_id := -rec.edge_id;
-
-    END IF;
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG 'Edge % - az % (%)', rec.edge_id, az, az - epan.myaz;
-#endif
-
-    az := az - epan.myaz;
-    IF az < 0 THEN
-      az := az + 2*PI();
-    END IF;
-
-    -- RAISE DEBUG ' normalized az %', az;
-
-    IF epan.maxaz IS NULL OR az > epan.maxaz THEN
-      epan.maxaz := az;
-      epan.nextCCW := rec.edge_id;
-      IF abs(rec.edge_id) != newedge.edge_id THEN
-        IF rec.edge_id < 0 THEN
-          -- TODO: check for mismatch ?
-          newedge.right_face := rec.left_face;
-        ELSE
-          -- TODO: check for mismatch ?
-          newedge.right_face := rec.right_face;
-        END IF;
-      END IF;
-    END IF;
-
-    IF epan.minaz IS NULL OR az < epan.minaz THEN
-      epan.minaz := az;
-      epan.nextCW := rec.edge_id;
-      IF abs(rec.edge_id) != newedge.edge_id THEN
-        IF rec.edge_id < 0 THEN
-          -- TODO: check for mismatch ?
-          newedge.left_face := rec.right_face;
-        ELSE
-          -- TODO: check for mismatch ?
-          newedge.left_face := rec.left_face;
-        END IF;
-      END IF;
-    END IF;
-
-    --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', epan.nextCW, epan.minaz, epan.nextCCW, epan.maxaz;
-
-  END LOOP; -- incident edges }
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'epan ROW_COUNT: %', i;
-#endif
-  IF newedge.isclosed THEN
-    IF i < 2 THEN epan.was_isolated = true; END IF;
-  ELSE
-    IF i < 1 THEN epan.was_isolated = true; END IF;
-  END IF;
-
-  IF epan.nextCW IS NULL THEN
-    -- This happens if the destination node is isolated
-    newedge.next_left_edge := -newedge.edge_id;
-    newedge.prev_right_edge := newedge.edge_id;
-  ELSE
-    newedge.next_left_edge := epan.nextCW;
-    newedge.prev_right_edge := -epan.nextCCW;
-  END IF;
-
-  -- } end_node analysis
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'edge:%', newedge.edge_id;
-#endif
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG ' left:%, next:%, prev:%',
-    newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge;
-#endif
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG ' right:%, next:%, prev:%',
-    newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge;
-#endif
-
-  ----------------------------------------------------------------------
-  --
-  -- If we don't have faces setup by now we must have encountered
-  -- a malformed topology (no containing_face on isolated nodes, no
-  -- left/right faces on adjacent edges or mismatching values)
-  --
-  ----------------------------------------------------------------------
-  IF newedge.left_face != newedge.right_face THEN
-    RAISE EXCEPTION 'Left(%)/right(%) faces mismatch: invalid topology ?', 
-      newedge.left_face, newedge.right_face;
-  END IF;
-  IF newedge.left_face IS NULL THEN
-    RAISE EXCEPTION 'Could not derive edge face from linked primitives: invalid topology ?';
-  END IF;
-
-  ----------------------------------------------------------------------
-  --
-  -- Insert the new edge, and update all linking
-  --
-  ----------------------------------------------------------------------
-
-  -- Insert the new edge with what we have so far
-  EXECUTE 'INSERT INTO ' || quote_ident(atopology) 
-    || '.edge VALUES(' || newedge.edge_id
-    || ',' || newedge.start_node
-    || ',' || newedge.end_node
-    || ',' || newedge.next_left_edge
-    || ',' || newedge.next_right_edge
-    || ',' || newedge.left_face
-    || ',' || newedge.right_face
-    || ',$1)'
-    USING newedge.geom
-    ;
-
-  -- Link prev_left_edge to us 
-  -- (if it's not us already)
-  IF abs(newedge.prev_left_edge) != newedge.edge_id THEN
-    IF newedge.prev_left_edge > 0 THEN
-      -- its next_left_edge is us
-      EXECUTE 'UPDATE ' || quote_ident(atopology)
-        || '.edge_data SET next_left_edge = '
-        || newedge.edge_id
-        || ', abs_next_left_edge = '
-        || newedge.edge_id
-        || ' WHERE edge_id = ' 
-        || newedge.prev_left_edge;
-    ELSE
-      -- its next_right_edge is us
-      EXECUTE 'UPDATE ' || quote_ident(atopology)
-        || '.edge_data SET next_right_edge = '
-        || newedge.edge_id
-        || ', abs_next_right_edge = '
-        || newedge.edge_id
-        || ' WHERE edge_id = ' 
-        || -newedge.prev_left_edge;
-    END IF;
-  END IF;
-
-  -- Link prev_right_edge to us 
-  -- (if it's not us already)
-  IF abs(newedge.prev_right_edge) != newedge.edge_id THEN
-    IF newedge.prev_right_edge > 0 THEN
-      -- its next_left_edge is -us
-      EXECUTE 'UPDATE ' || quote_ident(atopology)
-        || '.edge_data SET next_left_edge = '
-        || -newedge.edge_id
-        || ', abs_next_left_edge = '
-        || newedge.edge_id
-        || ' WHERE edge_id = ' 
-        || newedge.prev_right_edge;
-    ELSE
-      -- its next_right_edge is -us
-      EXECUTE 'UPDATE ' || quote_ident(atopology)
-        || '.edge_data SET next_right_edge = '
-        || -newedge.edge_id
-        || ', abs_next_right_edge = '
-        || newedge.edge_id
-        || ' WHERE edge_id = ' 
-        || -newedge.prev_right_edge;
-    END IF;
-  END IF;
-
-  -- NOT IN THE SPECS...
-  -- set containing_face = null for start_node and end_node
-  -- if they where isolated 
-  IF span.was_isolated OR epan.was_isolated THEN
-      EXECUTE 'UPDATE ' || quote_ident(atopology)
-        || '.node SET containing_face = null WHERE node_id IN ('
-        || anode || ',' || anothernode || ')';
-  END IF;
-
-  --------------------------------------------
-  -- Check face splitting
-  --------------------------------------------
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG 'Checking left face for a split';
-#endif
-  SELECT topology._ST_AddFaceSplit(atopology, newedge.edge_id, newedge.left_face, false)
-  INTO newface;
-  IF newface = 0 THEN
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG ' No split';
-#endif
-    RETURN newedge.edge_id; 
-  END IF;
-
-  IF newface IS NULL THEN -- must be forming a maximal ring in universal face
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG 'Checking right face';
-#endif
-    SELECT topology._ST_AddFaceSplit(atopology, -newedge.edge_id, newedge.left_face, false)
-    INTO newface;
-  ELSE
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG 'Updating right face mbr';
-#endif
-    PERFORM topology._ST_AddFaceSplit(atopology, -newedge.edge_id, newedge.left_face, true);
-  END IF;
-
-  IF newface IS NULL THEN
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-    RAISE DEBUG ' No split';
-#endif
-    RETURN newedge.edge_id; 
-  END IF;
-
-  --------------------------------------------
-  -- Update topogeometries, if needed
-  --------------------------------------------
-
-  IF newedge.left_face != 0 THEN -- {
-
-    -- NOT IN THE SPECS:
-    -- update TopoGeometry compositions to add newface
-    sql := 'SELECT r.topogeo_id, r.layer_id FROM '
-      || quote_ident(atopology)
-      || '.relation r, topology.layer l '
-      || ' WHERE l.topology_id = ' || topoid
-      || ' AND l.level = 0 '
-      || ' AND l.layer_id = r.layer_id '
-      || ' AND r.element_id = ' || newedge.left_face
-      || ' AND r.element_type = 3 ';
-    --RAISE DEBUG 'SQL: %', sql;
-    FOR rec IN EXECUTE sql
-    LOOP
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-      RAISE DEBUG 'TopoGeometry % in layer % contained the face being split (%) - updating to contain also new face %', rec.topogeo_id, rec.layer_id, newedge.left_face, newface;
-#endif
-
-      -- Add reference to the other face
-      sql := 'INSERT INTO ' || quote_ident(atopology)
-        || '.relation VALUES( ' || rec.topogeo_id
-        || ',' || rec.layer_id || ',' || newface || ', 3)';
-      --RAISE DEBUG 'SQL: %', sql;
-      EXECUTE sql;
-
-    END LOOP;
-
-  END IF; -- }
-
-  RETURN newedge.edge_id;
-END
-$$
-LANGUAGE 'plpgsql' VOLATILE;
+       'MODULE_PATHNAME','ST_AddEdgeModFace'
+  LANGUAGE 'c' VOLATILE;
 --} ST_AddEdgeModFace
 
 --{