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