lwgeom_geos_clean.o \
lwgeom_geos_node.o \
lwgeom_geos_split.o \
+ lwgeom_topo.o \
lwgeom_transform.o \
effectivearea.o \
varint.o
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 \
--- /dev/null
+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.
--- /dev/null
+/**********************************************************************
+ *
+ * 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 */
--- /dev/null
+/**********************************************************************
+ *
+ * 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 */
--- /dev/null
+/**********************************************************************
+ *
+ * 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;
+}
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
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
# 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.
# 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
--- /dev/null
+/**********************************************************************
+ *
+ * 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);
+}
--
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
--{