]> granicus.if.org Git - postgis/commitdiff
Draft topology API in liblwgeom and topology postgresql module.
authorSandro Santilli <strk@keybit.net>
Thu, 25 Jun 2015 16:24:31 +0000 (16:24 +0000)
committerSandro Santilli <strk@keybit.net>
Thu, 25 Jun 2015 16:24:31 +0000 (16:24 +0000)
Includes C implementation of ST_ModEdgeSplit.
Passes the existing topology testsuite while also affecting
functions addNode and toTopoGeom.

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

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

liblwgeom/Makefile.in
liblwgeom/README.topo [new file with mode: 0644]
liblwgeom/liblwgeom_topo.h [new file with mode: 0644]
liblwgeom/liblwgeom_topo_internal.h [new file with mode: 0644]
liblwgeom/lwgeom_topo.c [new file with mode: 0644]
topology/Makefile.in
topology/postgis_topology.c [new file with mode: 0644]
topology/sql/sqlmm.sql.in

index 501bed5d9fb3361b4190ff973a0c6e6d1d96cdc0..89ac905bfe7efffc10e86a06e88a7f1c77798439 100644 (file)
@@ -86,6 +86,7 @@ SA_OBJS = \
        lwgeom_geos_clean.o \
        lwgeom_geos_node.o \
        lwgeom_geos_split.o \
+       lwgeom_topo.o \
        lwgeom_transform.o \
        effectivearea.o \
        varint.o
@@ -110,6 +111,9 @@ SA_HEADERS = \
        liblwgeom_internal.h \
        lwgeodetic.h \
        lwgeodetic_tree.h \
+       liblwgeom_topo.h \
+       liblwgeom_topo_internal.h \
+       lwgeom_log.h \
        lwgeom_geos.h \
        lwgeom_log.h \
        lwgeom_sfcgal.h \
