From: Sandro Santilli Date: Fri, 17 Jul 2015 16:41:51 +0000 (+0000) Subject: Implement ST_AddEdgeModFace in C X-Git-Tag: 2.2.0rc1~247 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4a2b022a823a9c6f7b498ea29a61445ee1d45555;p=postgis Implement ST_AddEdgeModFace in C Add callbacks to: - get nodes and edges within box2d, edges by node or face, nodes by face. - insert faces. - update nodes, faces and edges. - update TopoGeometries after face split. - get edges in a ring Also fixes installation and de-installation of liblwgeom_topo.h Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8) git-svn-id: http://svn.osgeo.org/postgis/trunk@13808 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/liblwgeom/Makefile.in b/liblwgeom/Makefile.in index 9a91fef06..a218f0511 100644 --- a/liblwgeom/Makefile.in +++ b/liblwgeom/Makefile.in @@ -139,10 +139,12 @@ uninstall: uninstall-liblwgeom install-liblwgeom: liblwgeom.la $(LIBTOOL) --mode=install $(INSTALL) liblwgeom.la "$(DESTDIR)$(libdir)/liblwgeom.la" $(INSTALL) -m 0644 liblwgeom.h "$(DESTDIR)$(includedir)/liblwgeom.h" + $(INSTALL) -m 0644 liblwgeom_topo.h "$(DESTDIR)$(includedir)/liblwgeom_topo.h" uninstall-liblwgeom: $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/liblwgeom.la" $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/liblwgeom.h" + $(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/liblwgeom_topo.h" # Make all objects depend upon postgis_config.h and postgis_svn_revision.h $(LT_OBJS): ../postgis_config.h ../postgis_svn_revision.h $(SA_HEADERS) diff --git a/liblwgeom/liblwgeom_topo.h b/liblwgeom/liblwgeom_topo.h index 9cfc1ac20..6cade6529 100644 --- a/liblwgeom/liblwgeom_topo.h +++ b/liblwgeom/liblwgeom_topo.h @@ -13,8 +13,6 @@ #ifndef LIBLWGEOM_TOPO_H #define LIBLWGEOM_TOPO_H 1 -#include "../postgis_config.h" - #include "liblwgeom.h" /* INT64 */ @@ -84,6 +82,7 @@ LWT_ISO_FACE; /** Face fields */ #define LWT_COL_FACE_FACE_ID 1<<0 #define LWT_COL_FACE_MBR 1<<1 +#define LWT_COL_FACE_ALL (1<<2)-1 typedef enum LWT_SPATIALTYPE_T { LWT_PUNTAL = 0, @@ -348,12 +347,15 @@ typedef struct LWT_BE_CALLBACKS_T { * @param topo the topology to act upon * @param ids an array of element identifiers * @param numelems input/output parameter, pass number of edge identifiers - * in the input array, gets number of node in output array. + * in the input array, gets number of node in output array + * if the return is not null, otherwise see @return + * section for semantic. * @param fields fields to be filled in the returned structure, see * LWT_COL_FACE_* macros * - * @return an array of faces - * or NULL on error (@see lastErrorMessage) + * @return an array of faces or NULL in the following cases: + * - none found ("numelems" is set to 0) + * - error ("numelems" is set to -1) */ LWT_ISO_FACE* (*getFaceById) ( const LWT_BE_TOPOLOGY* topo, @@ -414,6 +416,268 @@ typedef struct LWT_BE_CALLBACKS_T { const LWT_ISO_EDGE* sel_edge, int sel_fields ); + /** + * Get nodes within a 2D bounding box + * + * @param topo the topology to act upon + * @param box the query box + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * @param limit max number of nodes to return, 0 for no limit, -1 + * to only check for existance if a matching row. + * + * @return an array of nodes or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_NODE* (*getNodeWithinBox2D) ( + const LWT_BE_TOPOLOGY* topo, + const GBOX* box, + int* numelems, int fields, int limit + ); + + /** + * Get edges within a 2D bounding box + * + * @param topo the topology to act upon + * @param box the query box + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * @param limit max number of edges to return, 0 for no limit, -1 + * to only check for existance if a matching row. + * + * @return an array of edges or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_EDGE* (*getEdgeWithinBox2D) ( + const LWT_BE_TOPOLOGY* topo, + const GBOX* box, + int* numelems, int fields, int limit + ); + + /** + * Get edges that start or end on any of the given node identifiers + * + * @param topo the topology to act upon + * @param ids an array of node identifiers + * @param numelems input/output parameter, pass number of node identifiers + * in the input array, gets number of edges in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * + * @return an array of edges that are incident to a node + * or NULL in the following cases: + * - no edge found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + */ + LWT_ISO_EDGE* (*getEdgeByNode) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ELEMID* ids, int* numelems, int fields + ); + + /** + * Update nodes selected by fields match/mismatch + * + * @param topo the topology to act upon + * @param sel_node an LWT_ISO_NODE object with selecting fields set. + * @param sel_fields fields used to select nodes to be updated, + * see LWT_COL_NODE_* macros + * @param upd_node an LWT_ISO_NODE object with updated fields set. + * @param upd_fields fields to be updated for the selected nodes, + * see LWT_COL_NODE_* macros + * @param exc_node an LWT_ISO_NODE object with exclusion fields set, + * can be NULL if no exlusion condition exists. + * @param exc_fields fields used for excluding nodes from the update, + * see LWT_COL_NODE_* macros + * + * @return number of nodes being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateNodes) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_NODE* sel_node, int sel_fields, + const LWT_ISO_NODE* upd_node, int upd_fields, + const LWT_ISO_NODE* exc_node, int exc_fields + ); + + /** + * Update TopoGeometry objects after a face split event + * + * @param topo the topology to act upon + * @param split_face identifier of the face that was splitted. + * @param new_face1 identifier of the first new face that was created + * as a result of face splitting. + * @param new_face2 identifier of the second new face that was created + * as a result of face splitting, or -1 if the old face was + * modified rather than replaced. + * + * @return 1 on success, 0 on error + * + */ + int (*updateTopoGeomFaceSplit) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 + ); + + /** + * Insert faces + * + * Insert face primitives in the topology, performing no + * consistency checks. + * + * @param topo the topology to act upon + * @param faces the faces to insert. Those with a node_id set to -1 + * it will be replaced to an automatically assigned identifier + * @param nelems number of elements in the faces array + * + * @return number of inserted faces, or -1 (@see lastErrorMessage) + */ + int (*insertFaces) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ISO_FACE* faces, + int numelems + ); + + /** + * Update faces by id + * + * @param topo the topology to act upon + * @param faces an array of LWT_ISO_FACE object with selecting id + * and setting mbr. + * @param numfaces number of faces in the "faces" array + * + * @return number of faces being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateFacesById) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_FACE* faces, int numfaces + ); + + /* + * Get the ordered list edge visited by a side walk + * + * The walk starts from the side of an edge and stops when + * either the max number of visited edges OR the starting + * position is reached. The output list never includes a + * duplicated signed edge identifier. + * + * It is expected that the walk uses the "next_left" and "next_right" + * attributes of ISO edges to perform the walk (rather than recomputing + * the turns at each node). + * + * @param topo the topology to operate on + * @param edge walk start position and direction: + * abs value identifies the edge, sign expresses + * side (left if positive, right if negative) + * and direction (forward if positive, backward if negative). + * @param numedges output parameter, gets the number of edges visited + * @param limit max edges to return (to avoid an infinite loop in case + * of a corrupted topology). 0 is for unlimited. + * The function is expected to error out if the limit is hit. + * + * @return an array of signed edge identifiers (positive edges being + * walked in their direction, negative ones in opposite) or + * NULL on error (@see lastErroMessage) + */ + LWT_ELEMID* (*getRingEdges) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID edge, int *numedges, int limit + ); + + /** + * Update edges by id + * + * @param topo the topology to act upon + * @param edges an array of LWT_ISO_EDGE object with selecting id + * and updating fields. + * @param numedges number of edges in the "edges" array + * @param upd_fields fields to be updated for the selected edges, + * see LWT_COL_EDGE_* macros + * + * @return number of edges being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateEdgesById) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_EDGE* edges, int numedges, + int upd_fields + ); + + /** + * Get edges that have any of the given faces on the left or right side + * + * @param topo the topology to act upon + * @param ids an array of face identifiers + * @param numelems input/output parameter, pass number of face identifiers + * in the input array, gets number of edges in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * + * @return an array of edges identifiers or NULL in the following cases: + * - no edge found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + */ + LWT_ISO_EDGE* (*getEdgeByFace) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ELEMID* ids, int* numelems, int fields + ); + + /** + * Get isolated nodes contained in any of the given faces + * + * @param topo the topology to act upon + * @param faces an array of face identifiers + * @param numelems input/output parameter, pass number of face + * identifiers in the input array, gets number of + * nodes in output array if the return is not null, + * otherwise see @return section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * + * @return an array of nodes or NULL in the following cases: + * - no nod found ("numelems" is set to 0) + * - error ("numelems" is set to -1, @see lastErrorMessage) + */ + LWT_ISO_NODE* (*getNodeByFace) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ELEMID* faces, int* numelems, int fields + ); + + /** + * Update nodes by id + * + * @param topo the topology to act upon + * @param nodes an array of LWT_ISO_EDGE objects with selecting id + * and updating fields. + * @param numnodes number of nodes in the "nodes" array + * @param upd_fields fields to be updated for the selected edges, + * see LWT_COL_NODE_* macros + * + * @return number of nodes being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateNodesById) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_NODE* nodes, int numnodes, + int upd_fields + ); + } LWT_BE_CALLBACKS; @@ -773,12 +1037,17 @@ LWT_ELEMID lwt_AddIsoEdge(LWT_TOPOLOGY* topo, * @param start_node identifier of the starting node * @param end_node identifier of the ending node * @param geom the edge geometry - * @return ID of the newly added edge + * @param skipChecks if non-zero skips consistency checks + * (curve being simple and valid, start/end nodes + * consistency actual face containement) + * + * @return ID of the newly added edge or null on error + * (@see lastErrorMessage) * */ LWT_ELEMID lwt_AddEdgeModFace(LWT_TOPOLOGY* topo, LWT_ELEMID start_node, LWT_ELEMID end_node, - LWLINE *geom); + LWLINE *geom, int skipChecks); /** * Add a new edge possibly splitting a face (replacing with two new faces) diff --git a/liblwgeom/liblwgeom_topo_internal.h b/liblwgeom/liblwgeom_topo_internal.h index c6fc38d5e..3c2176d7c 100644 --- a/liblwgeom/liblwgeom_topo_internal.h +++ b/liblwgeom/liblwgeom_topo_internal.h @@ -38,6 +38,8 @@ int lwt_be_freeTopology(LWT_TOPOLOGY *topo); LWT_ISO_NODE* lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt, double dist, int* numelems, int fields, int limit); +LWT_ISO_NODE* lwt_be_getNodeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, int* numelems, int fields); + int lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt); int lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems); @@ -60,6 +62,12 @@ LWT_ELEMID lwt_be_getFaceContainingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt); int lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2); +LWT_ISO_NODE* lwt_be_getNodeWithinBox2D( const LWT_TOPOLOGY* topo, + const GBOX* box, int* numelems, int fields, int limit); + +LWT_ISO_EDGE* lwt_be_getEdgeWithinBox2D( const LWT_TOPOLOGY* topo, + const GBOX* box, int* numelems, int fields, int limit); + /************************************************************************ * * Internal objects diff --git a/liblwgeom/lwgeom_topo.c b/liblwgeom/lwgeom_topo.c index 892244ba6..1bf3d3b4b 100644 --- a/liblwgeom/lwgeom_topo.c +++ b/liblwgeom/lwgeom_topo.c @@ -8,11 +8,22 @@ * This is free software; you can redistribute and/or modify it under * the terms of the GNU General Public Licence. See the COPYING file. * + ********************************************************************** + * + * Topology extension for liblwgeom. + * Initially funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8) + * **********************************************************************/ +#include "../postgis_config.h" + +#define POSTGIS_DEBUG_LEVEL 1 +#include "lwgeom_log.h" + #include "liblwgeom_internal.h" #include "liblwgeom_topo_internal.h" +#include "lwgeom_geos.h" #include #include @@ -107,6 +118,13 @@ lwt_be_freeTopology(LWT_TOPOLOGY *topo) CBT0(topo, freeTopology); } +LWT_ISO_NODE* +lwt_be_getNodeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, + int* numelems, int fields) +{ + CBT3(topo, getNodeById, ids, numelems, fields); +} + LWT_ISO_NODE* lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt, double dist, int* numelems, int fields, @@ -115,12 +133,34 @@ lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt, CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit); } +LWT_ISO_NODE* +lwt_be_getNodeWithinBox2D( const LWT_TOPOLOGY* topo, + const GBOX* box, int* numelems, int fields, + int limit ) +{ + CBT4(topo, getNodeWithinBox2D, box, numelems, fields, limit); +} + +LWT_ISO_EDGE* +lwt_be_getEdgeWithinBox2D( const LWT_TOPOLOGY* topo, + const GBOX* box, int* numelems, int fields, + int limit ) +{ + CBT4(topo, getEdgeWithinBox2D, box, numelems, fields, limit); +} + int lwt_be_insertNodes(LWT_TOPOLOGY* topo, LWT_ISO_NODE* node, int numelems) { CBT2(topo, insertNodes, node, numelems); } +static int +lwt_be_insertFaces(LWT_TOPOLOGY* topo, LWT_ISO_FACE* face, int numelems) +{ + CBT2(topo, insertFaces, face, numelems); +} + LWT_ELEMID lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo) { @@ -134,6 +174,34 @@ lwt_be_getEdgeById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, CBT3(topo, getEdgeById, ids, numelems, fields); } +static LWT_ISO_FACE* +lwt_be_getFaceById(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, + int* numelems, int fields) +{ + CBT3(topo, getFaceById, ids, numelems, fields); +} + +static LWT_ISO_EDGE* +lwt_be_getEdgeByNode(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, + int* numelems, int fields) +{ + CBT3(topo, getEdgeByNode, ids, numelems, fields); +} + +static LWT_ISO_EDGE* +lwt_be_getEdgeByFace(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, + int* numelems, int fields) +{ + CBT3(topo, getEdgeByFace, ids, numelems, fields); +} + +static LWT_ISO_NODE* +lwt_be_getNodeByFace(LWT_TOPOLOGY* topo, const LWT_ELEMID* ids, + int* numelems, int fields) +{ + CBT3(topo, getNodeByFace, ids, numelems, fields); +} + LWT_ISO_EDGE* lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY* topo, LWPOINT* pt, double dist, int* numelems, int fields, @@ -160,6 +228,42 @@ lwt_be_updateEdges(LWT_TOPOLOGY* topo, exc_edge, exc_fields); } +static int +lwt_be_updateNodes(LWT_TOPOLOGY* topo, + const LWT_ISO_NODE* sel_node, int sel_fields, + const LWT_ISO_NODE* upd_node, int upd_fields, + const LWT_ISO_NODE* exc_node, int exc_fields +) +{ + CBT6(topo, updateNodes, sel_node, sel_fields, + upd_node, upd_fields, + exc_node, exc_fields); +} + +static int +lwt_be_updateFacesById(LWT_TOPOLOGY* topo, + const LWT_ISO_FACE* faces, int numfaces +) +{ + CBT2(topo, updateFacesById, faces, numfaces); +} + +static int +lwt_be_updateEdgesById(LWT_TOPOLOGY* topo, + const LWT_ISO_EDGE* edges, int numedges, int upd_fields +) +{ + CBT3(topo, updateEdgesById, edges, numedges, upd_fields); +} + +static int +lwt_be_updateNodesById(LWT_TOPOLOGY* topo, + const LWT_ISO_NODE* nodes, int numnodes, int upd_fields +) +{ + CBT3(topo, updateNodesById, nodes, numnodes, upd_fields); +} + int lwt_be_deleteEdges(LWT_TOPOLOGY* topo, const LWT_ISO_EDGE* sel_edge, int sel_fields @@ -181,6 +285,20 @@ lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_EL CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2); } +static int +lwt_be_updateTopoGeomFaceSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2) +{ + CBT3(topo, updateTopoGeomFaceSplit, split_face, new_face1, new_face2); +} + +static LWT_ELEMID* +lwt_be_getRingEdges( LWT_TOPOLOGY* topo, + LWT_ELEMID edge, int *numedges, int limit ) +{ + CBT3(topo, getRingEdges, edge, numedges, limit); +} + + /* wrappers of backend wrappers... */ int @@ -207,6 +325,22 @@ lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt) return exists; } +/************************************************************************ + * + * Utility functions + * + ************************************************************************/ + +static void +_lwt_release_edges(LWT_ISO_EDGE *edges, int num_edges) +{ + int i; + for ( i=0; iedge_id, newedges[0].edge_id, newedges[1].edge_id); if ( ! ret ) { lwcollection_release(split_col); @@ -678,3 +811,1323 @@ lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, /* return new node id */ return node.node_id; } + +/* Data structure used by AddEdgeX functions */ +typedef struct edgeend_t { + /* Signed identifier of next clockwise edge (+outgoing,-incoming) */ + LWT_ELEMID nextCW; + /* Identifier of face between myaz and next CW edge */ + LWT_ELEMID cwFace; + /* Signed identifier of next counterclockwise edge (+outgoing,-incoming) */ + LWT_ELEMID nextCCW; + /* Identifier of face between myaz and next CCW edge */ + LWT_ELEMID ccwFace; + int was_isolated; + double myaz; /* azimuth of edgeend geometry */ +} edgeend; + +/* + * Find the first edge encountered going counterclockwise + * around a node, starting from the given azimuth, and take + * note of the face on the given azimut's side. + * + * @param topo the topology to act upon + * @param node the identifier of the node to analyze + * @param data input (myaz) / output (nextCW, nextCCW) parameter + * @param other edgeend, if also incident to given node (closed edge). + * @return number of incident edges found + * + */ +static int +_lwt_FindAdjacentEdges( LWT_TOPOLOGY* topo, LWT_ELEMID node, edgeend *data, + edgeend *other ) +{ + LWT_ISO_EDGE *edges; + int numedges = 1; + int i; + double minaz, maxaz; + double az, azdif; + + data->nextCW = data->nextCCW = 0; + data->cwFace = data->ccwFace = -1; + + if ( other ) { + azdif = other->myaz - data->myaz; + if ( azdif < 0 ) azdif += 2 * M_PI; + minaz = maxaz = azdif; + /* TODO: set nextCW/nextCCW/cwFace/ccwFace to other->something ? */ + LWDEBUGF(1, "Other edge end has cwFace=%d and ccwFace=%d", + other->cwFace, other->ccwFace); + } else { + minaz = maxaz = -1; + } + + LWDEBUGF(1, "Looking for edges incident to node %lld " + "and adjacent to azimuth %g", node, data->myaz); + + /* Get incident edges */ + edges = lwt_be_getEdgeByNode( topo, &node, &numedges, LWT_COL_EDGE_ALL ); + if ( numedges == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return 0; + } + + LWDEBUGF(1, "getEdgeByNode returned %d edges, minaz=%g, maxaz=%g", + numedges, minaz, maxaz); + + /* For each incident edge-end (1 or 2): */ + for ( i = 0; i < numedges; ++i ) + { + LWT_ISO_EDGE *edge = &(edges[i]); + LWGEOM *g = lwline_as_lwgeom(edge->geom); + /* NOTE: remove_repeated_points call could be replaced by + * some other mean to pick two distinct points for endpoints */ + LWGEOM *cleangeom = lwgeom_remove_repeated_points( g, 0 ); + POINT2D p1, p2; + POINTARRAY *pa = lwgeom_as_lwline(cleangeom)->points; + + if ( pa->npoints < 2 ) { + lwgeom_free(cleangeom); + lwerror("corrupted topology: edge %lld does not have two distinct points", edge->edge_id); + return -1; + } + + if ( edge->start_node == node ) { + getPoint2d_p(pa, 0, &p1); + getPoint2d_p(pa, 1, &p2); + LWDEBUGF(1, "edge %lld starts on node %lld, edgeend is %g,%g-%g,%g", + edge->edge_id, node, p1.x, p1.y, p2.x, p2.y); + if ( ! azimuth_pt_pt(&p1, &p2, &az) ) { + lwgeom_free(cleangeom); + lwerror("error computing azimuth of edge %d first edgeend [%g,%g-%g,%g]", + edge->edge_id, p1.x, p1.y, p2.x, p2.y); + return -1; + } + azdif = az - data->myaz; + LWDEBUGF(1, "azimuth of edge %lld: %g (diff: %g)", edge->edge_id, az, azdif); + + if ( azdif < 0 ) azdif += 2 * M_PI; + if ( minaz == -1 ) { + minaz = maxaz = azdif; + data->nextCW = data->nextCCW = edge->edge_id; /* outgoing */ + data->cwFace = edge->face_left; + data->ccwFace = edge->face_right; + LWDEBUGF(1, "new nextCW and nextCCW edge is %lld, " + "outgoing, " + "with face_left %lld and face_right %lld " + "(face_right is new ccwFace, face_left is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right); + } else { + if ( azdif < minaz ) { + data->nextCW = edge->edge_id; /* outgoing */ + data->cwFace = edge->face_left; + LWDEBUGF(1, "new nextCW edge is %lld, " + "outgoing, " + "with face_left %lld and face_right %lld " + "(previous had minaz=%g, face_left is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right, minaz); + minaz = azdif; + } + else if ( azdif > maxaz ) { + data->nextCCW = edge->edge_id; /* outgoing */ + data->ccwFace = edge->face_right; + LWDEBUGF(1, "new nextCCW edge is %lld, " + "outgoing, " + "with face_left %lld and face_right %lld " + "(previous had maxaz=%g, face_right is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right, maxaz); + maxaz = azdif; + } + } + } + + if ( edge->end_node == node ) { + getPoint2d_p(pa, pa->npoints-1, &p1); + getPoint2d_p(pa, pa->npoints-2, &p2); + LWDEBUGF(1, "edge %lld ends on node %lld, edgeend is %g,%g-%g,%g", + edge->edge_id, node, p1.x, p1.y, p2.x, p2.y); + if ( ! azimuth_pt_pt(&p1, &p2, &az) ) { + lwgeom_free(cleangeom); + lwerror("error computing azimuth of edge %d last edgeend [%g,%g-%g,%g]", + edge->edge_id, p1.x, p1.y, p2.x, p2.y); + return -1; + } + azdif = az - data->myaz; + LWDEBUGF(1, "azimuth of edge %lld: %g (diff: %g)", edge->edge_id, az, azdif); + if ( azdif < 0 ) azdif += 2 * M_PI; + if ( minaz == -1 ) { + minaz = maxaz = azdif; + data->nextCW = data->nextCCW = -edge->edge_id; /* incoming */ + data->cwFace = edge->face_right; + data->ccwFace = edge->face_left; + LWDEBUGF(1, "new nextCW and nextCCW edge is %lld, " + "incoming, " + "with face_left %lld and face_right %lld " + "(face_right is new cwFace, face_left is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right); + } else { + if ( azdif < minaz ) { + data->nextCW = -edge->edge_id; /* incoming */ + data->cwFace = edge->face_right; + LWDEBUGF(1, "new nextCW edge is %lld, " + "incoming, " + "with face_left %lld and face_right %lld " + "(previous had minaz=%g, face_right is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right, minaz); + minaz = azdif; + } + else if ( azdif > maxaz ) { + data->nextCCW = -edge->edge_id; /* incoming */ + data->ccwFace = edge->face_left; + LWDEBUGF(1, "new nextCCW edge is %lld, " + "outgoing from start point, " + "with face_left %lld and face_right %lld " + "(previous had maxaz=%g, face_left is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right, maxaz); + maxaz = azdif; + } + } + } + + lwgeom_free(cleangeom); + lwline_free(edge->geom); + } + if ( edges ) lwfree(edges); /* there might be none */ + + LWDEBUGF(1, "edges adjacent to azimuth %g (incident to node %lld): " + "CW:%lld(%g) CCW:%lld(%g)", + data->myaz, node, data->nextCW, minaz, + data->nextCCW, maxaz); + + if ( numedges && data->cwFace != data->ccwFace ) + { + if ( data->cwFace != -1 && data->ccwFace != -1 ) { + lwerror("Corrupted topology: adjacent edges %lld and %lld " + "bind different face (%lld and %lld)", + numedges, data->nextCW, data->nextCCW, + data->cwFace, data->ccwFace); + return -1; + } + } + + /* Return number of incident edges found */ + return numedges; +} + +/* + * Get a point internal to the line and write it into the "ip" + * parameter + * + * return 0 on failure (line is empty or collapsed), 1 otherwise + */ +static int +_lwt_GetInteriorEdgePoint(const LWLINE* edge, POINT2D* ip) +{ + int i; + POINT2D fp, lp, tp; + POINTARRAY *pa = edge->points; + + if ( pa->npoints < 2 ) return 0; /* empty or structurally collapsed */ + + getPoint2d_p(pa, 0, &fp); /* save first point */ + getPoint2d_p(pa, pa->npoints-1, &lp); /* save last point */ + for (i=1; inpoints-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; ibe_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; igeom->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; iedge_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; igeom), 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; icontaining_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; inode_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; igeom ) { + 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; +} diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c index 40cdf9738..382bf2d39 100644 --- a/topology/postgis_topology.c +++ b/topology/postgis_topology.c @@ -20,7 +20,9 @@ #include "../postgis_config.h" +#include "liblwgeom_internal.h" /* for gbox_clone */ #include "liblwgeom_topo.h" +#define POSTGIS_DEBUG_LEVEL 1 #include "lwgeom_log.h" #include "lwgeom_pg.h" @@ -45,7 +47,18 @@ LWT_BE_IFACE* be_iface; #define MAXERRLEN 256 struct LWT_BE_DATA_T { char lastErrorMsg[MAXERRLEN]; + /* + * This flag will need to be set to false + * at top-level function enter and set true + * whenever an callback changes the data + * in the database. + * It will be used by SPI_execute calls to + * make sure to see any data change occurring + * doring operations. + */ + bool data_changed; }; + LWT_BE_DATA be_data; struct LWT_BE_TOPOLOGY_T { @@ -85,7 +98,7 @@ cb_lastErrorMessage(const LWT_BE_DATA* be) static LWT_BE_TOPOLOGY* cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name) { - int spi_result; + int spi_result; StringInfoData sqldata; StringInfo sql = &sqldata; Datum dat; @@ -93,9 +106,9 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name) LWT_BE_TOPOLOGY *topo; initStringInfo(sql); - appendStringInfo(sql, "SELECT * FROM topology.topology " + appendStringInfo(sql, "SELECT id,srid FROM topology.topology " "WHERE name = '%s'", name); - spi_result = SPI_execute(sql->data, true, 0); + spi_result = SPI_execute(sql->data, !be->data_changed, 0); if ( spi_result != SPI_OK_SELECT ) { pfree(sqldata.data); cberror(be, "unexpected return (%d) from query execution: %s", spi_result, sql->data); @@ -127,10 +140,17 @@ cb_loadTopologyByName(const LWT_BE_DATA* be, const char *name) } topo->id = DatumGetInt32(dat); - topo->srid = 0; /* needed ? */ + dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull); + if ( isnull ) { + cberror(be, "Topology '%s' has null SRID", name); + return NULL; + } + topo->srid = DatumGetInt32(dat); + topo->precision = 0; /* needed ? */ - lwpgnotice("cb_loadTopologyByName: topo '%s' has id %d", name, topo->id); + POSTGIS_DEBUGF(1, "cb_loadTopologyByName: topo '%s' has id %d, srid %d", + name, topo->id, topo->srid); return topo; } @@ -183,33 +203,58 @@ addEdgeFields(StringInfo str, int fields, int fullEdgeData) } } -/* Add edge values for an insert, in text form */ +/* Add edge values in text form, include the parens */ static void -addEdgeValues(StringInfo str, LWT_ISO_EDGE *edge, int fullEdgeData) +addEdgeValues(StringInfo str, const LWT_ISO_EDGE *edge, int fields, int fullEdgeData) { size_t hexewkb_size; char *hexewkb; + const char *sep = ""; - if ( edge->edge_id != -1 ) appendStringInfo(str, "(%lld", edge->edge_id); - else appendStringInfoString(str, "(DEFAULT"); - - appendStringInfo(str, ",%lld", edge->start_node); - appendStringInfo(str, ",%lld", edge->end_node); - appendStringInfo(str, ",%lld", edge->face_left); - appendStringInfo(str, ",%lld", edge->face_right); - appendStringInfo(str, ",%lld", edge->next_left); - if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_left)); - appendStringInfo(str, ",%lld", edge->next_right); - if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_right)); - - if ( edge->geom ) { - hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom), - WKB_EXTENDED, &hexewkb_size); - appendStringInfo(str, ",'%s'::geometry)", hexewkb); - lwfree(hexewkb); - } else { - appendStringInfoString(str, ",null)"); + appendStringInfoChar(str, '('); + if ( fields & LWT_COL_EDGE_EDGE_ID ) { + if ( edge->edge_id != -1 ) appendStringInfo(str, "%lld", edge->edge_id); + else appendStringInfoString(str, "DEFAULT"); + sep = ","; + } + if ( fields & LWT_COL_EDGE_START_NODE ) { + appendStringInfo(str, "%s%lld", sep, edge->start_node); + sep = ","; + } + if ( fields & LWT_COL_EDGE_END_NODE ) { + appendStringInfo(str, "%s%lld", sep, edge->end_node); + sep = ","; + } + if ( fields & LWT_COL_EDGE_FACE_LEFT ) { + appendStringInfo(str, "%s%lld", sep, edge->face_left); + sep = ","; + } + if ( fields & LWT_COL_EDGE_FACE_RIGHT ) { + appendStringInfo(str, "%s%lld", sep, edge->face_right); + sep = ","; + } + if ( fields & LWT_COL_EDGE_NEXT_LEFT ) { + appendStringInfo(str, "%s%lld", sep, edge->next_left); + if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_left)); + sep = ","; + } + if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) { + appendStringInfo(str, "%s%lld", sep, edge->next_right); + if ( fullEdgeData ) appendStringInfo(str, ",%lld", llabs(edge->next_right)); + sep = ","; + } + if ( fields & LWT_COL_EDGE_GEOM ) + { + if ( edge->geom ) { + hexewkb = lwgeom_to_hexwkb(lwline_as_lwgeom(edge->geom), + WKB_EXTENDED, &hexewkb_size); + appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb); + lwfree(hexewkb); + } else { + appendStringInfo(str, "%snull", sep); + } } + appendStringInfoChar(str, ')'); } enum UpdateType { @@ -297,6 +342,56 @@ addEdgeUpdate(StringInfo str, const LWT_ISO_EDGE* edge, int fields, } } +static void +addNodeUpdate(StringInfo str, const LWT_ISO_NODE* node, int fields, + int fullNodeData, enum UpdateType updType) +{ + const char *sep = ""; + const char *sep1; + const char *op; + size_t hexewkb_size; + char *hexewkb; + + switch (updType) + { + case updSet: + op = "="; + sep1 = ","; + break; + case updSel: + op = "="; + sep1 = " AND "; + break; + case updNot: + default: + op = "!="; + sep1 = " AND "; + break; + } + + if ( fields & LWT_COL_NODE_NODE_ID ) { + appendStringInfoString(str, "node_id "); + appendStringInfo(str, "%s %lld", op, node->node_id); + sep = sep1; + } + if ( fields & LWT_COL_NODE_CONTAINING_FACE ) { + appendStringInfo(str, "%scontaining_face %s", sep, op); + if ( node->containing_face != -1 ) { + appendStringInfo(str, "%lld", node->containing_face); + } else { + appendStringInfoString(str, "NULL"); + } + sep = sep1; + } + if ( fields & LWT_COL_NODE_GEOM ) { + appendStringInfo(str, "%sgeom", sep); + hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom), + WKB_EXTENDED, &hexewkb_size); + appendStringInfo(str, "%s'%s'::geometry", op, hexewkb); + lwfree(hexewkb); + } +} + static void addNodeFields(StringInfo str, int fields) { @@ -315,25 +410,68 @@ addNodeFields(StringInfo str, int fields) } } +static void +addFaceFields(StringInfo str, int fields) +{ + const char *sep = ""; + + if ( fields & LWT_COL_FACE_FACE_ID ) { + appendStringInfoString(str, "face_id"); + sep = ","; + } + if ( fields & LWT_COL_FACE_MBR ) { + appendStringInfo(str, "%smbr", sep); + sep = ","; + } +} + /* Add node values for an insert, in text form */ static void -addNodeValues(StringInfo str, LWT_ISO_NODE *node) +addNodeValues(StringInfo str, const LWT_ISO_NODE *node, int fields) { size_t hexewkb_size; char *hexewkb; + const char *sep = ""; - if ( node->node_id != -1 ) appendStringInfo(str, "(%lld", node->node_id); - else appendStringInfoString(str, "(DEFAULT"); + appendStringInfoChar(str, '('); + + if ( fields & LWT_COL_NODE_NODE_ID ) { + if ( node->node_id != -1 ) appendStringInfo(str, "%lld", node->node_id); + else appendStringInfoString(str, "DEFAULT"); + sep = ","; + } - if ( node->containing_face != -1 ) - appendStringInfo(str, ",%lld", node->containing_face); - else appendStringInfoString(str, ",null"); + if ( fields & LWT_COL_NODE_CONTAINING_FACE ) { + if ( node->containing_face != -1 ) + appendStringInfo(str, "%s%lld", sep, node->containing_face); + else appendStringInfo(str, "%snull", sep); + } - if ( node->geom ) { - hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom), - WKB_EXTENDED, &hexewkb_size); - appendStringInfo(str, ",'%s'::geometry)", hexewkb); - lwfree(hexewkb); + if ( fields & LWT_COL_NODE_GEOM ) { + if ( node->geom ) { + hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(node->geom), + WKB_EXTENDED, &hexewkb_size); + appendStringInfo(str, "%s'%s'::geometry", sep, hexewkb); + lwfree(hexewkb); + } else { + appendStringInfo(str, "%snull", sep); + } + } + + appendStringInfoChar(str, ')'); +} + +/* Add face values for an insert, in text form */ +static void +addFaceValues(StringInfo str, LWT_ISO_FACE *face, int srid) +{ + if ( face->face_id != -1 ) appendStringInfo(str, "(%lld", face->face_id); + else appendStringInfoString(str, "(DEFAULT"); + + if ( face->mbr ) { + appendStringInfo(str, ",ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d))", + face->mbr->xmin, face->mbr->ymin, + face->mbr->xmax, face->mbr->ymax, srid); } else { appendStringInfoString(str, ",null)"); } @@ -344,36 +482,101 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields) { bool isnull; Datum dat; + int val; GSERIALIZED *geom; int colno = 0; + POSTGIS_DEBUGF(2, "fillEdgeFields: got %d atts and fields %x", + rowdesc->natts, fields); + if ( fields & LWT_COL_EDGE_EDGE_ID ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->edge_id = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL edge_id"); + edge->edge_id = -1; + } + val = DatumGetInt32(dat); + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (edge_id)" + " has int32 val of %d", + colno, val); + edge->edge_id = val; + } if ( fields & LWT_COL_EDGE_START_NODE ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->start_node = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL start_node"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->start_node = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (start_node)" + " has int32 val of %d", colno, val); } if ( fields & LWT_COL_EDGE_END_NODE ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->end_node = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL end_node"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->end_node = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (end_node)" + " has int32 val of %d", colno, val); } if ( fields & LWT_COL_EDGE_FACE_LEFT ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->face_left = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL face_left"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->face_left = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_left)" + " has int32 val of %d", colno, val); + } +#if POSTGIS_DEBUG_LEVEL > 1 + else { + edge->face_left = 6767; /* debugging */ } +#endif if ( fields & LWT_COL_EDGE_FACE_RIGHT ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->face_right = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL face_right"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->face_right = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (face_right)" + " has int32 val of %d", colno, val); + } +#if POSTGIS_DEBUG_LEVEL > 1 + else { + edge->face_right = 6767; /* debugging */ } +#endif if ( fields & LWT_COL_EDGE_NEXT_LEFT ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->next_left = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL next_left"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->next_left = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_left)" + " has int32 val of %d", colno, val); } if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); - edge->next_right = DatumGetInt32(dat); + if ( isnull ) { + lwpgwarning("Found edge with NULL next_right"); + edge->start_node = -1; + } + val = DatumGetInt32(dat); + edge->next_right = val; + POSTGIS_DEBUGF(2, "fillEdgeFields: colno%d (next_right)" + " has int32 val of %d", colno, val); } if ( fields & LWT_COL_EDGE_GEOM ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); @@ -381,10 +584,15 @@ fillEdgeFields(LWT_ISO_EDGE* edge, HeapTuple row, TupleDesc rowdesc, int fields) geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat); edge->geom = lwgeom_as_lwline(lwgeom_from_gserialized(geom)); } else { - lwpgnotice("Found edge with NULL geometry !"); + lwpgwarning("Found edge with NULL geometry !"); edge->geom = NULL; } } +#if POSTGIS_DEBUG_LEVEL > 1 + else { + edge->geom = (void*)0x67676767; /* debugging */ + } +#endif } static void @@ -404,7 +612,7 @@ fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields) if ( isnull ) node->containing_face = -1; else node->containing_face = DatumGetInt32(dat); } - if ( fields & LWT_COL_EDGE_GEOM ) { + if ( fields & LWT_COL_NODE_GEOM ) { dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); if ( ! isnull ) { geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat); @@ -414,6 +622,51 @@ fillNodeFields(LWT_ISO_NODE* node, HeapTuple row, TupleDesc rowdesc, int fields) node->geom = NULL; } } +#if POSTGIS_DEBUG_LEVEL > 1 + else { + node->geom = (void*)0x67676767; /* debugging */ + } +#endif +} + +static void +fillFaceFields(LWT_ISO_FACE* face, HeapTuple row, TupleDesc rowdesc, int fields) +{ + bool isnull; + Datum dat; + GSERIALIZED *geom; + LWGEOM *g; + const GBOX *box; + int colno = 0; + + if ( fields & LWT_COL_FACE_FACE_ID ) { + dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); + face->face_id = DatumGetInt32(dat); + } + if ( fields & LWT_COL_FACE_MBR ) { + dat = SPI_getbinval(row, rowdesc, ++colno, &isnull); + if ( ! isnull ) { + /* NOTE: this is a geometry of which we want to take (and clone) the BBOX */ + geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(dat); + g = lwgeom_from_gserialized(geom); + box = lwgeom_get_bbox(g); + if ( box ) { + face->mbr = gbox_clone(box); + } else { + lwpgnotice("Found face with EMPTY MBR !"); + face->mbr = NULL; + } + } else { + /* NOTE: perfectly fine for universe face */ + POSTGIS_DEBUG(1, "Found face with NULL MBR"); + face->mbr = NULL; + } + } +#if POSTGIS_DEBUG_LEVEL > 1 + else { + face->mbr = (void*)0x67676767; /* debugging */ + } +#endif } /* return 0 on failure (null) 1 otherwise */ @@ -450,7 +703,9 @@ cb_getEdgeById(const LWT_BE_TOPOLOGY* topo, appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]); } appendStringInfoString(sql, ")"); - spi_result = SPI_execute(sql->data, true, *numelems); + POSTGIS_DEBUGF(1, "cb_getEdgeById query: %s", sql->data); + + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, *numelems); if ( spi_result != SPI_OK_SELECT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); *numelems = -1; return NULL; @@ -474,71 +729,48 @@ cb_getEdgeById(const LWT_BE_TOPOLOGY* topo, } static LWT_ISO_EDGE* -cb_getEdgeWithinDistance2D(const LWT_BE_TOPOLOGY* topo, - const LWPOINT* pt, double dist, int* numelems, - int fields, int limit) +cb_getEdgeByNode(const LWT_BE_TOPOLOGY* topo, + const LWT_ELEMID* ids, int* numelems, int fields) { LWT_ISO_EDGE *edges; int spi_result; - int elems_requested = limit; - size_t hexewkb_size; - char *hexewkb; StringInfoData sqldata; StringInfo sql = &sqldata; int i; initStringInfo(sql); - if ( elems_requested == -1 ) { - appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1"); - } else { - appendStringInfoString(sql, "SELECT "); - addEdgeFields(sql, fields, 0); - } + appendStringInfoString(sql, "SELECT "); + addEdgeFields(sql, fields, 0); appendStringInfo(sql, " FROM \"%s\".edge_data", topo->name); - // TODO: use binary cursor here ? - hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size); - if ( dist ) { - appendStringInfo(sql, " WHERE ST_DWithin('%s'::geometry, geom, %g)", hexewkb, dist); - } else { - appendStringInfo(sql, " WHERE ST_Within('%s'::geometry, geom)", hexewkb); + appendStringInfoString(sql, " WHERE start_node IN ("); + // add all identifiers here + for (i=0; i<*numelems; ++i) { + appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]); } - lwfree(hexewkb); - if ( elems_requested == -1 ) { - appendStringInfoString(sql, ")"); - } else if ( elems_requested > 0 ) { - appendStringInfo(sql, " LIMIT %d", elems_requested); + appendStringInfoString(sql, ") OR end_node IN ("); + // add all identifiers here + for (i=0; i<*numelems; ++i) { + appendStringInfo(sql, "%s%lld", (i?",":""), ids[i]); } - lwpgnotice("cb_getEdgeWithinDistance2D: query is: %s", sql->data); - spi_result = SPI_execute(sql->data, true, limit >= 0 ? limit : 0); + appendStringInfoString(sql, ")"); + + POSTGIS_DEBUGF(1, "cb_getEdgeByNode query: %s", sql->data); + POSTGIS_DEBUGF(1, "data_changed is %d", topo->be_data->data_changed); + + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0); if ( spi_result != SPI_OK_SELECT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); *numelems = -1; return NULL; } pfree(sqldata.data); - lwpgnotice("cb_getEdgeWithinDistance2D: edge query " - "(limited by %d) returned %d rows", - elems_requested, SPI_processed); + POSTGIS_DEBUGF(1, "cb_getEdgeByNode: edge query returned %d rows", SPI_processed); *numelems = SPI_processed; if ( ! SPI_processed ) { return NULL; } - if ( elems_requested == -1 ) - { - /* This was an EXISTS query */ - { - Datum dat; - bool isnull, exists; - dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); - exists = DatumGetBool(dat); - *numelems = exists ? 1 : 0; - lwpgnotice("cb_getEdgeWithinDistance2D: exists ? %d", *numelems); - } - return NULL; - } - edges = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed ); for ( i=0; iname); + 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; ivals[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; ivals[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; ivals[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; ivals[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; ivals[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; ivals[i]; + fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields); + } + + return edges; +} + +static LWT_ISO_NODE* +cb_getNodeWithinDistance2D(const LWT_BE_TOPOLOGY* topo, + const LWPOINT* pt, double dist, int* numelems, + int fields, int limit) +{ + LWT_ISO_NODE *nodes; + int spi_result; + size_t hexewkb_size; + char *hexewkb; + StringInfoData sqldata; + StringInfo sql = &sqldata; + int elems_requested = limit; + int i; + + initStringInfo(sql); + if ( elems_requested == -1 ) { + appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1"); + } else { + appendStringInfoString(sql, "SELECT "); + if ( fields ) addNodeFields(sql, fields); + else { + lwpgwarning("liblwgeom-topo invoked 'getNodeWithinDistance2D' " + "backend callback with limit=%d and no fields", + elems_requested); + appendStringInfo(sql, "*"); + } + } + appendStringInfo(sql, " FROM \"%s\".node", topo->name); + // TODO: use binary cursor here ? + hexewkb = lwgeom_to_hexwkb(lwpoint_as_lwgeom(pt), WKB_EXTENDED, &hexewkb_size); + if ( dist ) { + appendStringInfo(sql, " WHERE ST_DWithin(geom, '%s'::geometry, dist)", + hexewkb); + } else { + appendStringInfo(sql, " WHERE ST_Within(geom, '%s'::geometry)", hexewkb); + } + lwfree(hexewkb); + if ( elems_requested == -1 ) { + appendStringInfoString(sql, ")"); + } else if ( elems_requested > 0 ) { + appendStringInfo(sql, " LIMIT %d", elems_requested); + } + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0); if ( spi_result != SPI_OK_SELECT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); @@ -647,10 +1217,12 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo, for ( i=0; idata); + spi_result = SPI_execute(sql->data, false, numelems); if ( spi_result != SPI_OK_INSERT_RETURNING ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", @@ -659,6 +1231,8 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo, } pfree(sqldata.data); + if ( SPI_processed ) topo->be_data->data_changed = true; + if ( SPI_processed != numelems ) { cberror(topo->be_data, "processed %d rows, expected %d", SPI_processed, numelems); @@ -674,55 +1248,110 @@ cb_insertNodes( const LWT_BE_TOPOLOGY* topo, SPI_tuptable->tupdesc, LWT_COL_NODE_NODE_ID); } - return 1; + return 1; +} + +static int +cb_insertEdges( const LWT_BE_TOPOLOGY* topo, + LWT_ISO_EDGE* edges, int numelems ) +{ + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + int i; + int needsEdgeIdReturn = 0; + + initStringInfo(sql); + /* NOTE: we insert into "edge", on which an insert rule is defined */ + appendStringInfo(sql, "INSERT INTO \"%s\".edge_data (", topo->name); + addEdgeFields(sql, LWT_COL_EDGE_ALL, 1); + appendStringInfoString(sql, ") VALUES "); + for ( i=0; idata); + 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; ivals[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; isrid); + 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; ivals[i], - SPI_tuptable->tupdesc, LWT_COL_EDGE_EDGE_ID); + if ( faces[i].face_id != -1 ) continue; + fillFaceFields(&faces[i], SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, LWT_COL_FACE_FACE_ID); } } @@ -751,7 +1380,7 @@ cb_updateEdges( const LWT_BE_TOPOLOGY* topo, addEdgeUpdate( sql, exc_edge, exc_fields, 1, updNot ); } - /* lwpgnotice("cb_updateEdges: %s", sql->data); */ + POSTGIS_DEBUGF(1, "cb_updateEdges query: %s", sql->data); spi_result = SPI_execute( sql->data, false, 0 ); if ( spi_result != SPI_OK_UPDATE ) @@ -762,11 +1391,239 @@ cb_updateEdges( const LWT_BE_TOPOLOGY* topo, } pfree(sqldata.data); + if ( SPI_processed ) topo->be_data->data_changed = true; + lwpgnotice("cb_updateEdges: update query processed %d rows", SPI_processed); return SPI_processed; } +static int +cb_updateNodes( const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_NODE* sel_node, int sel_fields, + const LWT_ISO_NODE* upd_node, int upd_fields, + const LWT_ISO_NODE* exc_node, int exc_fields ) +{ + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + + initStringInfo(sql); + appendStringInfo(sql, "UPDATE \"%s\".node SET ", topo->name); + addNodeUpdate( sql, upd_node, upd_fields, 1, updSet ); + if ( exc_node || sel_node ) appendStringInfoString(sql, " WHERE "); + if ( sel_node ) { + addNodeUpdate( sql, sel_node, sel_fields, 1, updSel ); + if ( exc_node ) appendStringInfoString(sql, " AND "); + } + if ( exc_node ) { + addNodeUpdate( sql, exc_node, exc_fields, 1, updNot ); + } + + POSTGIS_DEBUGF(1, "cb_updateNodes: %s", sql->data); + + spi_result = SPI_execute( sql->data, false, 0 ); + if ( spi_result != SPI_OK_UPDATE ) + { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return -1; + } + pfree(sqldata.data); + + if ( SPI_processed ) topo->be_data->data_changed = true; + + POSTGIS_DEBUGF(1, "cb_updateNodes: update query processed %d rows", SPI_processed); + + return SPI_processed; +} + +static int +cb_updateNodesById( const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_NODE* nodes, int numnodes, int fields ) +{ + int i; + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + const char *sep = ""; + const char *sep1 = ","; + + if ( ! fields ) { + cberror(topo->be_data, + "updateNodesById callback called with no update fields!"); + return -1; + } + + POSTGIS_DEBUGF(1, "cb_updateNodesById got %d nodes to update" + " (fields:%d)", + numnodes, fields); + + initStringInfo(sql); + appendStringInfoString(sql, "WITH newnodes(node_id,"); + addNodeFields(sql, fields); + appendStringInfoString(sql, ") AS ( VALUES "); + for (i=0; iname); + + /* 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; iface_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; iname); + + /* TODO: turn the following into a function */ + if ( fields & LWT_COL_EDGE_START_NODE ) { + appendStringInfo(sql, "%sstart_node = o.start_node", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_END_NODE ) { + appendStringInfo(sql, "%send_node = o.end_node", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_FACE_LEFT ) { + appendStringInfo(sql, "%sleft_face = o.left_face", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_FACE_RIGHT ) { + appendStringInfo(sql, "%sright_face = o.right_face", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_NEXT_LEFT ) { + appendStringInfo(sql, + "%snext_left_edge = o.next_left_edge, " + "abs_next_left_edge = abs(o.next_left_edge)", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_NEXT_RIGHT ) { + appendStringInfo(sql, + "%snext_right_edge = o.next_right_edge, " + "abs_next_right_edge = abs(o.next_right_edge)", sep); + sep = sep1; + } + if ( fields & LWT_COL_EDGE_GEOM ) { + appendStringInfo(sql, "%sgeom = o.geom", sep); + } + + appendStringInfo(sql, " FROM newedges o WHERE e.edge_id = o.edge_id"); + + POSTGIS_DEBUGF(1, "cb_updateEdgesById query: %s", sql->data); + + spi_result = SPI_execute( sql->data, false, 0 ); + if ( spi_result != SPI_OK_UPDATE ) + { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return -1; + } + pfree(sqldata.data); + + if ( SPI_processed ) topo->be_data->data_changed = true; + + POSTGIS_DEBUGF(1, "cb_updateEdgesById: update query processed %d rows", SPI_processed); + + return SPI_processed; +} + static int cb_deleteEdges( const LWT_BE_TOPOLOGY* topo, const LWT_ISO_EDGE* sel_edge, int sel_fields ) @@ -779,7 +1636,7 @@ cb_deleteEdges( const LWT_BE_TOPOLOGY* topo, appendStringInfo(sql, "DELETE FROM \"%s\".edge_data WHERE ", topo->name); addEdgeUpdate( sql, sel_edge, sel_fields, 0, updSel ); - /* lwpgnotice("cb_deleteEdges: %s", sql->data); */ + POSTGIS_DEBUGF(1, "cb_deleteEdges: %s", sql->data); spi_result = SPI_execute( sql->data, false, 0 ); if ( spi_result != SPI_OK_DELETE ) @@ -790,50 +1647,163 @@ cb_deleteEdges( const LWT_BE_TOPOLOGY* topo, } pfree(sqldata.data); - lwpgnotice("cb_deleteEdges: delete query processed %d rows", SPI_processed); + if ( SPI_processed ) topo->be_data->data_changed = true; + + POSTGIS_DEBUGF(1, "cb_deleteEdges: delete query processed %d rows", SPI_processed); + + return SPI_processed; +} + +static LWT_ELEMID +cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo ) +{ + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + bool isnull; + Datum dat; + LWT_ELEMID edge_id; + + initStringInfo(sql); + appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')", + topo->name); + spi_result = SPI_execute(sql->data, false, 0); + if ( spi_result != SPI_OK_SELECT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return -1; + } + pfree(sqldata.data); + + if ( SPI_processed ) topo->be_data->data_changed = true; + + if ( SPI_processed != 1 ) { + cberror(topo->be_data, "processed %d rows, expected 1", SPI_processed); + return -1; + } + + dat = SPI_getbinval( SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, 1, &isnull ); + if ( isnull ) { + cberror(topo->be_data, "nextval for edge_id returned null"); + return -1; + } + edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */ + return edge_id; +} + +static int +cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 ) +{ + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + int i, ntopogeoms; + const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type"; + + initStringInfo(sql); + if ( new_edge2 == -1 ) { + appendStringInfo(sql, "SELECT %s", proj); + } else { + appendStringInfoString(sql, "DELETE"); + } + appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE " + "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id " + "AND abs(r.element_id) = %lld AND r.element_type = 2", + topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge ); + if ( new_edge2 != -1 ) { + appendStringInfo(sql, " RETURNING %s", proj); + } + + spi_result = SPI_execute(sql->data, new_edge2 == -1 ? !topo->be_data->data_changed : false, 0); + if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + + if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed ) + { + topo->be_data->data_changed = true; + } + + ntopogeoms = SPI_processed; + for ( i=0; ivals[i]; + TupleDesc tdesc = SPI_tuptable->tupdesc; + int negate; + int element_id; + int topogeo_id; + int layer_id; + int element_type; + + if ( ! getNotNullInt32( row, tdesc, 1, &element_id ) ) { + cberror(topo->be_data, + "unexpected null element_id in \"%s\".relation", + topo->name); + return 0; + } + negate = ( element_id < 0 ); + + if ( ! getNotNullInt32( row, tdesc, 2, &topogeo_id ) ) { + cberror(topo->be_data, + "unexpected null topogeo_id in \"%s\".relation", + topo->name); + return 0; + } - return SPI_processed; -} + if ( ! getNotNullInt32( row, tdesc, 3, &layer_id ) ) { + cberror(topo->be_data, + "unexpected null layer_id in \"%s\".relation", + topo->name); + return 0; + } -static LWT_ELEMID -cb_getNextEdgeId( const LWT_BE_TOPOLOGY* topo ) -{ - int spi_result; - StringInfoData sqldata; - StringInfo sql = &sqldata; - bool isnull; - Datum dat; - LWT_ELEMID edge_id; + if ( ! getNotNullInt32( row, tdesc, 4, &element_type ) ) { + cberror(topo->be_data, + "unexpected null element_type in \"%s\".relation", + topo->name); + return 0; + } - initStringInfo(sql); - appendStringInfo(sql, "SELECT nextval('\"%s\".edge_data_edge_id_seq')", - topo->name); - spi_result = SPI_execute(sql->data, false, 0); - if ( spi_result != SPI_OK_SELECT ) { - cberror(topo->be_data, "unexpected return (%d) from query execution: %s", - spi_result, sql->data); - return -1; + resetStringInfo(sql); + appendStringInfo(sql, + "INSERT INTO \"%s\".relation VALUES (" + "%d,%d,%lld,%d)", topo->name, + topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type); + spi_result = SPI_execute(sql->data, false, 0); + if ( spi_result != SPI_OK_INSERT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + if ( SPI_processed ) topo->be_data->data_changed = true; + if ( new_edge2 != -1 ) { + resetStringInfo(sql); + appendStringInfo(sql, + "INSERT INTO FROM \"%s\".relation VALUES (" + "%d,%d,%lld,%d", topo->name, + topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type); + spi_result = SPI_execute(sql->data, false, 0); + if ( spi_result != SPI_OK_INSERT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + if ( SPI_processed ) topo->be_data->data_changed = true; + } } - pfree(sqldata.data); - if ( SPI_processed != 1 ) { - cberror(topo->be_data, "processed %d rows, expected 1", SPI_processed); - return -1; - } + POSTGIS_DEBUGF(1, "cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms); - dat = SPI_getbinval( SPI_tuptable->vals[0], - SPI_tuptable->tupdesc, 1, &isnull ); - if ( isnull ) { - cberror(topo->be_data, "nextval for edge_id returned null"); - return -1; - } - edge_id = DatumGetInt64(dat); /* sequences return 64bit integers */ - return edge_id; + return 1; } static int -cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo, - LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 ) +cb_updateTopoGeomFaceSplit ( const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 ) { int spi_result; StringInfoData sqldata; @@ -841,26 +1811,38 @@ cb_updateTopoGeomEdgeSplit ( const LWT_BE_TOPOLOGY* topo, int i, ntopogeoms; const char *proj = "r.element_id, r.topogeo_id, r.layer_id, r.element_type"; + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit signalled " + "split of face %lld into %lld and %lld", + split_face, new_face1, new_face2); + initStringInfo(sql); - if ( new_edge2 == -1 ) { + if ( new_face2 == -1 ) { appendStringInfo(sql, "SELECT %s", proj); } else { appendStringInfoString(sql, "DELETE"); } appendStringInfo( sql, " FROM \"%s\".relation r %s topology.layer l WHERE " "l.topology_id = %d AND l.level = 0 AND l.layer_id = r.layer_id " - "AND abs(r.element_id) = %lld AND r.element_type = 2", - topo->name, (new_edge2 == -1 ? "," : "USING" ), topo->id, split_edge ); - if ( new_edge2 != -1 ) { + "AND abs(r.element_id) = %lld AND r.element_type = 3", + topo->name, (new_face2 == -1 ? "," : "USING" ), topo->id, split_face ); + if ( new_face2 != -1 ) { appendStringInfo(sql, " RETURNING %s", proj); } - spi_result = SPI_execute(sql->data, new_edge2 == -1, 0); - if ( spi_result != ( new_edge2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) { + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data); + + spi_result = SPI_execute(sql->data, new_face2 == -1 ? !topo->be_data->data_changed : false, 0); + if ( spi_result != ( new_face2 == -1 ? SPI_OK_SELECT : SPI_OK_DELETE_RETURNING ) ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); return 0; } + + if ( spi_result == SPI_OK_DELETE_RETURNING && SPI_processed ) + { + topo->be_data->data_changed = true; + } + ntopogeoms = SPI_processed; for ( i=0; iname, - topogeo_id, layer_id, negate ? -new_edge1 : new_edge1, element_type); + topogeo_id, layer_id, negate ? -new_face1 : new_face1, element_type); + + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data); + spi_result = SPI_execute(sql->data, false, 0); if ( spi_result != SPI_OK_INSERT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); return 0; } - if ( new_edge2 != -1 ) { + if ( SPI_processed ) topo->be_data->data_changed = true; + if ( new_face2 != -1 ) { resetStringInfo(sql); appendStringInfo(sql, - "INSERT INTO FROM \"%s\".relation VALUES (" - "%d,%d,%lld,%d", topo->name, - topogeo_id, layer_id, negate ? -new_edge2 : new_edge2, element_type); + "INSERT INTO \"%s\".relation VALUES (" + "%d,%d,%lld,%d)", topo->name, + topogeo_id, layer_id, negate ? -new_face2 : new_face2, element_type); + + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit query: %s", sql->data); + spi_result = SPI_execute(sql->data, false, 0); if ( spi_result != SPI_OK_INSERT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); return 0; } + if ( SPI_processed ) topo->be_data->data_changed = true; } } - lwpgnotice("cb_updateTopoGeomEdgeSplit: updated %d topogeoms", ntopogeoms); + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceSplit: updated %d topogeoms", ntopogeoms); return 1; } @@ -955,7 +1945,7 @@ cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt ) topo->name, hexewkb, topo->name, hexewkb); lwfree(hexewkb); - spi_result = SPI_execute(sql->data, true, 1); + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 1); if ( spi_result != SPI_OK_SELECT ) { cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); @@ -977,12 +1967,149 @@ cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt ) return face_id; } -LWT_BE_CALLBACKS be_callbacks = { +static LWT_ISO_NODE* +cb_getNodeWithinBox2D ( const LWT_BE_TOPOLOGY* topo, const GBOX* box, + int* numelems, int fields, int limit ) +{ + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + int i; + int elems_requested = limit; + LWT_ISO_NODE* nodes; + + initStringInfo(sql); + + if ( elems_requested == -1 ) { + appendStringInfoString(sql, "SELECT EXISTS ( SELECT 1"); + } else { + appendStringInfoString(sql, "SELECT "); + addNodeFields(sql, fields); + } + appendStringInfo(sql, " FROM \"%s\".node WHERE geom && " + "ST_SetSRID(ST_MakeEnvelope(%g,%g,%g,%g),%d)", + topo->name, box->xmin, box->ymin, + box->xmax, box->ymax, topo->srid); + if ( elems_requested == -1 ) { + appendStringInfoString(sql, ")"); + } else if ( elems_requested > 0 ) { + appendStringInfo(sql, " LIMIT %d", elems_requested); + } + lwpgnotice("cb_getNodeWithinBox2D: query is: %s", sql->data); + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, limit >= 0 ? limit : 0); + if ( spi_result != SPI_OK_SELECT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", spi_result, sql->data); + *numelems = -1; return NULL; + } + pfree(sqldata.data); + + lwpgnotice("cb_getNodeWithinBox2D: edge query " + "(limited by %d) returned %d rows", + elems_requested, SPI_processed); + *numelems = SPI_processed; + if ( ! SPI_processed ) { + return NULL; + } + + if ( elems_requested == -1 ) + { + /* This was an EXISTS query */ + { + Datum dat; + bool isnull, exists; + dat = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull); + exists = DatumGetBool(dat); + *numelems = exists ? 1 : 0; + lwpgnotice("cb_getNodeWithinBox2D: exists ? %d", *numelems); + } + return NULL; + } + + nodes = palloc( sizeof(LWT_ISO_EDGE) * SPI_processed ); + for ( i=0; ivals[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; ivals[i]; + fillEdgeFields(&edges[i], row, SPI_tuptable->tupdesc, fields); + } + + return edges; +} + + +static LWT_BE_CALLBACKS be_callbacks = { cb_lastErrorMessage, NULL, /* createTopology */ - cb_loadTopologyByName, /* loadTopologyByName */ - cb_freeTopology, /* freeTopology */ - NULL, /* getNodeById */ + cb_loadTopologyByName, + cb_freeTopology, + cb_getNodeById, cb_getNodeWithinDistance2D, cb_insertNodes, cb_getEdgeById, @@ -990,10 +2117,22 @@ LWT_BE_CALLBACKS be_callbacks = { cb_getNextEdgeId, cb_insertEdges, cb_updateEdges, - NULL, /* getFacesById */ + cb_getFacesById, cb_getFaceContainingPoint, cb_updateTopoGeomEdgeSplit, - cb_deleteEdges + cb_deleteEdges, + cb_getNodeWithinBox2D, + cb_getEdgeWithinBox2D, + cb_getEdgeByNode, + cb_updateNodes, + cb_updateTopoGeomFaceSplit, + cb_insertFaces, + cb_updateFacesById, + cb_getRingEdges, + cb_updateEdgesById, + cb_getEdgeByFace, + cb_getNodeByFace, + cb_updateNodesById }; @@ -1077,6 +2216,7 @@ Datum ST_ModEdgeSplit(PG_FUNCTION_ARGS) lwpgerror("Could not connect to SPI"); PG_RETURN_NULL(); } + be_data.data_changed = false; topo = lwt_LoadTopology(be_iface, toponame); pfree(toponame); @@ -1142,6 +2282,7 @@ Datum ST_NewEdgesSplit(PG_FUNCTION_ARGS) lwpgerror("Could not connect to SPI"); PG_RETURN_NULL(); } + be_data.data_changed = false; topo = lwt_LoadTopology(be_iface, toponame); pfree(toponame); @@ -1218,6 +2359,7 @@ Datum ST_AddIsoNode(PG_FUNCTION_ARGS) lwpgerror("Could not connect to SPI"); PG_RETURN_NULL(); } + be_data.data_changed = false; topo = lwt_LoadTopology(be_iface, toponame); pfree(toponame); @@ -1243,3 +2385,70 @@ Datum ST_AddIsoNode(PG_FUNCTION_ARGS) SPI_finish(); PG_RETURN_INT32(node_id); } + +/* ST_AddEdgeModFace(atopology, snode, enode, line) */ +Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(ST_AddEdgeModFace); +Datum ST_AddEdgeModFace(PG_FUNCTION_ARGS) +{ + text* toponame_text; + char* toponame; + LWT_ELEMID startnode_id, endnode_id; + int edge_id; + GSERIALIZED *geom; + LWGEOM *lwgeom; + LWLINE *line; + LWT_TOPOLOGY *topo; + + if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2) || PG_ARGISNULL(3) ) { + lwpgerror("SQL/MM Spatial exception - null argument"); + PG_RETURN_NULL(); + } + + toponame_text = PG_GETARG_TEXT_P(0); + toponame = text2cstring(toponame_text); + PG_FREE_IF_COPY(toponame_text, 0); + + startnode_id = PG_GETARG_INT32(1) ; + endnode_id = PG_GETARG_INT32(2) ; + + geom = PG_GETARG_GSERIALIZED_P(3); + lwgeom = lwgeom_from_gserialized(geom); + line = lwgeom_as_lwline(lwgeom); + if ( ! line ) { + lwgeom_free(lwgeom); + PG_FREE_IF_COPY(geom, 2); + lwpgerror("ST_AddEdgeModFace fourth argument must be a line geometry"); + PG_RETURN_NULL(); + } + + if ( SPI_OK_CONNECT != SPI_connect() ) { + lwpgerror("Could not connect to SPI"); + PG_RETURN_NULL(); + } + be_data.data_changed = false; + + topo = lwt_LoadTopology(be_iface, toponame); + pfree(toponame); + if ( ! topo ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + POSTGIS_DEBUG(1, "Calling lwt_AddEdgeModFace"); + edge_id = lwt_AddEdgeModFace(topo, startnode_id, endnode_id, line, 0); + POSTGIS_DEBUG(1, "lwt_AddEdgeModFace returned"); + lwgeom_free(lwgeom); + PG_FREE_IF_COPY(geom, 3); + lwt_FreeTopology(topo); + + if ( edge_id == -1 ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + SPI_finish(); + PG_RETURN_INT32(edge_id); +} diff --git a/topology/sql/sqlmm.sql.in b/topology/sql/sqlmm.sql.in index 3bc9232cf..b6f3f2d64 100644 --- a/topology/sql/sqlmm.sql.in +++ b/topology/sql/sqlmm.sql.in @@ -3116,621 +3116,8 @@ LANGUAGE 'plpgsql' VOLATILE; -- CREATE OR REPLACE FUNCTION topology.ST_AddEdgeModFace(atopology varchar, anode integer, anothernode integer, acurve geometry) RETURNS INTEGER AS -$$ -DECLARE - rec RECORD; - rrec RECORD; - i INTEGER; - topoid INTEGER; - az FLOAT8; - span RECORD; -- start point analysis data - epan RECORD; -- end point analysis data - fan RECORD; -- face analisys - newedge RECORD; -- informations about new edge - sql TEXT; - newfaces INTEGER[]; - newface INTEGER; -BEGIN - - -- - -- All args required - -- - IF atopology IS NULL - OR anode IS NULL - OR anothernode IS NULL - OR acurve IS NULL - THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - null argument'; - END IF; - - -- - -- Acurve must be a LINESTRING - -- - IF substring(geometrytype(acurve), 1, 4) != 'LINE' - THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid curve'; - END IF; - - -- - -- Curve must be simple - -- - IF NOT ST_IsSimple(acurve) THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - curve not simple'; - END IF; - - -- - -- Get topology id - -- - BEGIN - SELECT id FROM topology.topology - INTO STRICT topoid WHERE name = atopology; - EXCEPTION - WHEN NO_DATA_FOUND THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; - END; - - -- Initialize new edge info (will be filled up more later) - SELECT anode as start_node, anothernode as end_node, acurve as geom, - NULL::int as next_left_edge, NULL::int as next_right_edge, - NULL::int as left_face, NULL::int as right_face, NULL::int as edge_id, - NULL::int as prev_left_edge, NULL::int as prev_right_edge, -- convenience - anode = anothernode as isclosed, -- convenience - false as start_node_isolated, -- convenience - false as end_node_isolated, -- convenience - NULL::geometry as start_node_geom, -- convenience - NULL::geometry as end_node_geom, -- convenience - ST_RemoveRepeatedPoints(acurve) as cleangeom -- convenience - INTO newedge; - - -- Compute azimut of first edge end on start node - SELECT null::int AS nextCW, null::int AS nextCCW, - null::float8 AS minaz, null::float8 AS maxaz, - false AS was_isolated, - ST_Azimuth(ST_StartPoint(newedge.cleangeom), - ST_PointN(newedge.cleangeom, 2)) AS myaz - INTO span; - IF span.myaz IS NULL THEN - RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)'; - END IF; - - -- Compute azimuth of last edge end on end node - SELECT null::int AS nextCW, null::int AS nextCCW, - null::float8 AS minaz, null::float8 AS maxaz, - false AS was_isolated, - ST_Azimuth(ST_EndPoint(newedge.cleangeom), - ST_PointN(newedge.cleangeom, - ST_NumPoints(newedge.cleangeom)-1)) AS myaz - INTO epan; - IF epan.myaz IS NULL THEN - RAISE EXCEPTION 'Invalid edge (no two distinct vertices exist)'; - END IF; - - - -- - -- Check endpoints existance, match with Curve geometry - -- and get face information (if any) - -- - i := 0; - FOR rec IN EXECUTE 'SELECT node_id, containing_face, geom FROM ' - || quote_ident(atopology) - || '.node WHERE node_id IN ( ' - || anode || ',' || anothernode - || ')' - LOOP - IF rec.containing_face IS NOT NULL THEN -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'containing_face for node %:%', - rec.node_id, rec.containing_face; -#endif - IF newedge.left_face IS NULL THEN - newedge.left_face := rec.containing_face; - newedge.right_face := rec.containing_face; - ELSE - IF newedge.left_face != rec.containing_face THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - geometry crosses an edge (endnodes in faces % and %)', newedge.left_face, rec.containing_face; - END IF; - END IF; - END IF; - - IF rec.node_id = anode THEN - newedge.start_node_geom = rec.geom; - END IF; - - IF rec.node_id = anothernode THEN - newedge.end_node_geom = rec.geom; - END IF; - - i := i + 1; - END LOOP; - - IF newedge.start_node_geom IS NULL - THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent node'; - ELSIF NOT ST_Equals(newedge.start_node_geom, ST_StartPoint(acurve)) - THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - start node not geometry start point.'; - END IF; - - IF newedge.end_node_geom IS NULL - THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent node'; - ELSIF NOT ST_Equals(newedge.end_node_geom, ST_EndPoint(acurve)) - THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - end node not geometry end point.'; - END IF; - - -- - -- Check if this geometry crosses any node - -- - FOR rec IN EXECUTE - 'SELECT node_id, ST_Relate(geom, $1, 2) as relate FROM ' - || quote_ident(atopology) - || '.node WHERE geom && $1' - USING acurve - LOOP - IF ST_RelateMatch(rec.relate, 'T********') THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - geometry crosses a node'; - END IF; - END LOOP; - - -- - -- Check if this geometry has any interaction with any existing edge - -- - FOR rec IN EXECUTE 'SELECT edge_id, ST_Relate(geom, $1, 2) as im FROM ' - || quote_ident(atopology) - || '.edge_data WHERE geom && $1' - USING acurve - LOOP - - --RAISE DEBUG 'IM=%',rec.im; - - IF ST_RelateMatch(rec.im, 'F********') THEN - CONTINUE; -- no interior intersection - END IF; - - IF ST_RelateMatch(rec.im, '1FFF*FFF2') THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - coincident edge %', rec.edge_id; - END IF; - - -- NOT IN THE SPECS: geometry touches an edge - IF ST_RelateMatch(rec.im, '1********') THEN - RAISE EXCEPTION - 'Spatial exception - geometry intersects edge %', rec.edge_id; - END IF; - - IF ST_RelateMatch(rec.im, 'T********') THEN - RAISE EXCEPTION - 'SQL/MM Spatial exception - geometry crosses edge %', rec.edge_id; - END IF; - - END LOOP; - - --------------------------------------------------------------- - -- - -- All checks passed, time to prepare the new edge - -- - --------------------------------------------------------------- - - EXECUTE 'SELECT nextval(' || quote_literal( - quote_ident(atopology) || '.edge_data_edge_id_seq') || ')' - INTO STRICT newedge.edge_id; - - - -- Find links on start node -- { - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'My start-segment azimuth: %', span.myaz; -#endif - - sql := - 'SELECT edge_id, -1 AS end_node, start_node, left_face, right_face, ' - || 'ST_RemoveRepeatedPoints(geom) as geom FROM ' - || quote_ident(atopology) - || '.edge_data WHERE start_node = $1' - || ' UNION SELECT edge_id, end_node, -1, left_face, right_face, ' - || 'ST_RemoveRepeatedPoints(geom) FROM ' - || quote_ident(atopology) - || '.edge_data WHERE end_node = $1'; - IF newedge.isclosed THEN - -- pretend we start elsewhere - sql := sql || ' UNION SELECT $2, $3, -1, 0, 0, $4'; - END IF; - i := 0; - FOR rec IN EXECUTE sql USING - anode, newedge.edge_id, newedge.end_node, newedge.cleangeom - LOOP -- incident edges { - - i := i + 1; - - IF rec.start_node = anode THEN - -- - -- Edge starts at our node, we compute - -- azimuth from node to its second point - -- - az := ST_Azimuth(ST_StartPoint(rec.geom), ST_PointN(rec.geom, 2)); - - ELSE - -- - -- Edge ends at our node, we compute - -- azimuth from node to its second-last point - -- - az := ST_Azimuth(ST_EndPoint(rec.geom), - ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1)); - rec.edge_id := -rec.edge_id; - - END IF; - - IF az IS NULL THEN - RAISE EXCEPTION 'Invalid edge % found (no two distinct nodes exist)', - rec.edge_id; - END IF; - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Edge % - az % (%) - fl:% fr:%', - rec.edge_id, az, az - span.myaz, rec.left_face, rec.right_face; -#endif - - az = az - span.myaz; - IF az < 0 THEN - az := az + 2*PI(); - END IF; - - -- RAISE DEBUG ' normalized az %', az; - - IF span.maxaz IS NULL OR az > span.maxaz THEN - span.maxaz := az; - span.nextCCW := rec.edge_id; - IF abs(rec.edge_id) != newedge.edge_id THEN - IF rec.edge_id < 0 THEN - -- TODO: check for mismatch ? - newedge.left_face := rec.left_face; - ELSE - -- TODO: check for mismatch ? - newedge.left_face := rec.right_face; - END IF; - END IF; - END IF; - - IF span.minaz IS NULL OR az < span.minaz THEN - span.minaz := az; - span.nextCW := rec.edge_id; - IF abs(rec.edge_id) != newedge.edge_id THEN - IF rec.edge_id < 0 THEN - -- TODO: check for mismatch ? - newedge.right_face := rec.right_face; - ELSE - -- TODO: check for mismatch ? - newedge.right_face := rec.left_face; - END IF; - END IF; - END IF; - - --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', span.nextCW, span.minaz, span.nextCCW, span.maxaz; - - END LOOP; -- incident edges } - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'span ROW_COUNT: %', i; -#endif - IF newedge.isclosed THEN - IF i < 2 THEN span.was_isolated = true; END IF; - ELSE - IF i < 1 THEN span.was_isolated = true; END IF; - END IF; - - IF span.nextCW IS NULL THEN - -- This happens if the destination node is isolated - newedge.next_right_edge := newedge.edge_id; - newedge.prev_left_edge := -newedge.edge_id; - ELSE - newedge.next_right_edge := span.nextCW; - newedge.prev_left_edge := -span.nextCCW; - END IF; - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'edge:%', newedge.edge_id; -#endif -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' left:%, next:%, prev:%', - newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge; -#endif -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' right:%, next:%, prev:%', - newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge; -#endif - - -- } start_node analysis - - - -- Find links on end_node { - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'My end-segment azimuth: %', epan.myaz; -#endif - - sql := - 'SELECT edge_id, -1 as end_node, start_node, left_face, right_face, ' - || 'ST_RemoveRepeatedPoints(geom) as geom FROM ' - || quote_ident(atopology) - || '.edge_data WHERE start_node = $1' - || 'UNION SELECT edge_id, end_node, -1, left_face, right_face, ' - || 'ST_RemoveRepeatedPoints(geom) FROM ' - || quote_ident(atopology) - || '.edge_data WHERE end_node = $1'; - IF newedge.isclosed THEN - -- pretend we end elsewhere (-1) - sql := sql || ' UNION SELECT $2, -1, $3, 0, 0, $4'; - END IF; - i := 0; - FOR rec IN EXECUTE sql USING - anothernode, newedge.edge_id, newedge.start_node, newedge.cleangeom - LOOP -- incident edges { - - i := i + 1; - - IF rec.start_node = anothernode THEN - -- - -- Edge starts at our node, we compute - -- azimuth from node to its second point - -- - az := ST_Azimuth(ST_StartPoint(rec.geom), - ST_PointN(rec.geom, 2)); - - ELSE - -- - -- Edge ends at our node, we compute - -- azimuth from node to its second-last point - -- - az := ST_Azimuth(ST_EndPoint(rec.geom), - ST_PointN(rec.geom, ST_NumPoints(rec.geom)-1)); - rec.edge_id := -rec.edge_id; - - END IF; - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Edge % - az % (%)', rec.edge_id, az, az - epan.myaz; -#endif - - az := az - epan.myaz; - IF az < 0 THEN - az := az + 2*PI(); - END IF; - - -- RAISE DEBUG ' normalized az %', az; - - IF epan.maxaz IS NULL OR az > epan.maxaz THEN - epan.maxaz := az; - epan.nextCCW := rec.edge_id; - IF abs(rec.edge_id) != newedge.edge_id THEN - IF rec.edge_id < 0 THEN - -- TODO: check for mismatch ? - newedge.right_face := rec.left_face; - ELSE - -- TODO: check for mismatch ? - newedge.right_face := rec.right_face; - END IF; - END IF; - END IF; - - IF epan.minaz IS NULL OR az < epan.minaz THEN - epan.minaz := az; - epan.nextCW := rec.edge_id; - IF abs(rec.edge_id) != newedge.edge_id THEN - IF rec.edge_id < 0 THEN - -- TODO: check for mismatch ? - newedge.left_face := rec.right_face; - ELSE - -- TODO: check for mismatch ? - newedge.left_face := rec.left_face; - END IF; - END IF; - END IF; - - --RAISE DEBUG 'Closest edges: CW:%(%) CCW:%(%)', epan.nextCW, epan.minaz, epan.nextCCW, epan.maxaz; - - END LOOP; -- incident edges } - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'epan ROW_COUNT: %', i; -#endif - IF newedge.isclosed THEN - IF i < 2 THEN epan.was_isolated = true; END IF; - ELSE - IF i < 1 THEN epan.was_isolated = true; END IF; - END IF; - - IF epan.nextCW IS NULL THEN - -- This happens if the destination node is isolated - newedge.next_left_edge := -newedge.edge_id; - newedge.prev_right_edge := newedge.edge_id; - ELSE - newedge.next_left_edge := epan.nextCW; - newedge.prev_right_edge := -epan.nextCCW; - END IF; - - -- } end_node analysis - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'edge:%', newedge.edge_id; -#endif -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' left:%, next:%, prev:%', - newedge.left_face, newedge.next_left_edge, newedge.prev_left_edge; -#endif -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' right:%, next:%, prev:%', - newedge.right_face, newedge.next_right_edge, newedge.prev_right_edge; -#endif - - ---------------------------------------------------------------------- - -- - -- If we don't have faces setup by now we must have encountered - -- a malformed topology (no containing_face on isolated nodes, no - -- left/right faces on adjacent edges or mismatching values) - -- - ---------------------------------------------------------------------- - IF newedge.left_face != newedge.right_face THEN - RAISE EXCEPTION 'Left(%)/right(%) faces mismatch: invalid topology ?', - newedge.left_face, newedge.right_face; - END IF; - IF newedge.left_face IS NULL THEN - RAISE EXCEPTION 'Could not derive edge face from linked primitives: invalid topology ?'; - END IF; - - ---------------------------------------------------------------------- - -- - -- Insert the new edge, and update all linking - -- - ---------------------------------------------------------------------- - - -- Insert the new edge with what we have so far - EXECUTE 'INSERT INTO ' || quote_ident(atopology) - || '.edge VALUES(' || newedge.edge_id - || ',' || newedge.start_node - || ',' || newedge.end_node - || ',' || newedge.next_left_edge - || ',' || newedge.next_right_edge - || ',' || newedge.left_face - || ',' || newedge.right_face - || ',$1)' - USING newedge.geom - ; - - -- Link prev_left_edge to us - -- (if it's not us already) - IF abs(newedge.prev_left_edge) != newedge.edge_id THEN - IF newedge.prev_left_edge > 0 THEN - -- its next_left_edge is us - EXECUTE 'UPDATE ' || quote_ident(atopology) - || '.edge_data SET next_left_edge = ' - || newedge.edge_id - || ', abs_next_left_edge = ' - || newedge.edge_id - || ' WHERE edge_id = ' - || newedge.prev_left_edge; - ELSE - -- its next_right_edge is us - EXECUTE 'UPDATE ' || quote_ident(atopology) - || '.edge_data SET next_right_edge = ' - || newedge.edge_id - || ', abs_next_right_edge = ' - || newedge.edge_id - || ' WHERE edge_id = ' - || -newedge.prev_left_edge; - END IF; - END IF; - - -- Link prev_right_edge to us - -- (if it's not us already) - IF abs(newedge.prev_right_edge) != newedge.edge_id THEN - IF newedge.prev_right_edge > 0 THEN - -- its next_left_edge is -us - EXECUTE 'UPDATE ' || quote_ident(atopology) - || '.edge_data SET next_left_edge = ' - || -newedge.edge_id - || ', abs_next_left_edge = ' - || newedge.edge_id - || ' WHERE edge_id = ' - || newedge.prev_right_edge; - ELSE - -- its next_right_edge is -us - EXECUTE 'UPDATE ' || quote_ident(atopology) - || '.edge_data SET next_right_edge = ' - || -newedge.edge_id - || ', abs_next_right_edge = ' - || newedge.edge_id - || ' WHERE edge_id = ' - || -newedge.prev_right_edge; - END IF; - END IF; - - -- NOT IN THE SPECS... - -- set containing_face = null for start_node and end_node - -- if they where isolated - IF span.was_isolated OR epan.was_isolated THEN - EXECUTE 'UPDATE ' || quote_ident(atopology) - || '.node SET containing_face = null WHERE node_id IN (' - || anode || ',' || anothernode || ')'; - END IF; - - -------------------------------------------- - -- Check face splitting - -------------------------------------------- - -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Checking left face for a split'; -#endif - SELECT topology._ST_AddFaceSplit(atopology, newedge.edge_id, newedge.left_face, false) - INTO newface; - IF newface = 0 THEN -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' No split'; -#endif - RETURN newedge.edge_id; - END IF; - - IF newface IS NULL THEN -- must be forming a maximal ring in universal face -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Checking right face'; -#endif - SELECT topology._ST_AddFaceSplit(atopology, -newedge.edge_id, newedge.left_face, false) - INTO newface; - ELSE -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Updating right face mbr'; -#endif - PERFORM topology._ST_AddFaceSplit(atopology, -newedge.edge_id, newedge.left_face, true); - END IF; - - IF newface IS NULL THEN -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG ' No split'; -#endif - RETURN newedge.edge_id; - END IF; - - -------------------------------------------- - -- Update topogeometries, if needed - -------------------------------------------- - - IF newedge.left_face != 0 THEN -- { - - -- NOT IN THE SPECS: - -- update TopoGeometry compositions to add newface - sql := 'SELECT r.topogeo_id, r.layer_id FROM ' - || quote_ident(atopology) - || '.relation r, topology.layer l ' - || ' WHERE l.topology_id = ' || topoid - || ' AND l.level = 0 ' - || ' AND l.layer_id = r.layer_id ' - || ' AND r.element_id = ' || newedge.left_face - || ' AND r.element_type = 3 '; - --RAISE DEBUG 'SQL: %', sql; - FOR rec IN EXECUTE sql - LOOP -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'TopoGeometry % in layer % contained the face being split (%) - updating to contain also new face %', rec.topogeo_id, rec.layer_id, newedge.left_face, newface; -#endif - - -- Add reference to the other face - sql := 'INSERT INTO ' || quote_ident(atopology) - || '.relation VALUES( ' || rec.topogeo_id - || ',' || rec.layer_id || ',' || newface || ', 3)'; - --RAISE DEBUG 'SQL: %', sql; - EXECUTE sql; - - END LOOP; - - END IF; -- } - - RETURN newedge.edge_id; -END -$$ -LANGUAGE 'plpgsql' VOLATILE; + 'MODULE_PATHNAME','ST_AddEdgeModFace' + LANGUAGE 'c' VOLATILE; --} ST_AddEdgeModFace --{