diff --git a/liblwgeom/README.topo b/liblwgeom/README.topo
new file mode 100644 (file)
index 0000000..7d5b265
--- /dev/null
@@ -0,0 +1,26 @@
+About topology support in liblwgeom
+-----------------------------------
+ Author: Sandro Santilli <strk@keybit.net>
+ Last modified: Jun 13, 2015
+
+The topology support in liblwgeom exposes an API to create and manage
+"standard" topologies that use provided callbacks to take care of actual
+data storage.
+
+The topology standard is based on what was provided by PostGIS at its
+version 2.0.0, which in turn is based on ISO SQL/MM (ISO 13249) with
+the addition of the "TopoGeometry" concept.
+
+The public header for topology support is `liblwgeom_topo.h`.
+The caller has to setup a backend interface  (LWT_BE_IFACE) implementing
+all the required callbacks and will then be able to use the provided
+editing functions.
+
+The contract for each callback is fully specified in the header.
+The callbacks are as simple as possible while still allowing for
+backend-specific optimizations.
+
+The backend interface is an opaque object and callabcks are registered
+into it using free functions. This is to allow for modifying the required
+set of callbacks between versions of the library without breaking backward
+compatibility.
diff --git a/liblwgeom/liblwgeom_topo.h b/liblwgeom/liblwgeom_topo.h
new file mode 100644 (file)
index 0000000..a40556a
--- /dev/null
@@ -0,0 +1,880 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#ifndef LIBLWGEOM_TOPO_H
+#define LIBLWGEOM_TOPO_H 1
+
+#include "../postgis_config.h"
+
+#include "liblwgeom.h"
+
+/* INT64 */
+#ifdef _WIN32
+typedef __int64 LWT_INT64;
+#else
+typedef long long int LWT_INT64;
+#endif
+
+/** Identifier of topology element */
+typedef LWT_INT64 LWT_ELEMID;
+
+/*
+ * ISO primitive elements
+ */
+
+/** NODE */
+typedef struct
+{
+  LWT_ELEMID node_id;
+  LWT_ELEMID containing_face; /* -1 if not isolated */
+  LWPOINT *geom;
+}
+LWT_ISO_NODE;
+
+void lwt_iso_node_release(LWT_ISO_NODE* node);
+
+/** Node fields */
+#define LWT_COL_NODE_NODE_ID         1<<0
+#define LWT_COL_NODE_CONTAINING_FACE 1<<1
+#define LWT_COL_NODE_GEOM            1<<2
+#define LWT_COL_NODE_ALL            (1<<3)-1
+
+/** EDGE */
+typedef struct
+{
+  LWT_ELEMID edge_id;
+  LWT_ELEMID start_node;
+  LWT_ELEMID end_node;
+  LWT_ELEMID face_left;
+  LWT_ELEMID face_right;
+  LWT_ELEMID next_left;
+  LWT_ELEMID next_right;
+  LWLINE *geom;
+}
+LWT_ISO_EDGE;
+
+/** Edge fields */
+#define LWT_COL_EDGE_EDGE_ID         1<<0
+#define LWT_COL_EDGE_START_NODE      1<<1
+#define LWT_COL_EDGE_END_NODE        1<<2
+#define LWT_COL_EDGE_FACE_LEFT       1<<3
+#define LWT_COL_EDGE_FACE_RIGHT      1<<4
+#define LWT_COL_EDGE_NEXT_LEFT       1<<5
+#define LWT_COL_EDGE_NEXT_RIGHT      1<<6
+#define LWT_COL_EDGE_GEOM            1<<7
+#define LWT_COL_EDGE_ALL            (1<<8)-1
+
+/** FACE */
+typedef struct
+{
+  LWT_ELEMID face_id;
+  GBOX *mbr;
+}
+LWT_ISO_FACE;
+
+/** Face fields */
+#define LWT_COL_FACE_FACE_ID         1<<0
+#define LWT_COL_FACE_MBR             1<<1
+
+typedef enum LWT_SPATIALTYPE_T {
+  LWT_PUNTAL = 0,
+  LWT_LINEAL = 1,
+  LWT_AREAL = 2,
+  LWT_COLLECTION = 3
+} LWT_SPATIALTYPE;
+
+/*
+ * Backend handling functions
+ */
+
+/* opaque pointers referencing native backend objects */
+
+/**
+ * Backend private data pointer
+ *
+ * Only the backend handler needs to know what it really is.
+ * It will be passed to all registered callback functions.
+ */
+typedef struct LWT_BE_DATA_T LWT_BE_DATA;
+
+/**
+ * Backend interface handler
+ *
+ * Embeds all registered backend callbacks and private data pointer.
+ * Will need to be passed (directly or indirectly) to al public facing
+ * APIs of this library.
+ */
+typedef struct LWT_BE_IFACE_T LWT_BE_IFACE;
+
+/**
+ * Topology handler.
+ *
+ * Embeds backend interface handler.
+ * Will need to be passed to all topology manipulation APIs
+ * of this library.
+ */
+typedef struct LWT_BE_TOPOLOGY_T LWT_BE_TOPOLOGY;
+
+/**
+ * Structure containing base backend callbacks
+ *
+ * Used for registering into the backend iface
+ */
+typedef struct LWT_BE_CALLBACKS_T {
+
+  /**
+   * Read last error message from backend
+   *
+   * @return NULL-terminated error string
+   */
+  const char* (*lastErrorMessage) (const LWT_BE_DATA* be);
+
+  /**
+   * Create a new topology in the backend
+   *
+   * @param name the topology name
+   * @param srid the topology SRID
+   * @param precision the topology precision/tolerance
+   * @param hasZ non-zero if topology primitives should have a Z ordinate
+   * @return a topology handler, which embeds the backend data/params
+   *         or NULL on error (@see lastErrorMessage)
+   */
+  LWT_BE_TOPOLOGY* (*createTopology) (
+    const LWT_BE_DATA* be,
+    const char* name, int srid, double precision, int hasZ
+  );
+
+  /**
+   * Load a topology from the backend
+   *
+   * @param name the topology name
+   * @return a topology handler, which embeds the backend data/params
+   *         or NULL on error (@see lastErrorMessage)
+   */
+  LWT_BE_TOPOLOGY* (*loadTopologyByName) (
+    const LWT_BE_DATA* be,
+    const char* name
+  );
+
+  /**
+   * Release memory associated to a backend topology
+   *
+   * @param topo the backend topology handler
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   */
+  int (*freeTopology) (LWT_BE_TOPOLOGY* topo);
+
+  /**
+   * Get nodes by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of element identifiers
+   * @param numelems input/output parameter, pass number of node identifiers
+   *                 in the input array, gets number of node in output array.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_NODE_* macros
+   *
+   * @return an array of nodes
+   *         or NULL on error (@see lastErrorMessage)
+   *
+   */
+  LWT_ISO_NODE* (*getNodeById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get nodes within distance by point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   * @param dist the distance
+   * @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* (*getNodeWithinDistance2D) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt, double dist, int* numelems,
+      int fields, int limit
+  );
+
+  /**
+   * Insert nodes
+   *
+   * Insert node primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param nodes the nodes 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 nodes array
+   *
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   */
+  int (*insertNodes) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_NODE* nodes,
+      int numelems
+  );
+
+  /**
+   * Get edge by id
+   *
+   * @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 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 or NULL in the following cases:
+   *         - none found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  LWT_ISO_EDGE* (*getEdgeById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get edges within distance by point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   * @param dist the distance
+   * @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* (*getEdgeWithinDistance2D) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt, double dist, int* numelems,
+      int fields, int limit
+  );
+
+  /**
+   * Get next available edge identifier
+   *
+   * Identifiers returned by this function should not be considered
+   * available anymore.
+   *
+   * @param topo the topology to act upon
+   *
+   * @return next available edge identifier or -1 on error
+   */
+  LWT_ELEMID (*getNextEdgeId) (
+      const LWT_BE_TOPOLOGY* topo
+  );
+
+  /**
+   * Insert edges
+   *
+   * Insert edge primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param edges the edges to insert. Those with a edge_id set to -1
+   *              it will be replaced to an automatically assigned identifier
+   * @param nelems number of elements in the edges array
+   *
+   * @return number of inserted edges, or -1 (@see lastErrorMessage)
+   */
+  int (*insertEdges) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_EDGE* edges,
+      int numelems
+  );
+
+  /**
+   * Update edges selected by fields match/mismatch
+   *
+   * @param topo the topology to act upon
+   * @param sel_edge an LWT_ISO_EDGE object with selecting fields set.
+   * @param sel_fields fields used to select edges to be updated,
+   *                   see LWT_COL_EDGE_* macros
+   * @param upd_edge an LWT_ISO_EDGE object with updated fields set.
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see LWT_COL_EDGE_* macros
+   * @param exc_edge an LWT_ISO_EDGE object with exclusion fields set,
+   *                 can be NULL if no exlusion condition exists.
+   * @param exc_fields fields used for excluding edges from the update,
+   *                   see LWT_COL_EDGE_* macros
+   *
+   * @return number of edges being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateEdges) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_EDGE* sel_edge, int sel_fields,
+      const LWT_ISO_EDGE* upd_edge, int upd_fields,
+      const LWT_ISO_EDGE* exc_edge, int exc_fields
+  );
+
+  /**
+   * Get faces by id
+   *
+   * @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.
+   * @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)
+   */
+  LWT_ISO_FACE* (*getFaceById) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get face containing point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   * @param numelems input/output parameter, pass number of edge identifiers
+   *                 in the input array, gets number of node in output array.
+   * @param fields fields to be filled in the returned structure, see
+   *               LWT_COL_FACE_* macros
+   *
+   * @return a face identifier, -1 if point is on a topology edge
+   *         or -2 on error (@see lastErrorMessage)
+   */
+  LWT_ELEMID (*getFaceContainingPoint) (
+      const LWT_BE_TOPOLOGY* topo,
+      const LWPOINT* pt
+  );
+
+  /**
+   * Update TopoGeometry objects after an edge split event
+   *
+   * @param topo the topology to act upon
+   * @param split_edge identifier of the edge that was splitted.
+   * @param new_edge1 identifier of the first new edge that was created
+   *        as a result of edge splitting.
+   * @param new_edge2 identifier of the second new edge that was created
+   *        as a result of edge splitting, or -1 if the old edge was
+   *        modified rather than replaced.
+   *
+        * @return 1 on success, 0 on error
+   *
+   * @note on splitting an edge, the new edges both have the
+   *       same direction as the original one. If a second new edge was
+   *       created, its start node will be equal to the first new edge
+   *       end node.
+   */
+  int (*updateTopoGeomEdgeSplit) (
+      const LWT_BE_TOPOLOGY* topo,
+      LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2
+  );
+
+} LWT_BE_CALLBACKS;
+
+
+/**
+ * Create a new backend interface
+ *
+ * Ownership to caller delete with lwt_FreeBackendIface
+ *
+ * @param data Backend data, passed as first parameter to all callback functions
+ */
+LWT_BE_IFACE* lwt_CreateBackendIface(const LWT_BE_DATA* data);
+
+/**
+ * Register backend callbacks into the opaque iface handler
+ *
+ * @param iface the backend interface handler (see lwt_CreateBackendIface)
+ * @param cb a pointer to the callbacks structure; ownership left to caller.
+ */
+void lwt_BackendIfaceRegisterCallbacks(LWT_BE_IFACE* iface, const LWT_BE_CALLBACKS* cb);
+
+/** Release memory associated with an LWT_BE_IFACE */
+void lwt_FreeBackendIface(LWT_BE_IFACE* iface);
+
+/********************************************************************
+ *
+ * End of BE interface
+ *
+ *******************************************************************/
+
+
+/** Element of a TopoGeometry */
+typedef struct LWT_TOPOELEMENT_T {
+  LWT_ELEMID id; /* primitive or topogeometry id */
+  /** primitive type (0:node, 1:edge, 2:face) or layer id */
+  int type;
+} LWT_TOPOELEMENT;
+
+/**
+ * Topology errors type
+ */
+typedef enum LWT_TOPOERR_TYPE_T {
+  LWT_TOPOERR_EDGE_CROSSES_NODE,
+  LWT_TOPOERR_EDGE_INVALID,
+  LWT_TOPOERR_EDGE_NOT_SIMPLE,
+  LWT_TOPOERR_EDGE_CROSSES_EDGE,
+  LWT_TOPOERR_EDGE_STARTNODE_MISMATCH,
+  LWT_TOPOERR_EDGE_ENDNODE_MISMATCH,
+  LWT_TOPOERR_FACE_WITHOUT_EDGES,
+  LWT_TOPOERR_FACE_HAS_NO_RINGS,
+  LWT_TOPOERR_FACE_OVERLAPS_FACE,
+  LWT_TOPOERR_FACE_WITHIN_FACE
+} LWT_TOPOERR_TYPE;
+
+/** Topology error */
+typedef struct LWT_TOPOERR_T {
+  /** Type of error */
+  LWT_TOPOERR_TYPE err;
+  /** Identifier of first affected element */
+  LWT_ELEMID elem1;
+  /** Identifier of second affected element (0 if inapplicable) */
+  LWT_ELEMID elem2;
+} LWT_TOPOERR;
+
+/*
+ * Topology functions
+ */
+
+/** Opaque topology structure
+ *
+ * Embeds backend interface and topology
+ */
+typedef struct LWT_TOPOLOGY_T LWT_TOPOLOGY;
+
+
+/*******************************************************************
+ *
+ * Non-ISO signatures here
+ *
+ *******************************************************************/
+
+/**
+ * Initializes a new topology in the database
+ *
+ * Topology support must be already enabled for the database
+ * (see lwt_EnableTopology).
+ */
+LWT_TOPOLOGY *lwt_CreateTopology(LWT_BE_IFACE *iface, const char *name,
+                        int srid, double prec, int hasz);
+
+/**
+ * Loads an existing topology by name from the database
+ *
+ * @return the handler of the topology, or NULL on error
+ *         (liblwgeom error handler will be invoked with error message)
+ */
+LWT_TOPOLOGY *lwt_LoadTopology(LWT_BE_IFACE *iface, const char *name);
+
+/**
+ * Drop a topology and all its associated objects from the database
+ */
+void lwt_DropTopology(LWT_TOPOLOGY* topo);
+
+/** Release memory associated with an LWT_TOPOLOGY */
+void lwt_FreeTopology(LWT_TOPOLOGY* topo);
+
+/*******************************************************************
+ *
+ * Topology population (non-ISO)
+ *
+ *******************************************************************/
+
+/**
+ * Adds a point to the topology
+ *
+ * The given point will snap to existing nodes or edges within given
+ * tolerance. An existing edge may be split by the point.
+ *
+ * @param topo the topology to operate on
+ * @param point the point to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ *
+ * @return identifier of added (or pre-existing) node
+ */
+LWT_ELEMID lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol);
+
+/**
+ * Adds a linestring to the topology
+ *
+ * The given line will snap to existing nodes or edges within given
+ * tolerance. Existing edges or faces may be split by the line.
+ *
+ * @param topo the topology to operate on
+ * @param line the line to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ * @param nedges output parameter, will be set to number of edges the
+ *               line was split into
+ *
+ * @return an array of <nedges> edge identifiers that sewed togheter
+ *         will build up the input linestring (after snapping). Caller
+ *         will need to free the array using lwfree()
+ */
+LWT_ELEMID* lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol,
+                        int* nedges);
+
+/**
+ * Adds a polygon to the topology
+ *
+ * The boundary of the given polygon will snap to existing nodes or
+ * edges within given tolerance.
+ * Existing edges or faces may be split by the boundary of the polygon.
+ *
+ * @param topo the topology to operate on
+ * @param poly the polygon to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ * @param nfaces output parameter, will be set to number of faces the
+ *               polygon was split into
+ *
+ * @return an array of <nfaces> face identifiers that sewed togheter
+ *         will build up the input polygon (after snapping). Caller
+ *         will need to free the array using lwfree()
+ */
+LWT_ELEMID* lwt_AddPolygon(LWT_TOPOLOGY* topo, LWPOLY* point, double tol,
+                        int* nfaces);
+
+/**
+ * Adds a geometry to the topology
+ *
+ * The given geometry will snap to existing nodes or
+ * edges within given tolerance.
+ * Existing edges or faces may be split by the operation.
+ *
+ * @param topo the topology to operate on
+ * @param geom the geometry to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ * @param nelems output parameter, will be set to number of primitive
+ *               elements the geometry was split into.
+ *
+ * @return an array of <nelems> topoelements that taken togheter
+ *         will build up the input geometry (after snapping). Caller
+ *         will need to free the array using lwfree()
+ *
+ * @see lwt_CreateTopoGeom
+ */
+LWT_TOPOELEMENT* lwt_AddGeometry(LWT_TOPOLOGY* topo, LWGEOM* geom,
+                                 double tol, int* nelems);
+
+/*******************************************************************
+ *
+ * TopoGeometry management
+ *
+ *******************************************************************/
+
+/**
+ * Add a TopoGeometry layer
+ *
+ * Registers a new topology layer into the layers registry.
+ * It is up to the caller to create the column(s) required to hold
+ * the TopoGeometry reference identifiers.
+ *
+ * @param topo the topology to operate on
+ * @param schema name of schema, or NULL if unsupported
+ * @param table name of table
+ * @param colname name of column (or column prefix if complex
+ *                types are unsupported)
+ * @param type spatial type of the geometries in the layer
+ * @param child identifier of child layer, or -1 for non-hierarchical
+ * @return a layer_id
+ */
+int lwt_AddTopoGeometryLayer(LWT_TOPOLOGY* topo, const char *tablename,
+                             const char *colname, LWT_SPATIALTYPE type,
+                             int child);
+
+/**
+ * Drop a TopoGeometry layer
+ *
+ * Unregister the layer from the registry.
+ * It is up to the caller to drop the deploy column(s).
+ *
+ * @param topo the topology to operate on
+ * @param layer_id identifier of the layer to drop
+ */
+void lwt_DropTopoGeometryLayer(LWT_TOPOLOGY* topo, int layer_id);
+
+/**
+ * Create a TopoGeometry in a layer from a list of elements
+ *
+ * Populates the relation table in the topology (if nelems > 0).
+ *
+ * Returns the identifier of the TopoGeometry, which is unique within
+ * its topology and layer (identified by layer_id).
+ *
+ * @param topo the topology to operate on
+ * @param layer_id identifier of the layer the TopoGeometry object
+ *                 belongs to.
+ * @param type spatial type of the geometry
+ * @param nelems number of elements composing the TopoGeometry
+ * @param elems elements composing the TopoGeometry
+ *
+ * @return the TopoGeometry identifier (valid within the topology and layer)
+ */
+LWT_ELEMID lwt_CreateTopoGeom(LWT_TOPOLOGY* topo, int layer_id,
+                              LWT_SPATIALTYPE type, int nelems,
+                              LWT_TOPOELEMENT* elems);
+
+/**
+ * Return the Geometry of a TopoGeometry in a layer
+ *
+ * @param topo the topology to operate on
+ * @param layer_id identifier of the TopoGeometry layer
+ * @param topogeom_id identifier of the TopoGeometry object
+ *
+ * @return an LWGEOM, ownership to caller, use lwgeom_release to free up
+ *
+ */
+LWGEOM* lwt_TopoGeomGeometry(LWT_TOPOLOGY* topo, int layer_id,
+                             LWT_ELEMID topogeom_id);
+
+/*******************************************************************
+ *
+ * ISO signatures here
+ *
+ *******************************************************************/
+
+/**
+ * Populate an empty topology with data from a simple geometry
+ *
+ * For ST_CreateTopoGeo
+ *
+ * @param topo the topology to operate on
+ * @param geom the geometry to import
+ *
+ */
+void lwt_CreateTopoGeo(LWT_TOPOLOGY* topo, LWGEOM *geom);
+
+/**
+ * Add an isolated node
+ *
+ * For ST_AddIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param face the identifier of containing face or -1 for "unknown"
+ * @param pt the node position
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (coincident nodes, crossing edges,
+ *                    actual face containement)
+ *
+ * @return ID of the newly added node
+ *
+ */
+LWT_ELEMID lwt_AddIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID face,
+                          LWPOINT* pt, int skipChecks);
+
+/**
+ * Move an isolated node
+ *
+ * For ST_MoveIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param node the identifier of the nod to be moved
+ * @param pt the new node position
+ *
+ */
+void lwt_MoveIsoNode(LWT_TOPOLOGY* topo,
+                     LWT_ELEMID node, LWPOINT* pt);
+
+/**
+ * Remove an isolated node
+ *
+ * For ST_RemoveIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param node the identifier of the nod to be moved
+ *
+ */
+void lwt_RemoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID node);
+
+/**
+ * Add an isolated edge connecting two existing isolated nodes
+ *
+ * For ST_AddIsoEdge
+ *
+ * @param topo the topology to operate on
+ * @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
+ *
+ */
+LWT_ELEMID lwt_AddIsoEdge(LWT_TOPOLOGY* topo,
+                          LWT_ELEMID startNode, LWT_ELEMID endNode,
+                          LWLINE *geom);
+
+/**
+ * Add a new edge possibly splitting a face (modifying it)
+ *
+ * For ST_AddEdgeModFace
+ *
+ * If the new edge splits a face, the face is shrinked and a new one
+ * is created. Unless the face being split is the Universal Face, the
+ * new face will be on the right side of the newly added edge.
+ *
+ * @param topo the topology to operate on
+ * @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
+ *
+ */
+LWT_ELEMID lwt_AddEdgeModFace(LWT_TOPOLOGY* topo,
+                              LWT_ELEMID start_node, LWT_ELEMID end_node,
+                              LWLINE *geom);
+
+/**
+ * Add a new edge possibly splitting a face (replacing with two new faces)
+ *
+ * For ST_AddEdgeNewFaces
+ *
+ * If the new edge splits a face, the face is replaced by two new faces.
+ *
+ * @param topo the topology to operate on
+ * @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
+ *
+ */
+LWT_ELEMID lwt_AddEdgeNewFaces(LWT_TOPOLOGY* topo,
+                              LWT_ELEMID start_node, LWT_ELEMID end_node,
+                              LWLINE *geom);
+
+/**
+ * Remove an edge, possibly merging two faces (replacing both with a new one)
+ *
+ * For ST_RemEdgeNewFace
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be removed
+ * @return the id of newly created face or -1 if no new face was created
+ *
+ */
+LWT_ELEMID lwt_RemEdgeNewFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge);
+
+/**
+ * Remove an edge, possibly merging two faces (replacing one with the other)
+ *
+ * For ST_RemEdgeModFace
+ *
+ * Preferentially keep the face on the right, to be symmetric with
+ * lwt_AddEdgeModFace.
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be removed
+ * @return the id of newly created face or -1 if no new face was created
+ *
+ */
+LWT_ELEMID lwt_RemEdgemodFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge);
+
+/**
+ * Changes the shape of an edge without affecting the topology structure.
+ *
+ * For ST_ChangeEdgeGeom
+ *
+ * @param topo the topology to operate on
+ * @param geom the edge geometry
+ *
+ */
+void lwt_ChangeEdgeGeom(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWGEOM* geom);
+
+/**
+ * Split an edge by a node, modifying the original edge and adding a new one.
+ *
+ * For ST_ModEdgeSplit
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be split
+ * @param pt geometry of the new node
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (coincident node)
+ * @return the id of newly created node, or -1 on error
+ *         (liblwgeom error handler will be invoked with error message)
+ *
+ */
+LWT_ELEMID lwt_ModEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipChecks);
+
+/**
+ * Split an edge by a node, replacing it with two new edges
+ *
+ * For ST_NewEdgesSplit
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be split
+ * @param pt geometry of the new node
+ * @return the id of newly created node
+ *
+ */
+LWT_ELEMID lwt_NewEdgesSplit(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt);
+
+/**
+ * Merge two edges, modifying the first and deleting the second
+ *
+ * For ST_ModEdgeHeal
+ *
+ * @param topo the topology to operate on
+ * @param e1 identifier of first edge
+ * @param e2 identifier of second edge
+ * @return the id of the removed node
+ *
+ */
+LWT_ELEMID lwt_ModEdgeHeal(LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2);
+
+/**
+ * Return the list of directed edges bounding a face
+ *
+ * For ST_GetFaceEdges
+ *
+ * @param topo the topology to operate on
+ * @param face identifier of the face
+ * @param edges will be set to an array of signed edge identifiers, will
+ *              need to be released with lwfree
+ * @return the number of edges in the edges array
+ *
+ */
+int lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWT_ELEMID **edges);
+
+/**
+ * Return the geometry of a face
+ *
+ * For ST_GetFaceGeometry
+ *
+ * @param topo the topology to operate on
+ * @param face identifier of the face
+ * @return a polygon geometry representing the face, ownership to caller,
+ *         to be released with lwgeom_release
+ */
+LWGEOM* lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID face);
+
+#endif /* LIBLWGEOM_TOPO_H */
diff --git a/liblwgeom/liblwgeom_topo_internal.h b/liblwgeom/liblwgeom_topo_internal.h
new file mode 100644 (file)
index 0000000..aea576f
--- /dev/null
@@ -0,0 +1,75 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#ifndef LIBLWGEOM_TOPO_INTERNAL_H
+#define LIBLWGEOM_TOPO_INTERNAL_H 1
+
+#include "../postgis_config.h"
+
+#include "liblwgeom.h"
+#include "liblwgeom_topo.h"
+
+/************************************************************************
+ *
+ * Generic SQL handler
+ *
+ ************************************************************************/
+
+struct LWT_BE_IFACE_T
+{
+  const LWT_BE_DATA *data;
+  const LWT_BE_CALLBACKS *cb;
+};
+
+const char* lwt_be_lastErrorMessage(const LWT_BE_IFACE* be);
+
+LWT_BE_TOPOLOGY * lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name);
+
+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);
+
+int lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt);
+int lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems);
+
+int lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt);
+
+LWT_ELEMID lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo);
+LWT_ISO_EDGE* lwt_be_getEdgeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                               int* numelems, int fields);
+LWT_ISO_EDGE* lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit);
+int
+lwt_be_insertEdges(LWT_TOPOLOGY* topo, LWT_ISO_EDGE* edge, int numelems);
+int
+lwt_be_updateEdges(LWT_TOPOLOGY* topo, const LWT_ISO_EDGE* sel_edge, int sel_fields, const LWT_ISO_EDGE* upd_edge, int upd_fields, const LWT_ISO_EDGE* exc_edge, int exc_fields);
+
+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);
+
+/************************************************************************
+ *
+ * Internal objects
+ *
+ ************************************************************************/
+
+struct LWT_TOPOLOGY_T
+{
+  const LWT_BE_IFACE *be_iface;
+  LWT_BE_TOPOLOGY *be_topo;
+  char *name;
+  char *table_prefix;
+};
+
+#endif /* LIBLWGEOM_TOPO_INTERNAL_H */
diff --git a/liblwgeom/lwgeom_topo.c b/liblwgeom/lwgeom_topo.c
new file mode 100644 (file)
index 0000000..75313f1
--- /dev/null
@@ -0,0 +1,473 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+
+#include "liblwgeom_internal.h"
+#include "liblwgeom_topo_internal.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+/*********************************************************************
+ *
+ * Backend iface
+ *
+ ********************************************************************/
+
+LWT_BE_IFACE* lwt_CreateBackendIface(const LWT_BE_DATA *data)
+{
+  LWT_BE_IFACE *iface = lwalloc(sizeof(LWT_BE_IFACE));
+  iface->data = data;
+  iface->cb = NULL;
+  return iface;
+};
+
+void lwt_BackendIfaceRegisterCallbacks(LWT_BE_IFACE *iface,
+                                       const LWT_BE_CALLBACKS* cb)
+{
+  iface->cb = cb;
+};
+
+void lwt_FreeBackendIface(LWT_BE_IFACE* iface)
+{
+  lwfree(iface);
+}
+
+/*********************************************************************
+ *
+ * Backend wrappers
+ *
+ ********************************************************************/
+
+#define CHECKCB(be, method) do { \
+  if ( ! (be)->cb || ! (be)->cb->method ) \
+  lwerror("Callback " # method " not registered by backend"); \
+} while (0)
+
+#define CB0(be, method) \
+  CHECKCB(be, method);\
+  return (be)->cb->method((be)->data)
+
+#define CB1(be, method, a1) \
+  CHECKCB(be, method);\
+  return (be)->cb->method((be)->data, a1)
+
+#define CBT0(to, method) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo)
+
+#define CBT1(to, method, a1) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1)
+
+#define CBT2(to, method, a1, a2) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2)
+
+#define CBT3(to, method, a1, a2, a3) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3)
+
+#define CBT4(to, method, a1, a2, a3, a4) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4)
+
+#define CBT5(to, method, a1, a2, a3, a4, a5) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5)
+
+#define CBT6(to, method, a1, a2, a3, a4, a5, a6) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5, a6)
+
+const char *
+lwt_be_lastErrorMessage(const LWT_BE_IFACE* be)
+{
+  CB0(be, lastErrorMessage);
+}
+
+LWT_BE_TOPOLOGY *
+lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name)
+{
+  CB1(be, loadTopologyByName, name);
+}
+
+int
+lwt_be_freeTopology(LWT_TOPOLOGY *topo)
+{
+  CBT0(topo, freeTopology);
+}
+
+LWT_ISO_NODE*
+lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit)
+{
+  CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit);
+}
+
+int
+lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems)
+{
+  CBT2(topo, insertNodes, node, numelems);
+}
+
+LWT_ELEMID
+lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo)
+{
+  CBT0(topo, getNextEdgeId);
+}
+
+LWT_ISO_EDGE*
+lwt_be_getEdgeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getEdgeById, ids, numelems, fields);
+}
+
+LWT_ISO_EDGE*
+lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit)
+{
+  CBT5(topo, getEdgeWithinDistance2D, pt, dist, numelems, fields, limit);
+}
+
+int
+lwt_be_insertEdges(LWT_TOPOLOGY* topo, LWT_ISO_EDGE* edge, int numelems)
+{
+  CBT2(topo, insertEdges, edge, numelems);
+}
+
+int
+lwt_be_updateEdges(LWT_TOPOLOGY* topo,
+  const LWT_ISO_EDGE* sel_edge, int sel_fields,
+  const LWT_ISO_EDGE* upd_edge, int upd_fields,
+  const LWT_ISO_EDGE* exc_edge, int exc_fields
+)
+{
+  CBT6(topo, updateEdges, sel_edge, sel_fields,
+                          upd_edge, upd_fields,
+                          exc_edge, exc_fields);
+}
+
+LWT_ELEMID
+lwt_be_getFaceContainingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt)
+{
+  CBT1(topo, getFaceContainingPoint, pt);
+}
+
+
+int
+lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2)
+{
+  CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2);
+}
+
+/* wrappers of be wrappers... */
+
+int
+lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt)
+{
+  int exists = 0;
+  lwt_be_getNodeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
+  return exists;
+}
+
+int
+lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt)
+{
+  int exists = 0;
+  lwt_be_getEdgeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
+  return exists;
+}
+
+/************************************************************************
+ *
+ * API implementation
+ *
+ ************************************************************************/
+
+LWT_TOPOLOGY *lwt_LoadTopology(LWT_BE_IFACE *iface, const char *name)
+{
+  LWT_BE_TOPOLOGY* be_topo;
+  LWT_TOPOLOGY* topo;
+
+  be_topo = lwt_be_loadTopologyByName(iface, name);
+  if ( ! be_topo ) {
+    //lwerror("Could not load topology from backend: %s",
+    lwerror("%s", lwt_be_lastErrorMessage(iface));
+    return NULL;
+  }
+  topo = lwalloc(sizeof(LWT_TOPOLOGY));
+  topo->be_iface = iface;
+  topo->be_topo = be_topo;
+  topo->name = NULL; /* don't want to think about it now.. */
+  topo->table_prefix = NULL; /* don't want to think about it now */
+
+  return topo;
+}
+
+void
+lwt_FreeTopology(LWT_TOPOLOGY* topo)
+{
+  if ( ! lwt_be_freeTopology(topo) ) {
+    lwnotice("Could not release backend topology memory: %s",
+            lwt_be_lastErrorMessage(topo->be_iface));
+  }
+  lwfree(topo);
+}
+
+
+LWT_ELEMID lwt_AddIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWPOINT* pt,
+                          int skipISOChecks)
+{
+  LWT_ELEMID foundInFace = -1;
+
+  if ( ! skipISOChecks )
+  {
+    if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
+    {
+      lwerror("SQL/MM Spatial exception - coincident node");
+      return -1;
+    }
+    if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) ) /*x*/
+    {
+      lwerror("SQL/MM Spatial exception - edge crosses node");
+      return -1;
+    }
+  }
+
+  if ( face == -1 || ! skipISOChecks )
+  {
+    foundInFace = lwt_be_getFaceContainingPoint(topo, pt); /*x*/
+    if ( foundInFace == -2 ) {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    if ( foundInFace == -1 ) {
+      lwerror("SQL/MM Spatial exception - edge crosses node");
+      return -1;
+    }
+  }
+
+  if ( face == -1 ) {
+    face = foundInFace;
+  }
+  else if ( ! skipISOChecks && foundInFace != face ) {
+    lwerror("SQL/MM Spatial exception - within face % (not %)",
+            foundInFace, face);
+    return -1;
+  }
+
+  LWT_ISO_NODE node;
+  node.node_id = -1;
+  node.containing_face = face;
+  node.geom = pt;
+  if ( ! lwt_be_insertNodes(topo, &node, 1) )
+  {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  return node.node_id;
+}
+
+LWT_ELEMID lwt_ModEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipISOChecks)
+{
+  LWT_ISO_NODE node;
+  LWT_ISO_EDGE* oldedge;
+  LWGEOM *split;
+  LWCOLLECTION *split_col;
+  const LWGEOM *oldedge_geom;
+  const LWGEOM *newedge_geom;
+  LWT_ISO_EDGE newedge1;
+  LWT_ISO_EDGE seledge, updedge, excedge;
+  int i, ret;
+
+  /* Get edge */
+  i = 1;
+  LWDEBUG(1, "lwt_ModEdgeSplit: calling lwt_be_getEdgeById");
+  oldedge = lwt_be_getEdgeById(topo, &edge, &i, LWT_COL_EDGE_ALL);
+  LWDEBUGF(1, "lwt_ModEdgeSplit: lwt_be_getEdgeById returned %p", oldedge);
+  if ( ! oldedge )
+  {
+    LWDEBUGF(1, "lwt_ModEdgeSplit: "
+                "lwt_be_getEdgeById returned NULL and set i=%d", i);
+    if ( i == -1 )
+    {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    else if ( i == 0 )
+    {
+      lwerror("SQL/MM Spatial exception - non-existent edge");
+      return -1;
+    }
+    else
+    {
+      lwerror("Backend coding error: getEdgeById callback returned NULL "
+              "but numelements output parameter has value %d "
+              "(expected 0 or 1)", i);
+    }
+  }
+
+
+  /*
+   *  - check if a coincident node already exists
+   */
+  if ( ! skipISOChecks )
+  {
+    LWDEBUG(1, "lwt_ModEdgeSplit: calling lwt_be_ExistsCoincidentNode");
+    if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
+    {
+      LWDEBUG(1, "lwt_ModEdgeSplit: lwt_be_ExistsCoincidentNode returned");
+      lwerror("SQL/MM Spatial exception - coincident node");
+      return -1;
+    }
+    LWDEBUG(1, "lwt_ModEdgeSplit: lwt_be_ExistsCoincidentNode returned");
+  }
+
+  /* Split edge */
+  split = lwgeom_split((LWGEOM*)oldedge->geom, (LWGEOM*)pt);
+  if ( ! split )
+  {
+    lwerror("could not split edge by point ?");
+    return -1;
+  }
+  split_col = lwgeom_as_lwcollection(split);
+  if ( ! split_col ) {
+    lwerror("lwgeom_as_lwcollection returned NULL");
+    return -1;
+  }
+  if (split_col->ngeoms < 2) {
+    lwgeom_release(split);
+    lwerror("SQL/MM Spatial exception - point not on edge");
+    return -1;
+  }
+  oldedge_geom = split_col->geoms[0];
+  newedge_geom = split_col->geoms[1];
+
+  /* Add new node, getting new id back */
+  node.node_id = -1;
+  node.containing_face = -1; /* means not-isolated */
+  node.geom = pt;
+  if ( ! lwt_be_insertNodes(topo, &node, 1) )
+  {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if (node.node_id == -1) {
+    /* should have been set by backend */
+    lwerror("Backend coding error: "
+            "insertNodes callback did not return node_id");
+    return -1;
+  }
+
+  /* Insert the new edge */
+  newedge1.edge_id = lwt_be_getNextEdgeId(topo);
+  if ( newedge1.edge_id == -1 ) {
+    lwgeom_release(split);
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  newedge1.start_node = node.node_id;
+  newedge1.end_node = oldedge->end_node;
+  newedge1.face_left = oldedge->face_left;
+  newedge1.face_right = oldedge->face_right;
+  newedge1.next_left = oldedge->next_left == -oldedge->edge_id ?
+      -newedge1.edge_id : oldedge->next_left;
+  newedge1.next_right = -oldedge->edge_id;
+  newedge1.geom = lwgeom_as_lwline(newedge_geom);
+  /* lwgeom_split of a line should only return lines ... */
+  if ( ! newedge1.geom ) {
+    lwgeom_release(split);
+    lwerror("first geometry in lwgeom_split output is not a line");
+    return -1;
+  }
+  ret = lwt_be_insertEdges(topo, &newedge1, 1);
+  if ( ret == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    lwgeom_release(split);
+    lwerror("Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  /* Update the old edge */
+  updedge.geom = lwgeom_as_lwline(oldedge_geom);
+  /* lwgeom_split of a line should only return lines ... */
+  if ( ! updedge.geom ) {
+    lwgeom_release(split);
+    lwerror("second geometry in lwgeom_split output is not a line");
+    return -1;
+  }
+  updedge.next_left = newedge1.edge_id;
+  updedge.end_node = node.node_id;
+  ret = lwt_be_updateEdges(topo,
+      oldedge, LWT_COL_EDGE_EDGE_ID,
+      &updedge, LWT_COL_EDGE_GEOM|LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
+      NULL, 0);
+  if ( ret == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    lwerror("Edge being splitted (%d) disappeared during operations?", oldedge->edge_id);
+    return -1;
+  } else if ( ret > 1 ) {
+    lwerror("More than a single edge found with id %d !", oldedge->edge_id);
+    return -1;
+  }
+
+  /* Update all next edge references to match new layout (ST_ModEdgeSplit) */
+
+  updedge.next_right = -newedge1.edge_id;
+  excedge.edge_id = newedge1.edge_id;
+  seledge.next_right = -oldedge->edge_id;
+  seledge.start_node = oldedge->end_node;
+  ret = lwt_be_updateEdges(topo,
+      &seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE,
+      &updedge, LWT_COL_EDGE_NEXT_RIGHT,
+      &excedge, LWT_COL_EDGE_EDGE_ID);
+  if ( ret == -1 ) {
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  updedge.next_left = -newedge1.edge_id;
+  excedge.edge_id = newedge1.edge_id;
+  seledge.next_left = -oldedge->edge_id;
+  seledge.end_node = oldedge->end_node;
+  ret = lwt_be_updateEdges(topo,
+      &seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
+      &updedge, LWT_COL_EDGE_NEXT_LEFT,
+      &excedge, LWT_COL_EDGE_EDGE_ID);
+  if ( ret == -1 ) {
+    lwgeom_release(split);
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Update TopoGeometries composition */
+  ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedge1.edge_id, -1);
+  if ( ! ret ) {
+    lwgeom_release(split);
+    lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  lwgeom_release(split);
+
+  /* return new node id */
+  return node.node_id;
+}
index 4132421fe5017a1f52993817559afac304d175ee..4ae5a950b9fc7e2c1ce1e06768ad506417c99911 100644 (file)
@@ -18,9 +18,8 @@
 
 POSTGIS_PGSQL_VERSION=@POSTGIS_PGSQL_VERSION@
 
-# NOTE: we can't use MODULE_big or PGXS insists in building a library...
-PGIS_MODULE_big=postgis-@POSTGIS_MAJOR_VERSION@.@POSTGIS_MINOR_VERSION@
-MODULEDIR=contrib/$(PGIS_MODULE_big)
+MODULE_big=postgis_topology-@POSTGIS_MAJOR_VERSION@.@POSTGIS_MINOR_VERSION@
+MODULEDIR=contrib/$(MODULE_big)
 
 # Files to be copied to the contrib/ directory
 DATA_built=topology.sql topology_upgrade.sql uninstall_topology.sql
@@ -35,6 +34,26 @@ SQL_OBJS = \
   topology_drop_before.sql \
   topology_drop_after.sql
 
+# Objects to build using PGXS
+OBJS = postgis_topology.o
+
+# Libraries to link into the module (proj, geos)
+#
+# Note: we specify liblwgeom.a directly in SHLIB_LINK rather than using
+# -L... -l options to prevent issues with some platforms trying to link
+# to an existing liblwgeom.so in the PostgreSQL $libdir supplied by an
+# older version of PostGIS, rather than with the static liblwgeom.a
+# supplied with newer versions of PostGIS
+PG_CPPFLAGS += -I../liblwgeom -I../libpgcommon @CPPFLAGS@ -fPIC
+SHLIB_LINK_F = ../libpgcommon/libpgcommon.a ../liblwgeom/.libs/liblwgeom.a @SHLIB_LINK@
+
+# Add SFCGAL Flags if defined
+ifeq (@SFCGAL@,sfcgal)
+PG_CPPFLAGS += @SFCGAL_CPPFLAGS@
+SHLIB_LINK_F += @SFCGAL_LDFLAGS@
+endif
+
+
 # Extra files to remove during 'make clean'
 EXTRA_CLEAN=$(SQL_OBJS) topology_upgrade.sql.in
 
@@ -49,6 +68,12 @@ include $(PGXS)
 # Set PERL _after_ the include of PGXS
 PERL=@PERL@
 
+# This is to workaround a bug in PGXS 8.4 win32 link line,
+# see http://trac.osgeo.org/postgis/ticket/1158#comment:57
+SHLIB_LINK := $(SHLIB_LINK_F) $(SHLIB_LINK)
+
+$(OBJS): ../liblwgeom/.libs/liblwgeom.a ../libpgcommon/libpgcommon.a ../postgis_config.h
+
 # If REGRESS=1 passed as a parameter, change the default install paths
 # so that no prefix is included. This allows us to relocate to a temporary
 # directory for regression testing.
@@ -63,7 +88,8 @@ endif
 
 # Generate any .sql file from .sql.in.c files by running them through the SQL pre-processor 
 %.sql: %.sql.in
-       $(SQLPP) $< | grep -v '^#' > $@
+       $(SQLPP) $< | grep -v '^#' | \
+       $(PERL) -lpe "s'MODULE_PATHNAME'\$$libdir/postgis_topology-@POSTGIS_MAJOR_VERSION@.@POSTGIS_MINOR_VERSION@'g" > $@
 
 #Generate upgrade script by stripping things that can't be reinstalled
 #e.g. don't bother with tables, types, triggers, and domains
diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c
new file mode 100644 (file)
index 0000000..15896bc
--- /dev/null
@@ -0,0 +1,954 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/elog.h"
+#include "utils/memutils.h" /* for TopMemoryContext */
+#include "lib/stringinfo.h"
+//#include "funcapi.h"
+#include "executor/spi.h" /* this is what you need to work with SPI */
+
+#include "../postgis_config.h"
+
+#include "liblwgeom_topo.h"
+#include "lwgeom_log.h"
+#include "lwgeom_pg.h"
+
+#include <stdarg.h>
+
+#ifdef __GNUC__
+# define GNU_PRINTF23 __attribute__ (( format(printf, 2, 3) ))
+#else
+# define GNU_PRINTF23
+#endif
+
+/*
+ * This is required for builds against pgsql
+ */
+PG_MODULE_MAGIC;
+
+LWT_BE_IFACE* be_iface;
+
+/*
+ * Private data we'll use for this backend
+ */
+#define MAXERRLEN 256
+struct LWT_BE_DATA_T {
+  char lastErrorMsg[MAXERRLEN];
+};
+LWT_BE_DATA be_data;
+
+struct LWT_BE_TOPOLOGY_T {
+  LWT_BE_DATA* be_data;
+  char *name;
+  int id;
+  int srid;
+  int precision;
+};
+
+/* utility funx */
+
+static void cberror(const LWT_BE_DATA* be, const char *fmt, ...) GNU_PRINTF23;
+
+static void
+cberror(const LWT_BE_DATA* be_in, const char *fmt, ...)
+{
+  LWT_BE_DATA *be = (LWT_BE_DATA*)be_in;/*const cast*/
+       va_list ap;
+
+       va_start(ap, fmt);
+
+       vsnprintf (be->lastErrorMsg, MAXERRLEN, fmt, ap);
+       be->lastErrorMsg[MAXERRLEN-1]='\0';
+
+       va_end(ap);
+}
+
+/* Backend callbacks */
+
+static const char*
+cb_lastErrorMessage(const LWT_BE_DATA* be)
+{
+  return be->lastErrorMsg;
+}
+
+static LWT_BE_TOPOLOGY*
+cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name)
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  Datum dat;
+  bool isnull;
+  LWT_BE_TOPOLOGY *topo;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "SELECT * FROM topology.topology "
+                        "WHERE name = '%s'", name);
+  spi_result = SPI_execute(sql->data, true, 0);
+  if ( spi_result != SPI_OK_SELECT ) {
+    pfree(sqldata.data);
+               cberror(be, "unexpected return (%d) from query execution: %s", spi_result, sql->data);
+         return NULL;
+  }
+  if ( ! SPI_processed )
+  {
+    pfree(sqldata.data);
+               //cberror(be, "no topology named '%s' was found", name);
+               cberror(be, "SQL/MM Spatial exception - invalid topology name");
+         return NULL;
+  }
+  if ( SPI_processed > 1 )
+  {
+    pfree(sqldata.data);
+               cberror(be, "multiple topologies named '%s' were found", name);
+         return NULL;
+  }
+  pfree(sqldata.data);
+
+  topo = palloc(sizeof(LWT_BE_TOPOLOGY));
+  topo->be_data = (LWT_BE_DATA *)be; /* const cast.. */
+  topo->name = pstrdup(name);
+
+  dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
+  if ( isnull ) {
+               cberror(be, "Topology '%s' has null identifier", name);
+    return NULL;
+  }
+  topo->id = DatumGetInt32(dat);
+
+  topo->srid = 0; /* needed ? */
+  topo->precision = 0; /* needed ? */
+
+  lwpgnotice("cb_loadTopologyByName: topo '%s' has id %d", name, topo->id);
+
+  return topo;
+}
+
+static int
+cb_freeTopology(LWT_BE_TOPOLOGY* topo)
+{
+  pfree(topo->name);
+  pfree(topo);
+  return 1;
+}
+
+static void
+addEdgeFields(StringInfo str, int fields, int fullEdgeData)
+{
+  const char *sep = "";
+
+  if ( fields & LWT_COL_EDGE_EDGE_ID ) {
+    appendStringInfoString(str, "edge_id");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_START_NODE ) {
+    appendStringInfo(str, "%sstart_node", sep);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_END_NODE ) {
+    appendStringInfo(str, "%send_node", sep);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
+    appendStringInfo(str, "%sleft_face", sep);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
+    appendStringInfo(str, "%sright_face", sep);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
+    appendStringInfo(str, "%snext_left_edge", sep);
+    if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_left_edge");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
+    appendStringInfo(str, "%snext_right_edge", sep);
+    if ( fullEdgeData ) appendStringInfoString(str, ", abs_next_right_edge");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_EDGE_GEOM ) {
+    appendStringInfo(str, "%sgeom", sep);
+  }
+}
+
+/* Add edge values for an insert, in text form */
+static void
+addEdgeValues(StringInfo str, LWT_ISO_EDGE *edge, int fullEdgeData)
+{
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  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)");
+  }
+}
+
+enum UpdateType {
+  updSet,
+  updSel,
+  updNot
+};
+
+static void
+addEdgeUpdate(StringInfo str, const LWT_ISO_EDGE* edge, int fields,
+              int fullEdgeData, 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_EDGE_EDGE_ID ) {
+    appendStringInfoString(str, "edge_id ");
+    appendStringInfo(str, "%s %lld", op, edge->edge_id);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_START_NODE ) {
+    appendStringInfo(str, "%sstart_node ", sep);
+    appendStringInfo(str, "%s %lld", op, edge->start_node);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_END_NODE ) {
+    appendStringInfo(str, "%send_node", sep);
+    appendStringInfo(str, "%s %lld", op, edge->end_node);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
+    appendStringInfo(str, "%sleft_face", sep);
+    appendStringInfo(str, "%s %lld", op, edge->face_left);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
+    appendStringInfo(str, "%sright_face", sep);
+    appendStringInfo(str, "%s %lld", op, edge->face_right);
+    sep = sep1;
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
+    appendStringInfo(str, "%snext_left_edge", sep);
+    appendStringInfo(str, "%s %lld", op, edge->next_left);
+    sep = sep1;
+    if ( fullEdgeData ) {
+      appendStringInfo(str, "%s abs_next_left_edge", sep);
+      appendStringInfo(str, "%s %lld", op, llabs(edge->next_left));
+    }
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
+    appendStringInfo(str, "%snext_right_edge", sep);
+    appendStringInfo(str, "%s %lld", op, edge->next_right);
+    sep = sep1;
+    if ( fullEdgeData ) {
+      appendStringInfo(str, "%s abs_next_right_edge", sep);
+      appendStringInfo(str, "%s %lld", op, llabs(edge->next_right));
+    }
+  }
+  if ( fields & LWT_COL_EDGE_GEOM ) {
+    appendStringInfo(str, "%sgeom", sep);
+    hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom),
+                                WKB_EXTENDED, &hexewkb_size);
+    appendStringInfo(str, "%s'%s'::geometry", op, hexewkb);
+    lwfree(hexewkb);
+  }
+}
+
+static void
+addNodeFields(StringInfo str, int fields)
+{
+  const char *sep = "";
+
+  if ( fields & LWT_COL_NODE_NODE_ID ) {
+    appendStringInfoString(str, "node_id");
+    sep = ",";
+  }
+  if ( fields & LWT_COL_NODE_CONTAINING_FACE ) {
+    appendStringInfo(str, "%scontaining_face", sep);
+    sep = ",";
+  }
+  if ( fields & LWT_COL_NODE_GEOM ) {
+    appendStringInfo(str, "%sgeom", sep);
+  }
+}
+
+/* Add node values for an insert, in text form */
+static void
+addNodeValues(StringInfo str, LWT_ISO_NODE *node)
+{
+  size_t hexewkb_size;
+  char *hexewkb;
+
+  if ( node->node_id != -1 ) appendStringInfo(str, "(%lld", node->node_id);
+  else appendStringInfoString(str, "(DEFAULT");
+
+  if ( node->containing_face != -1 )
+    appendStringInfo(str, ",%lld", node->containing_face);
+  else appendStringInfoString(str, ",null");
+
+  if ( node->geom ) {
+    hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom),
+                                WKB_EXTENDED, &hexewkb_size);
+    appendStringInfo(str, ",'%s'::geometry)", hexewkb);
+    lwfree(hexewkb);
+  } else {
+    appendStringInfoString(str, ",null)");
+  }
+}
+
+static void
+fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields)
+{
+  bool isnull;
+  Datum dat;
+  GSERIALIZED *geom;
+  int colno = 0;
+
+  if ( fields & LWT_COL_EDGE_EDGE_ID ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->edge_id = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_START_NODE ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->start_node = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_END_NODE ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->end_node = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_FACE_LEFT ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->face_left = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_FACE_RIGHT ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->face_right = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_LEFT ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->next_left = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    edge->next_right = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_GEOM ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    if ( ! isnull ) {
+      geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat);
+      edge->geom = lwgeom_as_lwline(lwgeom_from_gserialized(geom));
+    } else {
+      lwpgnotice("Found edge with NULL geometry !");
+      edge->geom = NULL;
+    }
+  }
+}
+
+static void
+fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields)
+{
+  bool isnull;
+  Datum dat;
+  GSERIALIZED *geom;
+  int colno = 0;
+
+  if ( fields & LWT_COL_NODE_NODE_ID ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    node->node_id = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_NODE_CONTAINING_FACE ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    if ( isnull ) node->containing_face = -1;
+    else node->containing_face = DatumGetInt32(dat);
+  }
+  if ( fields & LWT_COL_EDGE_GEOM ) {
+    dat = SPI_getbinval(row, rowdesc, ++colno, &isnull);
+    if ( ! isnull ) {
+      geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat);
+      node->geom = lwgeom_as_lwpoint(lwgeom_from_gserialized(geom));
+    } else {
+      lwpgnotice("Found node with NULL geometry !");
+      node->geom = NULL;
+    }
+  }
+}
+
+/* return 0 on failure (null) 1 otherwise */
+static int
+getNotNullInt32( HeapTuple row, TupleDesc desc, int col, int32 *val )
+{
+  bool isnull;
+  Datum dat = SPI_getbinval( row, desc, col, &isnull );
+  if ( isnull ) return 0;
+  *val = DatumGetInt32(dat);
+  return 1;
+}
+
+/* ----------------- Callbacks start here ------------------------ */
+
+static LWT_ISO_EDGE*
+cb_getEdgeById(const LWT_BE_TOPOLOGY* topo,
+      const LWT_ELEMID* ids, int* numelems, int fields)
+{
+  LWT_ISO_EDGE *edges;
+       int spi_result;
+
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfoString(sql, "SELECT ");
+  addEdgeFields(sql, fields, 0);
+  appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name);
+  appendStringInfoString(sql, " WHERE edge_id IN (");
+  // add all identifiers here
+  for (i=0; i<*numelems; ++i) {
+    appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]);
+  }
+  appendStringInfoString(sql, ")");
+  spi_result = SPI_execute(sql->data, true, *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_getEdgeById: 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_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, true, *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_getNodeWithinDistance2D: edge query "
+             "(limited by %d) returned %d rows",
+             elems_requested, SPI_processed);
+  if ( ! SPI_processed ) {
+    *numelems = 0; 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;
+    }
+    return NULL;
+  }
+  else
+  {
+    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);
+    }
+    *numelems = SPI_processed;
+    return nodes;
+  }
+}
+
+static int
+cb_insertNodes( const LWT_BE_TOPOLOGY* topo,
+      LWT_ISO_NODE* nodes, int numelems )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+  int i;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "INSERT INTO \"%s\".node (", topo->name);
+  addNodeFields(sql, LWT_COL_NODE_ALL);
+  appendStringInfoString(sql, ") VALUES ");
+  for ( i=0; i<numelems; ++i ) {
+    if ( i ) appendStringInfoString(sql, ",");
+    // TODO: prepare and execute ?
+    addNodeValues(sql, &nodes[i]);
+  }
+  appendStringInfoString(sql, " RETURNING node_id");
+
+  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",
+            spi_result, sql->data);
+         return 0;
+  }
+  pfree(sqldata.data);
+
+  if ( SPI_processed != numelems ) {
+               cberror(topo->be_data, "processed %d rows, expected %d",
+            SPI_processed, numelems);
+         return 0;
+  }
+
+  /* Set node_id (could skip this if none had it set to -1) */
+  /* TODO: check for -1 values in the first loop */
+  for ( i=0; i<SPI_processed; ++i )
+  {
+    if ( nodes[i].node_id != -1 ) continue;
+    fillNodeFields(&nodes[i], SPI_tuptable->vals[i],
+      SPI_tuptable->tupdesc, LWT_COL_NODE_NODE_ID);
+  }
+
+  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], 1);
+    if ( edges[i].edge_id == -1 ) needsEdgeIdReturn = 1;
+  }
+  if ( needsEdgeIdReturn ) appendStringInfoString(sql, " RETURNING edge_id");
+
+  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 != 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_updateEdges( const LWT_BE_TOPOLOGY* topo,
+      const LWT_ISO_EDGE* sel_edge, int sel_fields,
+      const LWT_ISO_EDGE* upd_edge, int upd_fields,
+      const LWT_ISO_EDGE* exc_edge, int exc_fields )
+{
+       int spi_result;
+  StringInfoData sqldata;
+  StringInfo sql = &sqldata;
+
+  initStringInfo(sql);
+  appendStringInfo(sql, "UPDATE \"%s\".edge_data SET ", topo->name);
+  addEdgeUpdate( sql, upd_edge, upd_fields, 1, updSet );
+  if ( exc_edge || sel_edge ) appendStringInfoString(sql, " WHERE ");
+  if ( sel_edge ) {
+    addEdgeUpdate( sql, sel_edge, sel_fields, 1, updSel );
+    if ( exc_edge ) appendStringInfoString(sql, " AND ");
+  }
+  if ( exc_edge ) {
+    addEdgeUpdate( sql, exc_edge, exc_fields, 1, updNot );
+  }
+
+  /* lwpgnotice("cb_updateEdges: %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);
+
+  lwpgnotice("cb_updateEdges: update 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 != 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 = DatumGetInt32(dat);
+  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, 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, topo->id, split_edge);
+  if ( new_edge2 != -1 ) {
+    appendStringInfo(sql, " RETURNING %s", proj);
+  }
+
+  spi_result = SPI_execute(sql->data, true, 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;
+  }
+  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;
+    }
+
+    if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) ) {
+                 cberror(topo->be_data,
+        "unexpected null layer_id in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
+
+    if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) ) {
+                 cberror(topo->be_data,
+        "unexpected null element_type in \"%s\".relation",
+        topo->name);
+           return 0;
+    }
+
+    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 ( 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;
+      }
+    }
+  }
+
+  lwpgnotice("cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms);
+
+  return 1;
+}
+
+LWT_BE_CALLBACKS be_callbacks = {
+    cb_lastErrorMessage,
+    NULL, /* createTopology */
+    cb_loadTopologyByName, /* loadTopologyByName */
+    cb_freeTopology, /* freeTopology */
+    NULL, /* getNodeById */
+    cb_getNodeWithinDistance2D,
+    cb_insertNodes,
+    cb_getEdgeById, /* getEdgeById */
+    NULL, /* getEdgeWithinDistance2D */
+    cb_getNextEdgeId,
+    cb_insertEdges,
+    cb_updateEdges,
+    NULL, /* getFacesById */
+    NULL, /* getFaceContainingPoint */
+    cb_updateTopoGeomEdgeSplit
+};
+
+
+/*
+ * Module load callback
+ */
+void _PG_init(void);
+void
+_PG_init(void)
+{
+  MemoryContext old_context;
+
+  /*
+   * install PostgreSQL handlers for liblwgeom
+   * NOTE: they may be already in place!
+   */
+  pg_install_lwgeom_handlers();
+
+  /* Switch to the top memory context so that the backend interface
+   * is valid for the whole backend lifetime */
+  old_context = MemoryContextSwitchTo( TopMemoryContext );
+
+  /* register callbacks against liblwgeom-topo */
+  be_iface = lwt_CreateBackendIface(&be_data);
+  lwt_BackendIfaceRegisterCallbacks(be_iface, &be_callbacks);
+
+  /* Switch back to whatever memory context was in place
+   * at time of _PG_init enter.
+   * See http://www.postgresql.org/message-id/20150623114125.GD5835@localhost
+   */
+  MemoryContextSwitchTo(old_context);
+}
+
+/*
+ * Module unload callback
+ */
+void _PG_fini(void);
+void
+_PG_fini(void)
+{
+  elog(NOTICE, "Goodbye from PostGIS Topology %s", POSTGIS_VERSION);
+  lwt_FreeBackendIface(be_iface);
+}
+
+/*  ST_ModEdgeSplit(atopology, anedge, apoint) */
+Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_ModEdgeSplit);
+Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS)
+{
+  text* toponame_text;
+  char* toponame;
+  LWT_ELEMID edge_id;
+  LWT_ELEMID node_id;
+  GSERIALIZED *geom;
+  LWGEOM *lwgeom;
+  LWPOINT *pt;
+  LWT_TOPOLOGY *topo;
+
+  if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) ) {
+    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);
+
+  edge_id = PG_GETARG_INT32(1) ;
+
+  geom = PG_GETARG_GSERIALIZED_P(2);
+  lwgeom = lwgeom_from_gserialized(geom);
+  pt = lwgeom_as_lwpoint(lwgeom);
+  if ( ! pt ) {
+    lwgeom_free(lwgeom);
+         PG_FREE_IF_COPY(geom, 3);
+    lwpgerror("ST_ModEdgeSplit third argument must be a point geometry");
+    PG_RETURN_NULL();
+  }
+
+  if ( SPI_OK_CONNECT != SPI_connect() ) {
+    lwpgerror("Could not connect to SPI");
+    PG_RETURN_NULL();
+  }
+
+  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_ModEdgeSplit");
+  node_id = lwt_ModEdgeSplit(topo, edge_id, pt, 0);
+  POSTGIS_DEBUG(1, "lwt_ModEdgeSplit returned");
+  lwgeom_free(lwgeom);
+  PG_FREE_IF_COPY(geom, 3);
+  lwt_FreeTopology(topo);
+
+  if ( node_id == -1 ) {
+    /* should never reach this point, as lwerror would raise an exception */
+    SPI_finish();
+    PG_RETURN_NULL();
+  }
+
+  SPI_finish();
+  PG_RETURN_INT32(node_id);
+}
index dab993d0f9389256463e83a0a559c0ff35b5851d..22a1019cd4e4398eb51121cc6401a6c6b585b4a5 100644 (file)
@@ -2115,205 +2115,8 @@ LANGUAGE 'plpgsql' VOLATILE;
 --
 CREATE OR REPLACE FUNCTION topology.ST_ModEdgeSplit(atopology varchar, anedge integer, apoint geometry)
   RETURNS INTEGER AS
-$$
-DECLARE
-  oldedge RECORD;
-  rec RECORD;
-  tmp integer;
-  topoid integer;
-  nodeid integer;
-  nodepos float8;
-  newedgeid integer;
-  newedge1 geometry;
-  newedge2 geometry;
-  query text;
-  ok BOOL;
-BEGIN
-
-  --
-  -- All args required
-  -- 
-  IF atopology IS NULL OR anedge IS NULL OR apoint IS NULL THEN
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - null argument';
-  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;
-
-  --
-  -- Check edge existance
-  -- 
-  ok = false;
-  FOR oldedge IN EXECUTE 'SELECT * FROM '
-    || quote_ident(atopology) || '.edge_data ' ||
-    ' WHERE edge_id =  ' || anedge
-  LOOP
-    ok = true;
-  END LOOP;
-  IF NOT ok THEN
-    RAISE EXCEPTION
-      'SQL/MM Spatial exception - non-existent edge';
-  END IF;
-
-  --
-  -- Check that given point is Within(anedge.geom)
-  -- 
-  IF NOT ST_Within(apoint, oldedge.geom) THEN
-    RAISE EXCEPTION
-      'SQL/MM Spatial exception - point not on edge';
-  END IF;
-
-  --
-  -- Check if a coincident node already exists
-  --
-  FOR rec IN EXECUTE 'SELECT node_id FROM '
-    || quote_ident(atopology) || '.node WHERE geom && $1'
-    ||' AND ST_X(geom) = ST_X($1) AND ST_Y(geom) = ST_Y($1)'
-    USING apoint
-  LOOP
-    RAISE EXCEPTION
-     'SQL/MM Spatial exception - coincident node';
-  END LOOP;
-
-  --
-  -- Add the new node, returning its identifier
-  --
-  EXECUTE 'INSERT INTO ' || quote_ident(atopology)
-    || '.node(node_id, geom) VALUES(DEFAULT, $2) RETURNING node_id'
-    INTO nodeid
-    USING nodeid,apoint;
-
-  --RAISE NOTICE 'Next node id = % ', nodeid;
-
-  --
-  -- Compute new edge
-  --
-  newedge2 := ST_Split(oldedge.geom, apoint);
-  newedge1 := ST_GeometryN(newedge2, 1);
-  newedge2 := ST_GeometryN(newedge2, 2);
-
-  --
-  -- Get ids for the new edge
-  --
-  FOR rec IN EXECUTE 'SELECT nextval(''' ||
-    atopology || '.edge_data_edge_id_seq'')'
-  LOOP
-    newedgeid = rec.nextval;
-  END LOOP;
-
-#ifdef POSTGIS_TOPOLOGY_DEBUG
-  RAISE DEBUG ' inserting new edge % split from %', newedgeid, anedge;
-#endif
-
-  --
-  -- Insert the new edge
-  --
-  EXECUTE 'INSERT INTO ' || quote_ident(atopology)
-    || '.edge '
-    || '(edge_id, start_node, end_node,'
-    || 'next_left_edge, next_right_edge,'
-    || 'left_face, right_face, geom) '
-    || 'VALUES('
-    || newedgeid
-    || ',' || nodeid
-    || ',' || oldedge.end_node
-    || ',' || COALESCE(                      -- next_left_edge
-                NULLIF(
-                  oldedge.next_left_edge,
-                  -anedge
-                ),
-                -newedgeid
-              )
-    || ',' || -anedge                        -- next_right_edge
-    || ',' || oldedge.left_face              -- left_face
-    || ',' || oldedge.right_face             -- right_face
-    || ',$1)' --geom
-    USING newedge2;
-
-  --
-  -- Update the old edge
-  --
-  EXECUTE 'UPDATE ' || quote_ident(atopology)
-    || '.edge_data SET geom = $1, next_left_edge = $2, ' 
-    || 'abs_next_left_edge = $2, end_node = $3 WHERE edge_id = $4'
-    USING newedge1, newedgeid, nodeid, anedge;
-
-
-  --
-  -- Update all next edge references to match new layout (ST_ModEdgeSplit)
-  --
-
-  EXECUTE 'UPDATE ' || quote_ident(atopology)
-    || '.edge_data SET next_right_edge = '
-    || -newedgeid 
-    || ','
-    || ' abs_next_right_edge = ' || newedgeid
-    || ' WHERE edge_id != ' || newedgeid
-    || ' AND next_right_edge = ' || -anedge
-    || ' AND start_node = ' || oldedge.end_node;
-
-  EXECUTE 'UPDATE ' || quote_ident(atopology)
-    || '.edge_data SET '
-    || ' next_left_edge = ' || -newedgeid
-    || ','
-    || ' abs_next_left_edge = ' || newedgeid
-    || ' WHERE edge_id != ' || newedgeid
-    || ' AND next_left_edge = ' || -anedge
-    || ' AND end_node = ' || oldedge.end_node;
-
-  --
-  -- Update references in the Relation table.
-  -- We only take into considerations non-hierarchical
-  -- TopoGeometry here, for obvious reasons.
-  --
-  FOR rec IN EXECUTE 'SELECT r.* 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 abs(r.element_id) = ' || anedge
-    || ' AND r.element_type = 2'
-  LOOP
-    --RAISE NOTICE 'TopoGeometry % in layer % contains the edge being split (%) - updating to add new edge %', rec.topogeo_id, rec.layer_id, anedge, newedgeid;
-
-    -- Add new reference to edge1
-    IF rec.element_id < 0 THEN
-      tmp = -newedgeid;
-    ELSE
-      tmp = newedgeid;
-    END IF;
-    query = 'INSERT INTO ' || quote_ident(atopology)
-      || '.relation '
-      || ' VALUES( '
-      || rec.topogeo_id
-      || ','
-      || rec.layer_id
-      || ','
-      || tmp
-      || ','
-      || rec.element_type
-      || ')';
-
-    --RAISE NOTICE '%', query;
-    EXECUTE query;
-  END LOOP;
-
-  --RAISE NOTICE 'Edge % split in edges % and % by node %',
-  --  anedge, anedge, newedgeid, nodeid;
-
-  RETURN nodeid; 
-END
-$$
-LANGUAGE 'plpgsql' VOLATILE;
+       'MODULE_PATHNAME','ST_ModEdgeSplit'
+  LANGUAGE 'c' VOLATILE;
 --} ST_ModEdgesSplit
 
 --{