postgis/liblwgeom/lwgeom_topo.c
Sandro Santilli 9db2a5fb0c Fix segfault in _lwt_RemEdge when no edge side-faces are found
References #5106 in master branch (3.4.0dev)

Includes regress tests
2023-01-30 16:21:15 +01:00

7353 lines
211 KiB
C

/**********************************************************************
*
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
*
* PostGIS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PostGIS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
*
**********************************************************************
*
* Copyright (C) 2015-2022 Sandro Santilli <strk@kbt.io>
*
**********************************************************************/
#include "../postgis_config.h"
/*#define POSTGIS_DEBUG_LEVEL 1*/
#include "lwgeom_log.h"
#include "liblwgeom_internal.h"
#include "liblwgeom_topo_internal.h"
#include "lwgeom_geos.h"
#include <stdio.h>
#include <inttypes.h> /* for PRId64 */
#include <math.h>
#ifdef WIN32
# define LWTFMT_ELEMID "lld"
#else
# define LWTFMT_ELEMID PRId64
#endif
/*********************************************************************
*
* Backend iface
*
********************************************************************/
LWT_BE_IFACE* lwt_CreateBackendIface(const LWT_BE_DATA *data)
{
LWT_BE_IFACE *iface = lwalloc(sizeof(LWT_BE_IFACE));
iface->data = data;
iface->cb = NULL;
return iface;
}
void lwt_BackendIfaceRegisterCallbacks(LWT_BE_IFACE *iface,
const LWT_BE_CALLBACKS* cb)
{
iface->cb = cb;
}
void lwt_FreeBackendIface(LWT_BE_IFACE* iface)
{
lwfree(iface);
}
/*********************************************************************
*
* Backend wrappers
*
********************************************************************/
#define CHECKCB(be, method) do { \
if ( ! (be)->cb || ! (be)->cb->method ) \
lwerror("Callback " # method " not registered by backend"); \
} while (0)
#define CB0(be, method) \
CHECKCB(be, method);\
return (be)->cb->method((be)->data)
#define CB1(be, method, a1) \
CHECKCB(be, method);\
return (be)->cb->method((be)->data, a1)
#define CBT0(to, method) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo)
#define CBT1(to, method, a1) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1)
#define CBT2(to, method, a1, a2) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1, a2)
#define CBT3(to, method, a1, a2, a3) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3)
#define CBT4(to, method, a1, a2, a3, a4) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4)
#define CBT5(to, method, a1, a2, a3, a4, a5) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5)
#define CBT6(to, method, a1, a2, a3, a4, a5, a6) \
CHECKCB((to)->be_iface, method);\
return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5, a6)
const char *
lwt_be_lastErrorMessage(const LWT_BE_IFACE* be)
{
CB0(be, lastErrorMessage);
}
LWT_BE_TOPOLOGY *
lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name)
{
CB1(be, loadTopologyByName, name);
}
static int
lwt_be_topoGetSRID(LWT_TOPOLOGY *topo)
{
CBT0(topo, topoGetSRID);
}
static double
lwt_be_topoGetPrecision(LWT_TOPOLOGY *topo)
{
CBT0(topo, topoGetPrecision);
}
static int
lwt_be_topoHasZ(LWT_TOPOLOGY *topo)
{
CBT0(topo, topoHasZ);
}
int
lwt_be_freeTopology(LWT_TOPOLOGY *topo)
{
CBT0(topo, freeTopology);
}
LWT_ISO_NODE *
lwt_be_getNodeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
{
CBT3(topo, getNodeById, ids, numelems, fields);
}
LWT_ISO_NODE *
lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY *topo,
LWPOINT *pt,
double dist,
uint64_t *numelems,
int fields,
int64_t limit)
{
CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit);
}
static LWT_ISO_NODE *
lwt_be_getNodeWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit)
{
CBT4(topo, getNodeWithinBox2D, box, numelems, fields, limit);
}
static LWT_ISO_EDGE *
lwt_be_getEdgeWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit)
{
CBT4(topo, getEdgeWithinBox2D, box, numelems, fields, limit);
}
static LWT_ISO_FACE *
lwt_be_getFaceWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit)
{
CBT4(topo, getFaceWithinBox2D, box, numelems, fields, limit);
}
int
lwt_be_insertNodes(LWT_TOPOLOGY *topo, LWT_ISO_NODE *node, uint64_t numelems)
{
CBT2(topo, insertNodes, node, numelems);
}
static int
lwt_be_insertFaces(LWT_TOPOLOGY *topo, LWT_ISO_FACE *face, uint64_t numelems)
{
CBT2(topo, insertFaces, face, numelems);
}
static int
lwt_be_deleteFacesById(const LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems)
{
CBT2(topo, deleteFacesById, ids, numelems);
}
static int
lwt_be_deleteNodesById(const LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems)
{
CBT2(topo, deleteNodesById, ids, numelems);
}
LWT_ELEMID
lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo)
{
CBT0(topo, getNextEdgeId);
}
LWT_ISO_EDGE *
lwt_be_getEdgeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
{
CBT3(topo, getEdgeById, ids, numelems, fields);
}
static LWT_ISO_FACE *
lwt_be_getFaceById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
{
CBT3(topo, getFaceById, ids, numelems, fields);
}
static LWT_ISO_EDGE *
lwt_be_getEdgeByNode(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields)
{
CBT3(topo, getEdgeByNode, ids, numelems, fields);
}
static LWT_ISO_EDGE *
lwt_be_getEdgeByFace(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box)
{
CBT4(topo, getEdgeByFace, ids, numelems, fields, box);
}
static LWT_ISO_NODE *
lwt_be_getNodeByFace(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box)
{
CBT4(topo, getNodeByFace, ids, numelems, fields, box);
}
LWT_ISO_EDGE *
lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY *topo,
const LWPOINT *pt,
double dist,
uint64_t *numelems,
int fields,
int64_t limit)
{
CBT5(topo, getEdgeWithinDistance2D, pt, dist, numelems, fields, limit);
}
int
lwt_be_insertEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge, uint64_t numelems)
{
CBT2(topo, insertEdges, edge, numelems);
}
int
lwt_be_updateEdges(LWT_TOPOLOGY* topo,
const LWT_ISO_EDGE* sel_edge, int sel_fields,
const LWT_ISO_EDGE* upd_edge, int upd_fields,
const LWT_ISO_EDGE* exc_edge, int exc_fields
)
{
CBT6(topo, updateEdges, sel_edge, sel_fields,
upd_edge, upd_fields,
exc_edge, exc_fields);
}
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 uint64_t
lwt_be_updateFacesById(LWT_TOPOLOGY* topo,
const LWT_ISO_FACE* faces, uint64_t 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
)
{
CBT2(topo, deleteEdges, sel_edge, sel_fields);
}
int
lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2)
{
CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2);
}
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 int
lwt_be_checkTopoGeomRemEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id,
LWT_ELEMID face_left, LWT_ELEMID face_right)
{
CBT3(topo, checkTopoGeomRemEdge, edge_id, face_left, face_right);
}
static int
lwt_be_checkTopoGeomRemIsoEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id)
{
CBT1(topo, checkTopoGeomRemIsoEdge, edge_id);
}
static int
lwt_be_checkTopoGeomRemNode(LWT_TOPOLOGY* topo, LWT_ELEMID node_id,
LWT_ELEMID eid1, LWT_ELEMID eid2)
{
CBT3(topo, checkTopoGeomRemNode, node_id, eid1, eid2);
}
static int
lwt_be_checkTopoGeomRemIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID node_id)
{
CBT1(topo, checkTopoGeomRemIsoNode, node_id);
}
static int
lwt_be_updateTopoGeomFaceHeal(LWT_TOPOLOGY* topo,
LWT_ELEMID face1, LWT_ELEMID face2,
LWT_ELEMID newface)
{
CBT3(topo, updateTopoGeomFaceHeal, face1, face2, newface);
}
static int
lwt_be_updateTopoGeomEdgeHeal(LWT_TOPOLOGY* topo,
LWT_ELEMID edge1, LWT_ELEMID edge2,
LWT_ELEMID newedge)
{
CBT3(topo, updateTopoGeomEdgeHeal, edge1, edge2, newedge);
}
static LWT_ELEMID *
lwt_be_getRingEdges(LWT_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numedges, uint64_t limit)
{
CBT3(topo, getRingEdges, edge, numedges, limit);
}
int
lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt)
{
uint64_t exists = 0;
lwt_be_getNodeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
if (exists == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return 0;
}
return exists;
}
int
lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt)
{
uint64_t exists = 0;
lwt_be_getEdgeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
if (exists == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return 0;
}
return exists;
}
static LWT_ISO_EDGE *
lwt_be_getClosestEdge(const LWT_TOPOLOGY *topo, const LWPOINT *pt, uint64_t *numelems, int fields)
{
CBT3(topo, getClosestEdge, pt, numelems, fields);
}
static GBOX *
lwt_be_computeFaceMBR(const LWT_TOPOLOGY *topo, LWT_ELEMID face)
{
CBT1(topo, computeFaceMBR, face);
}
/************************************************************************
*
* Utility functions
*
************************************************************************/
static LWGEOM *
_lwt_toposnap(LWGEOM *src, LWGEOM *tgt, double tol)
{
LWGEOM *tmp = src;
LWGEOM *tmp2;
int changed;
int iterations = 0;
int maxiterations = lwgeom_count_vertices(tgt);
/* GEOS snapping can be unstable */
/* See https://trac.osgeo.org/geos/ticket/760 */
do {
tmp2 = lwgeom_snap(tmp, tgt, tol);
++iterations;
changed = ( lwgeom_count_vertices(tmp2) != lwgeom_count_vertices(tmp) );
LWDEBUGF(2, "After iteration %d, geometry changed ? %d (%d vs %d vertices)", iterations, changed, lwgeom_count_vertices(tmp2), lwgeom_count_vertices(tmp));
if ( tmp != src ) lwgeom_free(tmp);
tmp = tmp2;
} while ( changed && iterations <= maxiterations );
LWDEBUGF(1, "It took %d/%d iterations to properly snap",
iterations, maxiterations);
return tmp;
}
static void
_lwt_release_faces(LWT_ISO_FACE *faces, int num_faces)
{
int i;
for ( i=0; i<num_faces; ++i ) {
if ( faces[i].mbr ) lwfree(faces[i].mbr);
}
lwfree(faces);
}
static void
_lwt_release_edges(LWT_ISO_EDGE *edges, int num_edges)
{
int i;
for ( i=0; i<num_edges; ++i ) {
if ( edges[i].geom ) lwline_free(edges[i].geom);
}
lwfree(edges);
}
static void
_lwt_release_nodes(LWT_ISO_NODE *nodes, int num_nodes)
{
int i;
for ( i=0; i<num_nodes; ++i ) {
if ( nodes[i].geom ) lwpoint_free(nodes[i].geom);
}
lwfree(nodes);
}
/************************************************************************
*
* API implementation
*
************************************************************************/
LWT_TOPOLOGY *
lwt_LoadTopology( LWT_BE_IFACE *iface, const char *name )
{
LWT_BE_TOPOLOGY* be_topo;
LWT_TOPOLOGY* topo;
be_topo = lwt_be_loadTopologyByName(iface, name);
if ( ! be_topo ) {
//lwerror("Could not load topology from backend: %s",
lwerror("%s", lwt_be_lastErrorMessage(iface));
return NULL;
}
topo = lwalloc(sizeof(LWT_TOPOLOGY));
topo->be_iface = iface;
topo->be_topo = be_topo;
topo->srid = lwt_be_topoGetSRID(topo);
topo->hasZ = lwt_be_topoHasZ(topo);
topo->precision = lwt_be_topoGetPrecision(topo);
return topo;
}
void
lwt_FreeTopology( LWT_TOPOLOGY* topo )
{
if ( ! lwt_be_freeTopology(topo) ) {
lwnotice("Could not release backend topology memory: %s",
lwt_be_lastErrorMessage(topo->be_iface));
}
lwfree(topo);
}
/**
* @param checkFace if non zero will check the given face
* for really containing the point or determine the
* face when given face is -1. Use 0 to simply use
* the given face value, no matter what (effectively
* allowing adding a non-isolated point when used
* with face=-1).
*/
static LWT_ELEMID
_lwt_AddIsoNode( LWT_TOPOLOGY* topo, LWT_ELEMID face,
LWPOINT* pt, int skipISOChecks, int checkFace )
{
LWT_ELEMID foundInFace = -1;
if ( lwpoint_is_empty(pt) )
{
lwerror("Cannot add empty point as isolated node");
return -1;
}
if ( ! skipISOChecks )
{
if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
{
lwerror("SQL/MM Spatial exception - coincident node");
return -1;
}
if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) ) /*x*/
{
lwerror("SQL/MM Spatial exception - edge crosses node.");
return -1;
}
}
if ( checkFace && ( face == -1 || ! skipISOChecks ) )
{
foundInFace = lwt_GetFaceContainingPoint(topo, pt); /*x*/
if ( foundInFace == -1 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( foundInFace == -1 ) foundInFace = 0;
}
if ( face == -1 ) {
face = foundInFace;
}
else if ( ! skipISOChecks && foundInFace != face ) {
#if 0
lwerror("SQL/MM Spatial exception - within face %d (not %d)",
foundInFace, face);
#else
lwerror("SQL/MM Spatial exception - not within face");
#endif
return -1;
}
LWT_ISO_NODE node;
node.node_id = -1;
node.containing_face = face;
node.geom = pt;
if ( ! lwt_be_insertNodes(topo, &node, 1) )
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
return node.node_id;
}
LWT_ELEMID
lwt_AddIsoNode( LWT_TOPOLOGY* topo, LWT_ELEMID face,
LWPOINT* pt, int skipISOChecks )
{
return _lwt_AddIsoNode( topo, face, pt, skipISOChecks, 1 );
}
/* Check that an edge does not cross an existing node or edge
*
* @param myself the id of an edge to skip, if any
* (for ChangeEdgeGeom). Can use 0 for none.
*
* Return -1 on cross or error, 0 if everything is fine.
* Note that before returning -1, lwerror is invoked...
*/
static int
_lwt_CheckEdgeCrossing( LWT_TOPOLOGY* topo,
LWT_ELEMID start_node, LWT_ELEMID end_node,
const LWLINE *geom, LWT_ELEMID myself )
{
uint64_t i, num_nodes, num_edges;
LWT_ISO_EDGE *edges;
LWT_ISO_NODE *nodes;
const GBOX *edgebox;
GEOSGeometry *edgegg;
initGEOS(lwnotice, lwgeom_geos_error);
edgegg = LWGEOM2GEOS(lwline_as_lwgeom(geom), 0);
if (!edgegg)
{
lwerror("Could not convert edge geometry to GEOS: %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 );
LWDEBUGF(1, "lwt_be_getNodeWithinBox2D returned %d nodes", num_nodes);
if (num_nodes == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for ( i=0; i<num_nodes; ++i )
{
const POINT2D *pt;
LWT_ISO_NODE* node = &(nodes[i]);
if ( node->node_id == start_node ) continue;
if ( node->node_id == end_node ) continue;
/* check if the edge contains this node (not on boundary) */
/* ST_RelateMatch(rec.relate, 'T********') */
pt = getPoint2d_cp(node->geom->point, 0);
int contains = ptarray_contains_point_partial(geom->points, pt, LW_FALSE, NULL) == LW_BOUNDARY;
if ( contains )
{
GEOSGeom_destroy(edgegg);
_lwt_release_nodes(nodes, num_nodes);
lwerror("SQL/MM Spatial exception - geometry crosses a node");
return -1;
}
}
if ( nodes ) _lwt_release_nodes(nodes, num_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 == UINT64_MAX)
{
GEOSGeom_destroy(edgegg);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for ( i=0; i<num_edges; ++i )
{
LWT_ISO_EDGE* edge = &(edges[i]);
LWT_ELEMID edge_id = edge->edge_id;
GEOSGeometry *eegg;
char *relate;
int match;
if ( edge_id == myself ) continue;
if ( ! edge->geom ) {
_lwt_release_edges(edges, num_edges);
lwerror("Edge %d has NULL geometry!", edge_id);
return -1;
}
eegg = LWGEOM2GEOS( lwline_as_lwgeom(edge->geom), 0 );
if ( ! eegg ) {
GEOSGeom_destroy(edgegg);
_lwt_release_edges(edges, num_edges);
lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
return -1;
}
LWDEBUGF(2, "Edge %d converted to GEOS", edge_id);
/* check if the edge crosses our edge (not boundary-boundary) */
relate = GEOSRelateBoundaryNodeRule(eegg, edgegg, 2);
if ( ! relate ) {
GEOSGeom_destroy(eegg);
GEOSGeom_destroy(edgegg);
_lwt_release_edges(edges, num_edges);
lwerror("GEOSRelateBoundaryNodeRule error: %s", lwgeom_geos_errmsg);
return -1;
}
LWDEBUGF(2, "Edge %d relate pattern is %s", edge_id, relate);
match = GEOSRelatePatternMatch(relate, "F********");
if ( match ) {
/* error or no interior intersection */
GEOSGeom_destroy(eegg);
GEOSFree(relate);
if ( match == 2 ) {
_lwt_release_edges(edges, num_edges);
GEOSGeom_destroy(edgegg);
lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg);
return -1;
}
else continue; /* no interior intersection */
}
match = GEOSRelatePatternMatch(relate, "1FFF*FFF2");
if ( match ) {
_lwt_release_edges(edges, num_edges);
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 %" LWTFMT_ELEMID,
edge_id);
}
return -1;
}
match = GEOSRelatePatternMatch(relate, "1********");
if ( match ) {
_lwt_release_edges(edges, num_edges);
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 %"
LWTFMT_ELEMID, edge_id);
}
return -1;
}
match = GEOSRelatePatternMatch(relate, "T********");
if ( match ) {
_lwt_release_edges(edges, num_edges);
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 %"
LWTFMT_ELEMID, edge_id);
}
return -1;
}
LWDEBUGF(2, "Edge %d analisys completed, it does no harm", edge_id);
GEOSFree(relate);
GEOSGeom_destroy(eegg);
}
if ( edges ) _lwt_release_edges(edges, num_edges);
/* would be NULL if num_edges was 0 */
GEOSGeom_destroy(edgegg);
return 0;
}
LWT_ELEMID
lwt_AddIsoEdge( LWT_TOPOLOGY* topo, LWT_ELEMID startNode,
LWT_ELEMID endNode, const LWLINE* geom )
{
uint64_t num_nodes;
uint64_t i;
LWT_ISO_EDGE newedge;
LWT_ISO_NODE *endpoints;
LWT_ELEMID containing_face = -1;
LWT_ELEMID node_ids[2];
LWT_ISO_NODE updated_nodes[2];
int skipISOChecks = 0;
POINT2D p1, p2;
/* NOT IN THE SPECS:
* A closed edge is never isolated (as it forms a face)
*/
if (startNode == endNode)
{
lwerror("Closed edges would not be isolated, try lwt_AddEdgeNewFaces");
return -1;
}
if ( ! skipISOChecks )
{
/* Acurve must be simple */
if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) )
{
lwerror("SQL/MM Spatial exception - curve not simple");
return -1;
}
}
/*
* Check for:
* existence of nodes
* nodes faces match
* Extract:
* nodes face id
* nodes geoms
*/
num_nodes = 2;
node_ids[0] = startNode;
node_ids[1] = endNode;
endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes,
LWT_COL_NODE_ALL );
if (num_nodes == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if ( num_nodes < 2 )
{
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - non-existent node");
return -1;
}
for ( i=0; i<num_nodes; ++i )
{
const LWT_ISO_NODE *n = &(endpoints[i]);
if ( n->containing_face == -1 )
{
_lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - not isolated node");
return -1;
}
if ( containing_face == -1 ) containing_face = n->containing_face;
else if ( containing_face != n->containing_face )
{
_lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - nodes in different faces");
return -1;
}
if ( ! skipISOChecks )
{
if ( n->node_id == startNode )
{
/* l) Check that start point of acurve match start node geoms. */
getPoint2d_p(geom->points, 0, &p1);
getPoint2d_p(n->geom->point, 0, &p2);
if ( ! p2d_same(&p1, &p2) )
{
_lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - "
"start node not geometry start point.");
return -1;
}
}
else
{
/* m) Check that end point of acurve match end node geoms. */
getPoint2d_p(geom->points, geom->points->npoints-1, &p1);
getPoint2d_p(n->geom->point, 0, &p2);
if ( ! p2d_same(&p1, &p2) )
{
_lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - "
"end node not geometry end point.");
return -1;
}
}
}
}
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
if ( ! skipISOChecks )
{
if ( _lwt_CheckEdgeCrossing( topo, startNode, endNode, geom, 0 ) )
{
/* would have called lwerror already, leaking :( */
return -1;
}
}
/*
* 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;
}
/* TODO: this should likely be an exception instead ! */
if ( containing_face == -1 ) containing_face = 0;
newedge.start_node = startNode;
newedge.end_node = endNode;
newedge.face_left = newedge.face_right = containing_face;
newedge.next_left = -newedge.edge_id;
newedge.next_right = newedge.edge_id;
newedge.geom = (LWLINE *)geom; /* const cast.. */
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;
}
/*
* Update Node containing_face values
*
* the nodes anode and anothernode are no more isolated
* because now there is an edge connecting them
*/
updated_nodes[0].node_id = startNode;
updated_nodes[0].containing_face = -1;
updated_nodes[1].node_id = endNode;
updated_nodes[1].containing_face = -1;
ret = lwt_be_updateNodesById(topo, updated_nodes, 2,
LWT_COL_NODE_CONTAINING_FACE);
if ( ret == -1 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
return newedge.edge_id;
}
static LWCOLLECTION *
_lwt_EdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipISOChecks, LWT_ISO_EDGE** oldedge )
{
LWGEOM *split;
LWCOLLECTION *split_col;
uint64_t i;
/* Get edge */
i = 1;
LWDEBUG(1, "calling lwt_be_getEdgeById");
*oldedge = lwt_be_getEdgeById(topo, &edge, &i, LWT_COL_EDGE_ALL);
LWDEBUGF(1, "lwt_be_getEdgeById returned %p", *oldedge);
if ( ! *oldedge )
{
LWDEBUGF(1, "lwt_be_getEdgeById returned NULL and set i=%d", i);
if (i == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
else if ( i == 0 )
{
lwerror("SQL/MM Spatial exception - non-existent edge");
return NULL;
}
else
{
lwerror("Backend coding error: getEdgeById callback returned NULL "
"but numelements output parameter has value %d "
"(expected 0 or 1)", i);
return NULL;
}
}
/*
* - check if a coincident node already exists
*/
if ( ! skipISOChecks )
{
LWDEBUG(1, "calling lwt_be_ExistsCoincidentNode");
if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
{
LWDEBUG(1, "lwt_be_ExistsCoincidentNode returned");
_lwt_release_edges(*oldedge, 1);
lwerror("SQL/MM Spatial exception - coincident node");
return NULL;
}
LWDEBUG(1, "lwt_be_ExistsCoincidentNode returned");
}
/* Split edge */
split = lwgeom_split((LWGEOM*)(*oldedge)->geom, (LWGEOM*)pt);
if ( ! split )
{
_lwt_release_edges(*oldedge, 1);
lwerror("could not split edge by point ?");
return NULL;
}
split_col = lwgeom_as_lwcollection(split);
if ( ! split_col ) {
_lwt_release_edges(*oldedge, 1);
lwgeom_free(split);
lwerror("lwgeom_as_lwcollection returned NULL");
return NULL;
}
if (split_col->ngeoms < 2) {
_lwt_release_edges(*oldedge, 1);
lwgeom_free(split);
lwerror("SQL/MM Spatial exception - point not on edge");
return NULL;
}
#if 0
{
size_t sz;
char *wkt = lwgeom_to_wkt((LWGEOM*)split_col, WKT_EXTENDED, 2, &sz);
LWDEBUGF(1, "returning split col: %s", wkt);
lwfree(wkt);
}
#endif
return split_col;
}
LWT_ELEMID
lwt_ModEdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
LWPOINT* pt, int skipISOChecks )
{
LWT_ISO_NODE node;
LWT_ISO_EDGE* oldedge = NULL;
LWCOLLECTION *split_col;
const LWGEOM *oldedge_geom;
const LWGEOM *newedge_geom;
LWT_ISO_EDGE newedge1;
LWT_ISO_EDGE seledge, updedge, excedge;
int ret;
split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
if ( ! split_col ) return -1; /* should have raised an exception */
oldedge_geom = split_col->geoms[0];
newedge_geom = split_col->geoms[1];
/* Make sure the SRID is set on the subgeom */
((LWGEOM*)oldedge_geom)->srid = split_col->srid;
((LWGEOM*)newedge_geom)->srid = split_col->srid;
/* Add new node, getting new id back */
node.node_id = -1;
node.containing_face = -1; /* means not-isolated */
node.geom = pt;
if ( ! lwt_be_insertNodes(topo, &node, 1) )
{
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if (node.node_id == -1) {
/* should have been set by backend */
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend coding error: "
"insertNodes callback did not return node_id");
return -1;
}
/* Insert the new edge */
newedge1.edge_id = lwt_be_getNextEdgeId(topo);
if ( newedge1.edge_id == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
newedge1.start_node = node.node_id;
newedge1.end_node = oldedge->end_node;
newedge1.face_left = oldedge->face_left;
newedge1.face_right = oldedge->face_right;
newedge1.next_left = oldedge->next_left == -oldedge->edge_id ?
-newedge1.edge_id : oldedge->next_left;
newedge1.next_right = -oldedge->edge_id;
newedge1.geom = lwgeom_as_lwline(newedge_geom);
/* lwgeom_split of a line should only return lines ... */
if ( ! newedge1.geom ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("first geometry in lwgeom_split output is not a line");
return -1;
}
ret = lwt_be_insertEdges(topo, &newedge1, 1);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
} else if ( ret == 0 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Insertion of split edge failed (no reason)");
return -1;
}
/* Update the old edge */
updedge.geom = lwgeom_as_lwline(oldedge_geom);
/* lwgeom_split of a line should only return lines ... */
if ( ! updedge.geom ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("second geometry in lwgeom_split output is not a line");
return -1;
}
updedge.next_left = newedge1.edge_id;
updedge.end_node = node.node_id;
ret = lwt_be_updateEdges(topo,
oldedge, LWT_COL_EDGE_EDGE_ID,
&updedge, LWT_COL_EDGE_GEOM|LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
NULL, 0);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
} else if ( ret == 0 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Edge being split (%d) disappeared during operations?", oldedge->edge_id);
return -1;
} else if ( ret > 1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("More than a single edge found with id %d !", oldedge->edge_id);
return -1;
}
/* Update all next edge references to match new layout (ST_ModEdgeSplit) */
updedge.next_right = -newedge1.edge_id;
excedge.edge_id = newedge1.edge_id;
seledge.next_right = -oldedge->edge_id;
seledge.start_node = oldedge->end_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE,
&updedge, LWT_COL_EDGE_NEXT_RIGHT,
&excedge, LWT_COL_EDGE_EDGE_ID);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
updedge.next_left = -newedge1.edge_id;
excedge.edge_id = newedge1.edge_id;
seledge.next_left = -oldedge->edge_id;
seledge.end_node = oldedge->end_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
&updedge, LWT_COL_EDGE_NEXT_LEFT,
&excedge, LWT_COL_EDGE_EDGE_ID);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Update TopoGeometries composition */
ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedge1.edge_id, -1);
if ( ! ret ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
/* return new node id */
return node.node_id;
}
LWT_ELEMID
lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge,
LWPOINT* pt, int skipISOChecks )
{
LWT_ISO_NODE node;
LWT_ISO_EDGE* oldedge = NULL;
LWCOLLECTION *split_col;
const LWGEOM *oldedge_geom;
const LWGEOM *newedge_geom;
LWT_ISO_EDGE newedges[2];
LWT_ISO_EDGE seledge, updedge;
int ret;
split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
if ( ! split_col ) return -1; /* should have raised an exception */
oldedge_geom = split_col->geoms[0];
newedge_geom = split_col->geoms[1];
/* Make sure the SRID is set on the subgeom */
((LWGEOM*)oldedge_geom)->srid = split_col->srid;
((LWGEOM*)newedge_geom)->srid = split_col->srid;
/* Add new node, getting new id back */
node.node_id = -1;
node.containing_face = -1; /* means not-isolated */
node.geom = pt;
if ( ! lwt_be_insertNodes(topo, &node, 1) )
{
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if (node.node_id == -1) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
/* should have been set by backend */
lwerror("Backend coding error: "
"insertNodes callback did not return node_id");
return -1;
}
/* Delete the old edge */
seledge.edge_id = edge;
ret = lwt_be_deleteEdges(topo, &seledge, LWT_COL_EDGE_EDGE_ID);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Get new edges identifiers */
newedges[0].edge_id = lwt_be_getNextEdgeId(topo);
if ( newedges[0].edge_id == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
newedges[1].edge_id = lwt_be_getNextEdgeId(topo);
if ( newedges[1].edge_id == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Define the first new edge (to new node) */
newedges[0].start_node = oldedge->start_node;
newedges[0].end_node = node.node_id;
newedges[0].face_left = oldedge->face_left;
newedges[0].face_right = oldedge->face_right;
newedges[0].next_left = newedges[1].edge_id;
if ( oldedge->next_right == edge )
newedges[0].next_right = newedges[0].edge_id;
else if ( oldedge->next_right == -edge )
newedges[0].next_right = -newedges[1].edge_id;
else
newedges[0].next_right = oldedge->next_right;
newedges[0].geom = lwgeom_as_lwline(oldedge_geom);
/* lwgeom_split of a line should only return lines ... */
if ( ! newedges[0].geom ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("first geometry in lwgeom_split output is not a line");
return -1;
}
/* Define the second new edge (from new node) */
newedges[1].start_node = node.node_id;
newedges[1].end_node = oldedge->end_node;
newedges[1].face_left = oldedge->face_left;
newedges[1].face_right = oldedge->face_right;
newedges[1].next_right = -newedges[0].edge_id;
if ( oldedge->next_left == -edge )
newedges[1].next_left = -newedges[1].edge_id;
else if ( oldedge->next_left == edge )
newedges[1].next_left = newedges[0].edge_id;
else
newedges[1].next_left = oldedge->next_left;
newedges[1].geom = lwgeom_as_lwline(newedge_geom);
/* lwgeom_split of a line should only return lines ... */
if ( ! newedges[1].geom ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("second geometry in lwgeom_split output is not a line");
return -1;
}
/* Insert both new edges */
ret = lwt_be_insertEdges(topo, newedges, 2);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
} else if ( ret == 0 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Insertion of split edge failed (no reason)");
return -1;
}
/* Update all next edge references pointing to old edge id */
updedge.next_right = newedges[1].edge_id;
seledge.next_right = edge;
seledge.start_node = oldedge->start_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE,
&updedge, LWT_COL_EDGE_NEXT_RIGHT,
NULL, 0);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
updedge.next_right = -newedges[0].edge_id;
seledge.next_right = -edge;
seledge.start_node = oldedge->end_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE,
&updedge, LWT_COL_EDGE_NEXT_RIGHT,
NULL, 0);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
updedge.next_left = newedges[0].edge_id;
seledge.next_left = edge;
seledge.end_node = oldedge->start_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
&updedge, LWT_COL_EDGE_NEXT_LEFT,
NULL, 0);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
updedge.next_left = -newedges[1].edge_id;
seledge.next_left = -edge;
seledge.end_node = oldedge->end_node;
ret = lwt_be_updateEdges(topo,
&seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE,
&updedge, LWT_COL_EDGE_NEXT_LEFT,
NULL, 0);
if ( ret == -1 ) {
_lwt_release_edges(oldedge, 1);
lwcollection_release(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Update TopoGeometries composition */
ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedges[0].edge_id, newedges[1].edge_id);
if ( ! ret ) {
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
_lwt_release_edges(oldedge, 1);
lwcollection_free(split_col);
/* return new node id */
return node.node_id;
}
/* Data structure used by AddEdgeX functions */
typedef struct edgeend_t {
/* Signed identifier of next clockwise edge (+outgoing,-incoming) */
LWT_ELEMID nextCW;
/* Identifier of face between myaz and next CW edge */
LWT_ELEMID cwFace;
/* Signed identifier of next counterclockwise edge (+outgoing,-incoming) */
LWT_ELEMID nextCCW;
/* Identifier of face between myaz and next CCW edge */
LWT_ELEMID ccwFace;
int was_isolated;
double myaz; /* azimuth of edgeend geometry */
} edgeend;
/*
* Get first distinct vertex from endpoint
* @param pa the pointarray to seek points in
* @param ref the point we want to search a distinct one
* @param from vertex index to start from (will really start from "from"+dir)
* @param dir 1 to go forward
* -1 to go backward
* @return 0 if edge is collapsed (no distinct points)
*/
static int
_lwt_FirstDistinctVertex2D(const POINTARRAY* pa, POINT2D *ref, int from, int dir, POINT2D *op)
{
int i, toofar, inc;
POINT2D fp;
if ( dir > 0 )
{
toofar = pa->npoints;
inc = 1;
}
else
{
toofar = -1;
inc = -1;
}
LWDEBUGF(1, "first point is index %d", from);
fp = *ref; /* getPoint2d_p(pa, from, &fp); */
for ( i = from+inc; i != toofar; i += inc )
{
LWDEBUGF(1, "testing point %d", i);
getPoint2d_p(pa, i, op); /* pick next point */
if ( p2d_same(op, &fp) ) continue; /* equal to startpoint */
/* this is a good one, neither same of start nor of end point */
return 1; /* found */
}
/* no distinct vertices found */
return 0;
}
/*
* Return non-zero on failure (lwerror is invoked in that case)
* Possible failures:
* -1 no two distinct vertices exist
* -2 azimuth computation failed for first edge end
*/
static int
_lwt_InitEdgeEndByLine(edgeend *fee, edgeend *lee, LWLINE *edge,
POINT2D *fp, POINT2D *lp)
{
POINTARRAY *pa = edge->points;
POINT2D pt;
fee->nextCW = fee->nextCCW =
lee->nextCW = lee->nextCCW = 0;
fee->cwFace = fee->ccwFace =
lee->cwFace = lee->ccwFace = -1;
/* Compute azimuth of first edge end */
LWDEBUG(1, "computing azimuth of first edge end");
if ( ! _lwt_FirstDistinctVertex2D(pa, fp, 0, 1, &pt) )
{
lwerror("Invalid edge (no two distinct vertices exist)");
return -1;
}
if ( ! azimuth_pt_pt(fp, &pt, &(fee->myaz)) ) {
lwerror("error computing azimuth of first edgeend [%.15g %.15g,%.15g %.15g]",
fp->x, fp->y, pt.x, pt.y);
return -2;
}
LWDEBUGF(1, "azimuth of first edge end [%.15g %.15g,%.15g %.15g] is %g",
fp->x, fp->y, pt.x, pt.y, fee->myaz);
/* Compute azimuth of second edge end */
LWDEBUG(1, "computing azimuth of second edge end");
if ( ! _lwt_FirstDistinctVertex2D(pa, lp, pa->npoints-1, -1, &pt) )
{
lwerror("Invalid edge (no two distinct vertices exist)");
return -1;
}
if ( ! azimuth_pt_pt(lp, &pt, &(lee->myaz)) ) {
lwerror("error computing azimuth of last edgeend [%.15g %.15g,%.15g %.15g]",
lp->x, lp->y, pt.x, pt.y);
return -2;
}
LWDEBUGF(1, "azimuth of last edge end [%.15g %.15g,%.15g %.15g] is %g",
lp->x, lp->y, pt.x, pt.y, lee->myaz);
return 0;
}
/*
* Find the first edges encountered going clockwise and counterclockwise
* around a node, starting from the given azimuth, and take
* note of the face on the both sides.
*
* @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).
* @param myedge_id identifier of the edge id that data->myaz belongs to
* @return number of incident edges found
*
*/
static int
_lwt_FindAdjacentEdges( LWT_TOPOLOGY* topo, LWT_ELEMID node, edgeend *data,
edgeend *other, int myedge_id )
{
LWT_ISO_EDGE *edges;
uint64_t numedges = 1;
uint64_t 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 %" LWTFMT_ELEMID
" and adjacent to azimuth %g", node, data->myaz);
/* Get incident edges */
edges = lwt_be_getEdgeByNode( topo, &node, &numedges, LWT_COL_EDGE_ALL );
if (numedges == UINT64_MAX)
{
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;
LWGEOM *g;
LWGEOM *cleangeom;
POINT2D p1, p2;
POINTARRAY *pa;
edge = &(edges[i]);
if ( edge->edge_id == myedge_id ) continue;
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 */
cleangeom = lwgeom_remove_repeated_points( g, 0 );
pa = lwgeom_as_lwline(cleangeom)->points;
if ( pa->npoints < 2 ) {{
LWT_ELEMID id = edge->edge_id;
_lwt_release_edges(edges, numedges);
lwgeom_free(cleangeom);
lwerror("corrupted topology: edge %" LWTFMT_ELEMID
" does not have two distinct points", id);
return -1;
}}
if ( edge->start_node == node ) {
getPoint2d_p(pa, 0, &p1);
if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, 0, 1, &p2) )
{
lwerror("Edge %d has no distinct vertices: [%.15g %.15g,%.15g %.15g]: ",
edge->edge_id, p1.x, p1.y, p2.x, p2.y);
return -1;
}
LWDEBUGF(1, "edge %" LWTFMT_ELEMID
" starts on node %" LWTFMT_ELEMID
", 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) ) {{
LWT_ELEMID id = edge->edge_id;
_lwt_release_edges(edges, numedges);
lwgeom_free(cleangeom);
lwerror("error computing azimuth of edge %d first edgeend [%.15g %.15g,%.15g %.15g]",
id, p1.x, p1.y, p2.x, p2.y);
return -1;
}}
azdif = az - data->myaz;
LWDEBUGF(1, "azimuth of edge %" LWTFMT_ELEMID
": %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 %" LWTFMT_ELEMID
", outgoing, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (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 %" LWTFMT_ELEMID
", outgoing, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (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 %" LWTFMT_ELEMID
", outgoing, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (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);
if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, pa->npoints-1, -1, &p2) )
{
lwerror("Edge %d has no distinct vertices: [%.15g %.15g,%.15g %.15g]: ",
edge->edge_id, p1.x, p1.y, p2.x, p2.y);
return -1;
}
LWDEBUGF(1, "edge %" LWTFMT_ELEMID " ends on node %" LWTFMT_ELEMID
", 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) ) {{
LWT_ELEMID id = edge->edge_id;
_lwt_release_edges(edges, numedges);
lwgeom_free(cleangeom);
lwerror("error computing azimuth of edge %d last edgeend [%.15g %.15g,%.15g %.15g]",
id, p1.x, p1.y, p2.x, p2.y);
return -1;
}}
azdif = az - data->myaz;
LWDEBUGF(1, "azimuth of edge %" LWTFMT_ELEMID
": %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 %" LWTFMT_ELEMID
", incoming, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (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 %" LWTFMT_ELEMID
", incoming, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (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 %" LWTFMT_ELEMID
", outgoing, from start point, "
"with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID
" (previous had maxaz=%g, face_left is new ccwFace)",
edge->edge_id, edge->face_left,
edge->face_right, maxaz);
maxaz = azdif;
}
}
}
lwgeom_free(cleangeom);
}
if ( numedges ) _lwt_release_edges(edges, numedges);
LWDEBUGF(1, "edges adjacent to azimuth %g"
" (incident to node %" LWTFMT_ELEMID ")"
": CW:%" LWTFMT_ELEMID "(%g) CCW:%" LWTFMT_ELEMID "(%g)",
data->myaz, node, data->nextCW, minaz,
data->nextCCW, maxaz);
if ( myedge_id < 1 && numedges && data->cwFace != data->ccwFace )
{
if ( data->cwFace != -1 && data->ccwFace != -1 ) {
lwerror("Corrupted topology: adjacent edges %" LWTFMT_ELEMID " and %" LWTFMT_ELEMID
" bind different face (%" LWTFMT_ELEMID " and %" LWTFMT_ELEMID ")",
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)
{
uint32_t i;
POINT2D fp, lp, tp;
POINTARRAY *pa = edge->points;
if ( pa->npoints < 2 ) return 0; /* empty or structurally collapsed */
getPoint2d_p(pa, 0, &fp); /* save first point */
getPoint2d_p(pa, pa->npoints-1, &lp); /* save last point */
for (i=1; i<pa->npoints-1; ++i)
{
getPoint2d_p(pa, i, &tp); /* pick next point */
if ( p2d_same(&tp, &fp) ) continue; /* equal to startpoint */
if ( p2d_same(&tp, &lp) ) continue; /* equal to endpoint */
/* this is a good one, neither same of start nor of end point */
*ip = tp;
return 1; /* found */
}
/* no distinct vertex found */
/* interpolate if start point != end point */
if ( p2d_same(&fp, &lp) ) return 0; /* no distinct points in edge */
ip->x = fp.x + ( (lp.x - fp.x) * 0.5 );
ip->y = fp.y + ( (lp.y - fp.y) * 0.5 );
return 1;
}
static LWPOLY *
_lwt_MakeRingShell(LWT_TOPOLOGY *topo, LWT_ELEMID *signed_edge_ids, uint64_t num_signed_edge_ids)
{
LWT_ELEMID *edge_ids;
uint64_t numedges, i, j;
LWT_ISO_EDGE *ring_edges;
/* Construct a polygon using edges of the ring */
numedges = 0;
edge_ids = lwalloc(sizeof(LWT_ELEMID)*num_signed_edge_ids);
for (i=0; i<num_signed_edge_ids; ++i) {
int absid = llabs(signed_edge_ids[i]);
int found = 0;
/* Do not add the same edge twice */
for (j=0; j<numedges; ++j) {
if ( edge_ids[j] == absid ) {
found = 1;
break;
}
}
if ( ! found ) edge_ids[numedges++] = absid;
}
i = numedges;
ring_edges = lwt_be_getEdgeById(topo, edge_ids, &i,
LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM);
lwfree( edge_ids );
if (i == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
else if ( i != numedges )
{
lwfree( signed_edge_ids );
_lwt_release_edges(ring_edges, i);
lwerror("Unexpected error: %d edges found when expecting %d", i, numedges);
return NULL;
}
/* Should now build a polygon with those edges, in the order
* given by GetRingEdges.
*/
POINTARRAY *pa = NULL;
for ( i=0; i<num_signed_edge_ids; ++i )
{
LWT_ELEMID eid = signed_edge_ids[i];
LWDEBUGF(2, "Edge %d in ring is edge %" LWTFMT_ELEMID, i, eid);
LWT_ISO_EDGE *edge = NULL;
POINTARRAY *epa;
for ( j=0; j<numedges; ++j )
{
if ( ring_edges[j].edge_id == llabs(eid) )
{
edge = &(ring_edges[j]);
break;
}
}
if ( edge == NULL )
{
_lwt_release_edges(ring_edges, numedges);
lwerror("missing edge that was found in ring edges loop");
return NULL;
}
if ( pa == NULL )
{
pa = ptarray_clone_deep(edge->geom->points);
if ( eid < 0 ) ptarray_reverse_in_place(pa);
}
else
{
if ( eid < 0 )
{
epa = ptarray_clone_deep(edge->geom->points);
ptarray_reverse_in_place(epa);
ptarray_append_ptarray(pa, epa, 0);
ptarray_free(epa);
}
else
{
/* avoid a clone here */
ptarray_append_ptarray(pa, edge->geom->points, 0);
}
}
}
_lwt_release_edges(ring_edges, numedges);
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);
return shell;
}
/*
* 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 )
{
uint64_t numfaceedges, i, j;
int newface_outside;
uint64_t num_signed_edge_ids;
LWT_ELEMID *signed_edge_ids;
LWT_ISO_EDGE *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 %" LWTFMT_ELEMID "): %s",
sedge,
lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
LWDEBUGF(1, "getRingEdges returned %d edges", num_signed_edge_ids);
/* You can't get to the other side of an edge forming a ring */
for (i=0; i<num_signed_edge_ids; ++i) {
if ( signed_edge_ids[i] == -sedge ) {
/* No split here */
LWDEBUG(1, "not a ring");
lwfree( signed_edge_ids );
return 0;
}
}
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " split face %" LWTFMT_ELEMID " (mbr_only:%d)",
sedge, face, mbr_only);
/* Construct a polygon using edges of the ring */
LWPOLY *shell = _lwt_MakeRingShell(topo, signed_edge_ids,
num_signed_edge_ids);
if ( ! shell ) {
lwfree( signed_edge_ids );
/* ring_edges should be NULL */
lwerror("Could not create ring shell: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
const POINTARRAY *pa = shell->rings[0];
if ( ! ptarray_is_closed_2d(pa) )
{
lwpoly_free(shell);
lwfree( signed_edge_ids );
lwerror("Corrupted topology: ring of edge %" LWTFMT_ELEMID
" is geometrically not-closed", sedge);
return -2;
}
int isccw = ptarray_isccw(pa);
LWDEBUGF(1, "Ring of edge %" LWTFMT_ELEMID " is %sclockwise",
sedge, isccw ? "counter" : "");
const GBOX* shellbox = lwgeom_get_bbox(lwpoly_as_lwgeom(shell));
if ( face == 0 )
{
/* Edge split the universe face */
if ( ! isccw )
{
lwpoly_free(shell);
lwfree( signed_edge_ids );
/* 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 )
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox above */
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( ret != 1 )
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox above */
lwerror("Unexpected error: %d faces found when expecting 1", ret);
return -2;
}
}}
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox above */
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 */
uint64_t nfaces = 1;
oldface = lwt_be_getFaceById(topo, &face, &nfaces, LWT_COL_FACE_ALL);
if (nfaces == UINT64_MAX)
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox */
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( nfaces != 1 )
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox */
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 )
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox */
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( ret != 1 )
{
lwfree( signed_edge_ids );
lwpoly_free(shell); /* NOTE: owns shellbox */
lwerror("Unexpected error: %d faces inserted when expecting 1", ret);
return -2;
}
if ( oldface ) {
newface.mbr = NULL; /* it is a reference to oldface mbr... */
_lwt_release_faces(oldface, 1);
}
/* 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, newface.mbr );
if (numfaceedges == UINT64_MAX)
{
lwfree(signed_edge_ids);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
LWDEBUGF(1, "_lwt_AddFaceSplit: lwt_be_getEdgeByFace returned %d edges", numfaceedges);
if ( numfaceedges )
{
forward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges);
forward_edges_count = 0;
backward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges);
backward_edges_count = 0;
/* (2) loop over the results and: */
for ( i=0; i<numfaceedges; ++i )
{
LWT_ISO_EDGE *e = &(edges[i]);
int found = 0;
int contains;
POINT2D ep;
/* (2.1) skip edges whose ID is in the list of boundary edges ? */
for ( j=0; j<num_signed_edge_ids; ++j )
{
int seid = signed_edge_ids[j];
if ( seid == e->edge_id )
{
/* IDEA: remove entry from signed_edge_ids ? */
LWDEBUGF(1, "Edge %d is a forward edge of the new ring", e->edge_id);
forward_edges[forward_edges_count].edge_id = e->edge_id;
forward_edges[forward_edges_count++].face_left = newface.face_id;
found++;
if ( found == 2 ) break;
}
else if ( -seid == e->edge_id )
{
/* IDEA: remove entry from signed_edge_ids ? */
LWDEBUGF(1, "Edge %d is a backward edge of the new ring", e->edge_id);
backward_edges[backward_edges_count].edge_id = e->edge_id;
backward_edges[backward_edges_count++].face_right = newface.face_id;
found++;
if ( found == 2 ) break;
}
}
if ( found ) continue;
/* We need to check only a single point
* (to avoid collapsed elements of the shell polygon
* giving false positive).
* The point but must not be an endpoint.
*/
if ( ! _lwt_GetInteriorEdgePoint(e->geom, &ep) )
{
lwfree(signed_edge_ids);
lwpoly_free(shell);
lwfree(forward_edges); /* contents owned by edges */
lwfree(backward_edges); /* contents owned by edges */
_lwt_release_edges(edges, numfaceedges);
lwerror("Could not find interior point for edge %d: %s",
e->edge_id, lwgeom_geos_errmsg);
return -2;
}
/* IDEA: check that bounding box shortcut is taken, or use
* shellbox to do it here */
contains = ptarray_contains_point(pa, &ep) == LW_INSIDE;
if ( contains == 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 )
{
lwfree( signed_edge_ids );
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( ret != forward_edges_count )
{
lwfree( signed_edge_ids );
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 )
{
lwfree( signed_edge_ids );
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( ret != backward_edges_count )
{
lwfree( signed_edge_ids );
lwerror("Unexpected error: %d edges updated when expecting %d",
ret, backward_edges_count);
return -2;
}
}
lwfree(forward_edges);
lwfree(backward_edges);
}
_lwt_release_edges(edges, numfaceedges);
/* Update isolated nodes which are now in new face */
uint64_t numisonodes = 1;
fields = LWT_COL_NODE_NODE_ID | LWT_COL_NODE_GEOM;
LWT_ISO_NODE *nodes = lwt_be_getNodeByFace(topo, &face,
&numisonodes, fields, newface.mbr);
if (numisonodes == UINT64_MAX)
{
lwfree(signed_edge_ids);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
if ( numisonodes ) {
LWT_ISO_NODE *updated_nodes = lwalloc(sizeof(LWT_ISO_NODE)*numisonodes);
int nodes_to_update = 0;
for (i=0; i<numisonodes; ++i)
{
LWT_ISO_NODE *n = &(nodes[i]);
const POINT2D *pt = getPoint2d_cp(n->geom->point, 0);
int contains = ptarray_contains_point(pa, pt) == LW_INSIDE;
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;
}
}
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);
}
_lwt_release_nodes(nodes, numisonodes);
if ( nodes_to_update )
{
int ret = lwt_be_updateNodesById(topo, updated_nodes,
nodes_to_update,
LWT_COL_NODE_CONTAINING_FACE);
if ( ret == -1 ) {
lwfree( signed_edge_ids );
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -2;
}
}
lwfree(updated_nodes);
}
lwfree(signed_edge_ids);
lwpoly_free(shell);
return newface.face_id;
}
/**
* @param modFace can be
* 0 - have two new faces replace a splitted face
* 1 - modify a splitted face, adding a new one
* -1 - do not check at all for face splitting
*
*/
static LWT_ELEMID
_lwt_AddEdge( LWT_TOPOLOGY* topo,
LWT_ELEMID start_node, LWT_ELEMID end_node,
LWLINE *geom, int skipChecks, int modFace )
{
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[2];
const LWPOINT *start_node_geom = NULL;
const LWPOINT *end_node_geom = NULL;
uint64_t num_nodes;
LWT_ISO_NODE *endpoints;
uint64_t i;
int prev_left;
int prev_right;
LWT_ISO_EDGE seledge;
LWT_ISO_EDGE updedge;
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;
/* TODO: should do the repeated points removal in 2D space */
cleangeom = lwgeom_remove_repeated_points( lwline_as_lwgeom(geom), 0 );
pa = lwgeom_as_lwline(cleangeom)->points;
if ( pa->npoints < 2 ) {
lwgeom_free(cleangeom);
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 azimuth of first edge end on start node */
getPoint2d_p(pa, 0, &p1);
if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, 0, 1, &pn) )
{
lwgeom_free(cleangeom);
lwerror("Invalid edge (no two distinct vertices exist)");
return -1;
}
if ( ! azimuth_pt_pt(&p1, &pn, &span.myaz) ) {
lwgeom_free(cleangeom);
lwerror("error computing azimuth of first edgeend [%.15g %.15g,%.15g %.15g]",
p1.x, p1.y, pn.x, pn.y);
return -1;
}
LWDEBUGF(1, "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);
if ( ! _lwt_FirstDistinctVertex2D(pa, &p2, pa->npoints-1, -1, &pn) )
{
lwgeom_free(cleangeom);
/* This should never happen as we checked the edge while computing first edgend */
lwerror("Invalid clean edge (no two distinct vertices exist) - should not happen");
return -1;
}
lwgeom_free(cleangeom);
if ( ! azimuth_pt_pt(&p2, &pn, &epan.myaz) ) {
lwerror("error computing azimuth of last edgeend [%.15g %.15g,%.15g %.15g]",
p2.x, p2.y, pn.x, pn.y);
return -1;
}
LWDEBUGF(1, "edge's end node is %g,%g", p2.x, p2.y);
/*
* Check endpoints existence, match with Curve geometry
* and get face information (if any)
*/
if ( start_node != end_node ) {
num_nodes = 2;
node_ids[0] = start_node;
node_ids[1] = end_node;
} else {
num_nodes = 1;
node_ids[0] = start_node;
}
endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes, LWT_COL_NODE_ALL );
if (num_nodes == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for ( i=0; i<num_nodes; ++i )
{
LWT_ISO_NODE* node = &(endpoints[i]);
if ( modFace != -1 && node->containing_face != -1 )
{
if ( newedge.face_left == -1 )
{
newedge.face_left = newedge.face_right = node->containing_face;
}
else if ( newedge.face_left != node->containing_face )
{
_lwt_release_nodes(endpoints, num_nodes);
lwerror("SQL/MM Spatial exception - geometry crosses an edge"
" (endnodes in faces %" LWTFMT_ELEMID " and %" LWTFMT_ELEMID ")",
newedge.face_left, node->containing_face);
}
}
LWDEBUGF(1, "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 )
{
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
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) )
{
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
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 )
{
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
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) )
{
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
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;
}
}
if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes);
if ( _lwt_CheckEdgeCrossing( topo, start_node, end_node, geom, 0 ) )
return -1;
} /* ! 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, -1 );
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 ( modFace != -1 )
{
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, -1 );
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 ( modFace != -1 )
{
if ( newedge.face_right == -1 ) {
newedge.face_right = span.ccwFace;
} else if ( newedge.face_right != epan.ccwFace ) {
/* side-location conflict */
lwerror("Side-location conflict: "
"new edge starts in face"
" %" LWTFMT_ELEMID " and ends in face"
" %" LWTFMT_ELEMID,
newedge.face_right, epan.ccwFace
);
return -1;
}
if ( newedge.face_left == -1 ) {
newedge.face_left = span.cwFace;
} else if ( newedge.face_left != epan.cwFace ) {
/* side-location conflict */
lwerror("Side-location conflict: "
"new edge starts in face"
" %" LWTFMT_ELEMID " and ends in face"
" %" LWTFMT_ELEMID,
newedge.face_left, epan.cwFace
);
return -1;
}
}
} 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 ( modFace > -1 )
{
if ( newedge.face_left != newedge.face_right )
{
lwerror("Left(%" LWTFMT_ELEMID ")/right(%" LWTFMT_ELEMID ")"
" 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, if required */
if ( modFace > -1 ) {
if ( ! isclosed && ( epan.was_isolated || span.was_isolated ) )
{
LWDEBUG(1, "New edge is dangling, so it cannot split any face");
return newedge.edge_id; /* no split */
}
int newface1 = -1;
/* IDEA: avoid building edge ring if input is closed, which means we
* know in advance it splits a face */
if ( ! modFace )
{
newface1 = _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 0 );
if ( newface1 == 0 ) {
LWDEBUG(1, "New edge does not split any face");
return newedge.edge_id; /* no split */
}
}
int newface = _lwt_AddFaceSplit( topo, newedge.edge_id,
newedge.face_left, 0 );
if ( modFace )
{
if ( newface == 0 ) {
LWDEBUG(1, "New edge does not split any face");
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
*/
if ( newedge.face_left != 0 )
{
ret = lwt_be_updateTopoGeomFaceSplit(topo, newedge.face_left,
newface, newface1);
if ( ret == 0 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ! modFace )
{
/* drop old face from the face table */
ret = lwt_be_deleteFacesById(topo, &(newedge.face_left), 1);
if ( ret == -1 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
}
} // end of face split checking
return newedge.edge_id;
}
LWT_ELEMID
lwt_AddEdgeModFace( LWT_TOPOLOGY* topo,
LWT_ELEMID start_node, LWT_ELEMID end_node,
LWLINE *geom, int skipChecks )
{
return _lwt_AddEdge( topo, start_node, end_node, geom, skipChecks, 1 );
}
LWT_ELEMID
lwt_AddEdgeNewFaces( LWT_TOPOLOGY* topo,
LWT_ELEMID start_node, LWT_ELEMID end_node,
LWLINE *geom, int skipChecks )
{
return _lwt_AddEdge( topo, start_node, end_node, geom, skipChecks, 0 );
}
static LWGEOM *
_lwt_FaceByEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edges, int numfaceedges)
{
LWGEOM *outg;
LWCOLLECTION *bounds;
LWGEOM **geoms = lwalloc( sizeof(LWGEOM*) * numfaceedges );
int i, validedges = 0;
for ( i=0; i<numfaceedges; ++i )
{
/* NOTE: skipping edges with same face on both sides, although
* correct, results in a failure to build faces from
* invalid topologies as expected by legacy tests.
* TODO: update legacy tests expectances/unleash this skipping ?
*/
/* if ( edges[i].face_left == edges[i].face_right ) continue; */
geoms[validedges++] = lwline_as_lwgeom(edges[i].geom);
}
if ( ! validedges )
{
/* Face has no valid boundary edges, we'll return EMPTY, see
* https://trac.osgeo.org/postgis/ticket/3221 */
if ( numfaceedges ) lwfree(geoms);
LWDEBUG(1, "_lwt_FaceByEdges returning empty polygon");
return lwpoly_as_lwgeom(
lwpoly_construct_empty(topo->srid, topo->hasZ, 0)
);
}
bounds = lwcollection_construct(MULTILINETYPE,
topo->srid,
NULL, /* gbox */
validedges,
geoms);
outg = lwgeom_buildarea( lwcollection_as_lwgeom(bounds) );
lwcollection_release(bounds);
lwfree(geoms);
#if 0
{
size_t sz;
char *wkt = lwgeom_to_wkt(outg, WKT_EXTENDED, 2, &sz);
LWDEBUGF(1, "_lwt_FaceByEdges returning area: %s", wkt);
lwfree(wkt);
}
#endif
return outg;
}
LWGEOM*
lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID faceid)
{
uint64_t numfaceedges;
LWT_ISO_EDGE *edges;
LWT_ISO_FACE *face;
LWPOLY *out;
LWGEOM *outg;
uint64_t i, edgeid;
int fields;
if (faceid == 0)
{
lwerror("SQL/MM Spatial exception - universal face has no geometry");
return NULL;
}
/* Construct the face geometry */
numfaceedges = 1;
fields = LWT_COL_EDGE_GEOM |
LWT_COL_EDGE_EDGE_ID |
LWT_COL_EDGE_FACE_LEFT |
LWT_COL_EDGE_FACE_RIGHT
;
edges = lwt_be_getEdgeByFace( topo, &faceid, &numfaceedges, fields, NULL );
if (numfaceedges == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
LWDEBUGF(1, "lwt_GetFaceGeometry: lwt_be_getEdgeByFace returned %d edges", numfaceedges);
if ( numfaceedges == 0 )
{
i = 1;
face = lwt_be_getFaceById(topo, &faceid, &i, LWT_COL_FACE_FACE_ID);
if (i == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
if ( i == 0 ) {
lwerror("SQL/MM Spatial exception - non-existent face.");
return NULL;
}
lwfree( face );
if ( i > 1 ) {
lwerror("Corrupted topology: multiple face records have face_id=%"
LWTFMT_ELEMID, faceid);
return NULL;
}
/* Face has no boundary edges, we'll return EMPTY, see
* https://trac.osgeo.org/postgis/ticket/3221 */
lwnotice("Corrupted topology: face %"
LWTFMT_ELEMID " has no associated edges.", faceid);
out = lwpoly_construct_empty(topo->srid, topo->hasZ, 0);
return lwpoly_as_lwgeom(out);
}
edgeid = edges[0].edge_id;
outg = _lwt_FaceByEdges( topo, edges, numfaceedges );
_lwt_release_edges(edges, numfaceedges);
if ( ! outg )
{
/* Face did have edges but no polygon could be constructed
* with that material, sounds like a corrupted topology..
*
* We'll return EMPTY, see
* https://trac.osgeo.org/postgis/ticket/3221 */
lwnotice("Corrupted topology: face %"
LWTFMT_ELEMID " could not be constructed only from edges "
"knowing about it (like edge %" LWTFMT_ELEMID ").",
faceid, edgeid);
out = lwpoly_construct_empty(topo->srid, topo->hasZ, 0);
return lwpoly_as_lwgeom(out);
}
return outg;
}
/* Find which edge from the "edges" set defines the next
* portion of the given "ring".
*
* The edge might be either forward or backward.
*
* @param ring The ring to find definition of.
* It is assumed it does not contain duplicated vertices.
* @param from offset of the ring point to start looking from
* @param edges array of edges to search into
* @param numedges number of edges in the edges array
*
* @return index of the edge defining the next ring portion or
* -1 if no edge was found to be part of the ring
*/
static int
_lwt_FindNextRingEdge(const POINTARRAY *ring, int from,
const LWT_ISO_EDGE *edges, int numedges)
{
int i;
POINT2D p1;
/* Get starting ring point */
getPoint2d_p(ring, from, &p1);
LWDEBUGF(1, "Ring's 'from' point (%d) is %g,%g", from, p1.x, p1.y);
/* find the edges defining the next portion of ring starting from
* vertex "from" */
for ( i=0; i<numedges; ++i )
{
const LWT_ISO_EDGE *isoe = &(edges[i]);
LWLINE *edge = isoe->geom;
POINTARRAY *epa = edge->points;
POINT2D p2, pt;
int match = 0;
uint32_t j;
/* Skip if the edge is a dangling one */
if ( isoe->face_left == isoe->face_right )
{
LWDEBUGF(3, "_lwt_FindNextRingEdge: edge %" LWTFMT_ELEMID
" has same face (%" LWTFMT_ELEMID
") on both sides, skipping",
isoe->edge_id, isoe->face_left);
continue;
}
if (epa->npoints < 2)
{
LWDEBUGF(3, "_lwt_FindNextRingEdge: edge %" LWTFMT_ELEMID
" has only %"PRIu32" points",
isoe->edge_id, epa->npoints);
continue;
}
#if 0
size_t sz;
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " is %s",
isoe->edge_id,
lwgeom_to_wkt(lwline_as_lwgeom(edge), WKT_EXTENDED, 2, &sz));
#endif
/* ptarray_remove_repeated_points ? */
getPoint2d_p(epa, 0, &p2);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'first' point is %g,%g",
isoe->edge_id, p2.x, p2.y);
LWDEBUGF(1, "Rings's 'from' point is still %g,%g", p1.x, p1.y);
if ( p2d_same(&p1, &p2) )
{
LWDEBUG(1, "p2d_same(p1,p2) returned true");
LWDEBUGF(1, "First point of edge %" LWTFMT_ELEMID
" matches ring vertex %d", isoe->edge_id, from);
/* first point matches, let's check next non-equal one */
for ( j=1; j<epa->npoints; ++j )
{
getPoint2d_p(epa, j, &p2);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'next' point %d is %g,%g",
isoe->edge_id, j, p2.x, p2.y);
/* we won't check duplicated edge points */
if ( p2d_same(&p1, &p2) ) continue;
/* we assume there are no duplicated points in ring */
getPoint2d_p(ring, from+1, &pt);
LWDEBUGF(1, "Ring's point %d is %g,%g",
from+1, pt.x, pt.y);
match = p2d_same(&pt, &p2);
break; /* we want to check a single non-equal next vertex */
}
#if POSTGIS_DEBUG_LEVEL > 0
if ( match ) {
LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID
" matches ring vertex %d", isoe->edge_id, from+1);
} else {
LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID
" does not match ring vertex %d", isoe->edge_id, from+1);
}
#endif
}
if ( ! match )
{
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " did not match as forward",
isoe->edge_id);
getPoint2d_p(epa, epa->npoints-1, &p2);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'last' point is %g,%g",
isoe->edge_id, p2.x, p2.y);
if ( p2d_same(&p1, &p2) )
{
LWDEBUGF(1, "Last point of edge %" LWTFMT_ELEMID
" matches ring vertex %d", isoe->edge_id, from);
/* last point matches, let's check next non-equal one */
for ( j=2; j<=epa->npoints; j++ )
{
getPoint2d_p(epa, epa->npoints - j, &p2);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'prev' point %d is %g,%g",
isoe->edge_id, epa->npoints - j, p2.x, p2.y);
/* we won't check duplicated edge points */
if ( p2d_same(&p1, &p2) ) continue;
/* we assume there are no duplicated points in ring */
getPoint2d_p(ring, from+1, &pt);
LWDEBUGF(1, "Ring's point %d is %g,%g",
from+1, pt.x, pt.y);
match = p2d_same(&pt, &p2);
break; /* we want to check a single non-equal next vertex */
}
}
#if POSTGIS_DEBUG_LEVEL > 0
if ( match ) {
LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID
" matches ring vertex %d", isoe->edge_id, from+1);
} else {
LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID
" does not match ring vertex %d", isoe->edge_id, from+1);
}
#endif
}
if ( match ) return i;
}
return -1;
}
/* Reverse values in array between "from" (inclusive)
* and "to" (exclusive) indexes */
static void
_lwt_ReverseElemidArray(LWT_ELEMID *ary, int from, int to)
{
LWT_ELEMID t;
while (from < to)
{
t = ary[from];
ary[from++] = ary[to];
ary[to--] = t;
}
}
/* Rotate values in array between "from" (inclusive)
* and "to" (exclusive) indexes, so that "rotidx" is
* the new value at "from" */
static void
_lwt_RotateElemidArray(LWT_ELEMID *ary, int from, int to, int rotidx)
{
_lwt_ReverseElemidArray(ary, from, rotidx-1);
_lwt_ReverseElemidArray(ary, rotidx, to-1);
_lwt_ReverseElemidArray(ary, from, to-1);
}
int
lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face_id, LWT_ELEMID **out )
{
LWGEOM *face;
LWPOLY *facepoly;
LWT_ISO_EDGE *edges;
uint64_t numfaceedges;
int fields;
uint32_t i;
int nseid = 0; /* number of signed edge ids */
int prevseid;
LWT_ELEMID *seid; /* signed edge ids */
/* Get list of face edges */
numfaceedges = 1;
fields = LWT_COL_EDGE_EDGE_ID |
LWT_COL_EDGE_GEOM |
LWT_COL_EDGE_FACE_LEFT |
LWT_COL_EDGE_FACE_RIGHT
;
edges = lwt_be_getEdgeByFace( topo, &face_id, &numfaceedges, fields, NULL );
if (numfaceedges == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ! numfaceedges ) return 0; /* no edges in output */
LWDEBUGF(1, "lwt_GetFaceEdges: lwt_be_getEdgeByFace returned %d edges", numfaceedges);
/* order edges by occurrence in face */
face = _lwt_FaceByEdges(topo, edges, numfaceedges);
if ( ! face )
{
/* _lwt_FaceByEdges should have already invoked lwerror in this case */
_lwt_release_edges(edges, numfaceedges);
return -1;
}
if ( lwgeom_is_empty(face) )
{
/* no edges in output */
_lwt_release_edges(edges, numfaceedges);
lwgeom_free(face);
return 0;
}
/* force_lhr, if the face is not the universe */
/* _lwt_FaceByEdges seems to guaranteed RHR */
/* lwgeom_force_clockwise(face); */
if ( face_id ) lwgeom_reverse_in_place(face);
#if 0
{
size_t sz;
char *wkt = lwgeom_to_wkt(face, WKT_EXTENDED, 6, &sz);
LWDEBUGF(1, "Geometry of face %" LWTFMT_ELEMID " is: %s",
face_id, wkt);
lwfree(wkt);
}
#endif
facepoly = lwgeom_as_lwpoly(face);
if ( ! facepoly )
{
_lwt_release_edges(edges, numfaceedges);
lwgeom_free(face);
lwerror("Geometry of face %" LWTFMT_ELEMID " is not a polygon", face_id);
return -1;
}
nseid = prevseid = 0;
seid = lwalloc( sizeof(LWT_ELEMID) * numfaceedges );
/* for each ring of the face polygon... */
for ( i=0; i<facepoly->nrings; ++i )
{
const POINTARRAY *ring = facepoly->rings[i];
int32_t j = 0;
LWT_ISO_EDGE *nextedge;
LWLINE *nextline;
LWDEBUGF(1, "Ring %d has %d points", i, ring->npoints);
while ( j < (int32_t) ring->npoints-1 )
{
LWDEBUGF(1, "Looking for edge covering ring %d from vertex %d",
i, j);
int edgeno = _lwt_FindNextRingEdge(ring, j, edges, numfaceedges);
if ( edgeno == -1 )
{
/* should never happen */
_lwt_release_edges(edges, numfaceedges);
lwgeom_free(face);
lwfree(seid);
lwerror("No edge (among %d) found to be defining geometry of face %"
LWTFMT_ELEMID, numfaceedges, face_id);
return -1;
}
nextedge = &(edges[edgeno]);
nextline = nextedge->geom;
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID
" covers ring %d from vertex %d to %d",
nextedge->edge_id, i, j, j + nextline->points->npoints - 1);
#if 0
{
size_t sz;
char *wkt = lwgeom_to_wkt(lwline_as_lwgeom(nextline), WKT_EXTENDED, 6, &sz);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " is %s",
nextedge->edge_id, wkt);
lwfree(wkt);
}
#endif
j += nextline->points->npoints - 1;
/* Add next edge to the output array */
seid[nseid++] = nextedge->face_left == face_id ?
nextedge->edge_id :
-nextedge->edge_id;
/* avoid checking again on next time turn */
nextedge->face_left = nextedge->face_right = -1;
}
/* now "scroll" the list of edges so that the one
* with smaller absolute edge_id is first */
/* Range is: [prevseid, nseid) -- [inclusive, exclusive) */
if ( (nseid - prevseid) > 1 )
{{
LWT_ELEMID minid = 0;
int minidx = 0;
LWDEBUGF(1, "Looking for smallest id among the %d edges "
"composing ring %d", (nseid-prevseid), i);
for ( j=prevseid; j<nseid; ++j )
{
LWT_ELEMID id = llabs(seid[j]);
LWDEBUGF(1, "Abs id of edge in pos %d is %" LWTFMT_ELEMID, j, id);
if ( ! minid || id < minid )
{
minid = id;
minidx = j;
}
}
LWDEBUGF(1, "Smallest id is %" LWTFMT_ELEMID
" at position %d", minid, minidx);
if ( minidx != prevseid )
{
_lwt_RotateElemidArray(seid, prevseid, nseid, minidx);
}
}}
prevseid = nseid;
}
lwgeom_free(face);
_lwt_release_edges(edges, numfaceedges);
*out = seid;
return nseid;
}
int
lwt_ChangeEdgeGeom(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, LWLINE *geom)
{
LWT_ISO_EDGE *oldedge;
LWT_ISO_EDGE newedge;
POINT2D p1, p2, pt;
uint64_t i;
int isclosed = 0;
/* curve must be simple */
if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) )
{
lwerror("SQL/MM Spatial exception - curve not simple");
return -1;
}
i = 1;
oldedge = lwt_be_getEdgeById(topo, &edge_id, &i, LWT_COL_EDGE_ALL);
if ( ! oldedge )
{
LWDEBUGF(1, "lwt_ChangeEdgeGeom: "
"lwt_be_getEdgeById returned NULL and set i=%d", i);
if (i == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if ( i == 0 )
{
lwerror("SQL/MM Spatial exception - non-existent edge %"
LWTFMT_ELEMID, edge_id);
return -1;
}
else
{
lwerror("Backend coding error: getEdgeById callback returned NULL "
"but numelements output parameter has value %d "
"(expected 0 or 1)", i);
return -1;
}
}
LWDEBUGF(1, "lwt_ChangeEdgeGeom: "
"old edge has %d points, new edge has %d points",
oldedge->geom->points->npoints, geom->points->npoints);
/*
* e) Check StartPoint consistency
*/
getPoint2d_p(oldedge->geom->points, 0, &p1);
getPoint2d_p(geom->points, 0, &pt);
if ( ! p2d_same(&p1, &pt) )
{
_lwt_release_edges(oldedge, 1);
lwerror("SQL/MM Spatial exception - "
"start node not geometry start point.");
return -1;
}
/*
* f) Check EndPoint consistency
*/
if ( oldedge->geom->points->npoints < 2 )
{
_lwt_release_edges(oldedge, 1);
lwerror("Corrupted topology: edge %" LWTFMT_ELEMID
" has less than 2 vertices", oldedge->edge_id);
return -1;
}
getPoint2d_p(oldedge->geom->points, oldedge->geom->points->npoints-1, &p2);
if ( geom->points->npoints < 2 )
{
_lwt_release_edges(oldedge, 1);
lwerror("Invalid edge: less than 2 vertices");
return -1;
}
getPoint2d_p(geom->points, geom->points->npoints-1, &pt);
if ( ! p2d_same(&pt, &p2) )
{
_lwt_release_edges(oldedge, 1);
lwerror("SQL/MM Spatial exception - "
"end node not geometry end point.");
return -1;
}
/* Not in the specs:
* if the edge is closed, check we didn't change winding !
* (should be part of isomorphism checking)
*/
if ( oldedge->start_node == oldedge->end_node )
{
isclosed = 1;
#if 1 /* TODO: this is actually bogus as a test */
/* check for valid edge (distinct vertices must exist) */
if ( ! _lwt_GetInteriorEdgePoint(geom, &pt) )
{
_lwt_release_edges(oldedge, 1);
lwerror("Invalid edge (no two distinct vertices exist)");
return -1;
}
#endif
if ( ptarray_isccw(oldedge->geom->points) !=
ptarray_isccw(geom->points) )
{
_lwt_release_edges(oldedge, 1);
lwerror("Edge twist at node POINT(%g %g)", p1.x, p1.y);
return -1;
}
}
if ( _lwt_CheckEdgeCrossing(topo, oldedge->start_node,
oldedge->end_node, geom, edge_id ) )
{
/* would have called lwerror already, leaking :( */
_lwt_release_edges(oldedge, 1);
return -1;
}
LWDEBUG(1, "lwt_ChangeEdgeGeom: "
"edge crossing check passed ");
/*
* Not in the specs:
* Check topological isomorphism
*/
/* Check that the "motion range" doesn't include any node */
// 1. compute combined bbox of old and new edge
GBOX mbox; /* motion box */
lwgeom_add_bbox((LWGEOM*)oldedge->geom); /* just in case */
lwgeom_add_bbox((LWGEOM*)geom); /* just in case */
gbox_union(oldedge->geom->bbox, geom->bbox, &mbox);
// 2. fetch all nodes in the combined box
LWT_ISO_NODE *nodes;
uint64_t numnodes;
nodes = lwt_be_getNodeWithinBox2D(topo, &mbox, &numnodes,
LWT_COL_NODE_ALL, 0);
LWDEBUGF(1, "lwt_be_getNodeWithinBox2D returned %d nodes", numnodes);
if (numnodes == UINT64_MAX)
{
_lwt_release_edges(oldedge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
// 3. if any node beside endnodes are found:
if ( numnodes > ( 1 + isclosed ? 0 : 1 ) )
{{
// 3.2. bail out if any node is in one and not the other
for (i=0; i<numnodes; ++i)
{
LWT_ISO_NODE *n = &(nodes[i]);
if ( n->node_id == oldedge->start_node ) continue;
if ( n->node_id == oldedge->end_node ) continue;
const POINT2D *pt = getPoint2d_cp(n->geom->point, 0);
int ocont = ptarray_contains_point_partial(oldedge->geom->points, pt, isclosed, NULL) == LW_INSIDE;
int ncont = ptarray_contains_point_partial(geom->points, pt, isclosed, NULL) == LW_INSIDE;
if (ocont != ncont)
{
size_t sz;
char *wkt = lwgeom_to_wkt(lwpoint_as_lwgeom(n->geom), WKT_ISO, 15, &sz);
_lwt_release_nodes(nodes, numnodes);
lwerror("Edge motion collision at %s", wkt);
lwfree(wkt); /* would not necessarely reach this point */
return -1;
}
}
}}
if ( numnodes ) _lwt_release_nodes(nodes, numnodes);
LWDEBUG(1, "nodes containment check passed");
/*
* Check edge adjacency before
* TODO: can be optimized to gather azimuths of all edge ends once
*/
edgeend span_pre, epan_pre;
/* initialize span_pre.myaz and epan_pre.myaz with existing edge */
int res = _lwt_InitEdgeEndByLine(&span_pre, &epan_pre, oldedge->geom, &p1, &p2);
if (res)
return -1; /* lwerror should have been raised */
_lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_pre,
isclosed ? &epan_pre : NULL, edge_id );
_lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_pre,
isclosed ? &span_pre : NULL, edge_id );
LWDEBUGF(1, "edges adjacent to old edge are %" LWTFMT_ELEMID
" and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID
" and %" LWTFMT_ELEMID " (last point)",
span_pre.nextCW, span_pre.nextCCW,
epan_pre.nextCW, epan_pre.nextCCW);
/* update edge geometry */
newedge.edge_id = edge_id;
newedge.geom = geom;
res = lwt_be_updateEdgesById(topo, &newedge, 1, LWT_COL_EDGE_GEOM);
if (res == -1)
{
_lwt_release_edges(oldedge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if (!res)
{
_lwt_release_edges(oldedge, 1);
lwerror("Unexpected error: %d edges updated when expecting 1", i);
return -1;
}
/*
* Check edge adjacency after
*/
edgeend span_post, epan_post;
/* initialize epan_post.myaz and epan_post.myaz */
res = _lwt_InitEdgeEndByLine(&span_post, &epan_post, geom, &p1, &p2);
if (res)
return -1; /* lwerror should have been raised */
_lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_post,
isclosed ? &epan_post : NULL, edge_id );
_lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_post,
isclosed ? &span_post : NULL, edge_id );
LWDEBUGF(1, "edges adjacent to new edge are %" LWTFMT_ELEMID
" and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID
" and %" LWTFMT_ELEMID " (last point)",
span_pre.nextCW, span_pre.nextCCW,
epan_pre.nextCW, epan_pre.nextCCW);
/* Bail out if next CW or CCW edge on start node changed */
if ( span_pre.nextCW != span_post.nextCW ||
span_pre.nextCCW != span_post.nextCCW )
{{
LWT_ELEMID nid = oldedge->start_node;
_lwt_release_edges(oldedge, 1);
lwerror("Edge changed disposition around start node %"
LWTFMT_ELEMID, nid);
return -1;
}}
/* Bail out if next CW or CCW edge on end node changed */
if ( epan_pre.nextCW != epan_post.nextCW ||
epan_pre.nextCCW != epan_post.nextCCW )
{{
LWT_ELEMID nid = oldedge->end_node;
_lwt_release_edges(oldedge, 1);
lwerror("Edge changed disposition around end node %"
LWTFMT_ELEMID, nid);
return -1;
}}
/*
-- Update faces MBR of left and right faces
-- TODO: think about ways to optimize this part, like see if
-- the old edge geometry participated in the definition
-- of the current MBR (for shrinking) or the new edge MBR
-- would be larger than the old face MBR...
--
*/
const GBOX* oldbox = lwgeom_get_bbox(lwline_as_lwgeom(oldedge->geom));
const GBOX* newbox = lwgeom_get_bbox(lwline_as_lwgeom(geom));
if ( ! gbox_same(oldbox, newbox) )
{
GBOX* updatedBox;
uint64_t facestoupdate = 0;
LWT_ISO_FACE faces[2];
if ( oldedge->face_left > 0 )
{
updatedBox = lwt_be_computeFaceMBR(topo, oldedge->face_left);
if ( ! updatedBox )
{
lwerror("Corrupted topology: face %d, left of edge %d, has no bbox",
oldedge->face_left, edge_id);
return -1;
}
faces[facestoupdate].face_id = oldedge->face_left;
/* ownership transferred to faces[] */
faces[facestoupdate++].mbr = updatedBox;
}
if ( oldedge->face_right > 0
/* no need to update twice the same face.. */
&& oldedge->face_right != oldedge->face_left )
{
updatedBox = lwt_be_computeFaceMBR(topo, oldedge->face_right);
if ( ! updatedBox )
{
lwerror("Corrupted topology: face %d, right of edge %d, has no bbox",
oldedge->face_right, edge_id);
return -1;
}
faces[facestoupdate].face_id = oldedge->face_right;
/* ownership transferred to faces[] */
faces[facestoupdate++].mbr = updatedBox;
}
LWDEBUGF(1, "%d faces to update", facestoupdate);
if ( facestoupdate )
{
uint64_t updatedFaces = lwt_be_updateFacesById(topo, &(faces[0]), facestoupdate);
if (updatedFaces != facestoupdate)
{
while ( facestoupdate-- ) lwfree(faces[facestoupdate].mbr);
_lwt_release_edges(oldedge, 1);
if (updatedFaces == UINT64_MAX)
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
else
lwerror("Unexpected error: %d faces updated when expecting 1", updatedFaces);
return -1;
}
}
while ( facestoupdate-- ) lwfree(faces[facestoupdate].mbr);
}
else
{
LWDEBUG(1, "BBOX of changed edge did not change");
}
LWDEBUG(1, "all done, cleaning up edges");
_lwt_release_edges(oldedge, 1);
return 0; /* success */
}
/* Only return CONTAINING_FACE in the node object */
static LWT_ISO_NODE *
_lwt_GetIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid)
{
LWT_ISO_NODE *node;
uint64_t n = 1;
node = lwt_be_getNodeById( topo, &nid, &n, LWT_COL_NODE_CONTAINING_FACE );
if (n == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return 0;
}
if ( n < 1 ) {
lwerror("SQL/MM Spatial exception - non-existent node");
return 0;
}
if ( node->containing_face == -1 )
{
lwfree(node);
lwerror("SQL/MM Spatial exception - not isolated node");
return 0;
}
return node;
}
int
lwt_MoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid, LWPOINT *pt)
{
LWT_ISO_NODE *node;
int ret;
int newPointFace;
node = _lwt_GetIsoNode( topo, nid );
if ( ! node ) return -1;
if ( lwt_be_ExistsCoincidentNode(topo, pt) )
{
lwfree(node);
lwerror("SQL/MM Spatial exception - coincident node");
return -1;
}
if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) )
{
lwfree(node);
lwerror("SQL/MM Spatial exception - edge crosses node.");
return -1;
}
/* Check that the new point is in the same containing face !
* See https://trac.osgeo.org/postgis/ticket/3232 */
newPointFace = lwt_GetFaceContainingPoint(topo, pt);
if ( newPointFace == -1 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( node->containing_face != newPointFace )
{
lwfree(node);
lwerror("Cannot move isolated node across faces");
return -1;
}
node->node_id = nid;
node->geom = pt;
ret = lwt_be_updateNodesById(topo, node, 1,
LWT_COL_NODE_GEOM);
if ( ret == -1 ) {
lwfree(node);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
lwfree(node);
return 0;
}
int
lwt_RemoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid)
{
LWT_ISO_NODE *node;
int n = 1;
node = _lwt_GetIsoNode( topo, nid );
if ( ! node ) return -1;
n = lwt_be_deleteNodesById( topo, &nid, n );
if ( n == -1 )
{
lwfree(node);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( n != 1 )
{
lwfree(node);
lwerror("Unexpected error: %d nodes deleted when expecting 1", n);
return -1;
}
if ( ! lwt_be_checkTopoGeomRemIsoNode(topo, nid) )
{
lwfree(node);
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
lwfree(node);
return 0; /* success */
}
int
lwt_RemIsoEdge(LWT_TOPOLOGY* topo, LWT_ELEMID id)
{
LWT_ISO_EDGE deledge;
LWT_ISO_EDGE *edge;
LWT_ELEMID nid[2];
LWT_ISO_NODE upd_node[2];
LWT_ELEMID containing_face;
uint64_t n = 1;
uint64_t i;
edge = lwt_be_getEdgeById( topo, &id, &n, LWT_COL_EDGE_START_NODE|
LWT_COL_EDGE_END_NODE |
LWT_COL_EDGE_FACE_LEFT |
LWT_COL_EDGE_FACE_RIGHT );
if ( ! edge )
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ! n )
{
lwerror("SQL/MM Spatial exception - non-existent edge");
return -1;
}
if ( n > 1 )
{
lwfree(edge);
lwerror("Corrupted topology: more than a single edge have id %"
LWTFMT_ELEMID, id);
return -1;
}
if ( edge[0].face_left != edge[0].face_right )
{
lwfree(edge);
lwerror("SQL/MM Spatial exception - not isolated edge");
return -1;
}
containing_face = edge[0].face_left;
nid[0] = edge[0].start_node;
nid[1] = edge[0].end_node;
lwfree(edge);
n = 2;
edge = lwt_be_getEdgeByNode( topo, nid, &n, LWT_COL_EDGE_EDGE_ID );
if ((n == UINT64_MAX) || (edge == NULL))
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for (i = 0; i < n; ++i)
{
if (edge[i].edge_id != id)
{
lwfree(edge);
lwerror("SQL/MM Spatial exception - not isolated edge");
return -1;
}
}
lwfree(edge);
deledge.edge_id = id;
n = lwt_be_deleteEdges( topo, &deledge, LWT_COL_EDGE_EDGE_ID );
if (n == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( n != 1 )
{
lwerror("Unexpected error: %d edges deleted when expecting 1", n);
return -1;
}
upd_node[0].node_id = nid[0];
upd_node[0].containing_face = containing_face;
n = 1;
if ( nid[1] != nid[0] ) {
upd_node[1].node_id = nid[1];
upd_node[1].containing_face = containing_face;
++n;
}
n = lwt_be_updateNodesById(topo, upd_node, n,
LWT_COL_NODE_CONTAINING_FACE);
if (n == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Check that the edge can be safely removed
* See https://trac.osgeo.org/postgis/ticket/3248
*/
if ( ! lwt_be_checkTopoGeomRemIsoEdge(topo, id) )
{
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
return 0; /* success */
}
/* Used by _lwt_RemEdge to update edge face ref on healing
*
* @param of old face id (never 0 as you cannot remove face 0)
* @param nf new face id
* @return 0 on success, -1 on backend error
*/
static int
_lwt_UpdateEdgeFaceRef( LWT_TOPOLOGY *topo, LWT_ELEMID of, LWT_ELEMID nf)
{
LWT_ISO_EDGE sel_edge, upd_edge;
int ret;
assert( of != 0 );
/* Update face_left for all edges still referencing old face */
sel_edge.face_left = of;
upd_edge.face_left = nf;
ret = lwt_be_updateEdges(topo, &sel_edge, LWT_COL_EDGE_FACE_LEFT,
&upd_edge, LWT_COL_EDGE_FACE_LEFT,
NULL, 0);
if ( ret == -1 ) return -1;
/* Update face_right for all edges still referencing old face */
sel_edge.face_right = of;
upd_edge.face_right = nf;
ret = lwt_be_updateEdges(topo, &sel_edge, LWT_COL_EDGE_FACE_RIGHT,
&upd_edge, LWT_COL_EDGE_FACE_RIGHT,
NULL, 0);
if ( ret == -1 ) return -1;
return 0;
}
/* Used by _lwt_RemEdge to update node face ref on healing
*
* @param of old face id (never 0 as you cannot remove face 0)
* @param nf new face id
* @return 0 on success, -1 on backend error
*/
static int
_lwt_UpdateNodeFaceRef( LWT_TOPOLOGY *topo, LWT_ELEMID of, LWT_ELEMID nf)
{
LWT_ISO_NODE sel, upd;
int ret;
assert( of != 0 );
/* Update face_left for all edges still referencing old face */
sel.containing_face = of;
upd.containing_face = nf;
ret = lwt_be_updateNodes(topo, &sel, LWT_COL_NODE_CONTAINING_FACE,
&upd, LWT_COL_NODE_CONTAINING_FACE,
NULL, 0);
if ( ret == -1 ) return -1;
return 0;
}
/* Used by lwt_RemEdgeModFace and lwt_RemEdgeNewFaces
*
* Returns -1 on error, identifier of the face that takes up the space
* previously occupied by the removed edge if modFace is 1, identifier of
* the created face (0 if none) if modFace is 0.
*/
static LWT_ELEMID
_lwt_RemEdge( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, int modFace )
{
uint64_t i, nedges, nfaces, fields;
LWT_ISO_EDGE *edge = NULL;
LWT_ISO_EDGE *upd_edge = NULL;
LWT_ISO_EDGE upd_edge_left[2];
int nedge_left = 0;
LWT_ISO_EDGE upd_edge_right[2];
int nedge_right = 0;
LWT_ISO_NODE upd_node[2];
int nnode = 0;
LWT_ISO_FACE *faces = NULL;
LWT_ISO_FACE newface;
LWT_ELEMID node_ids[2];
LWT_ELEMID face_ids[2];
int fnode_edges = 0; /* number of edges on the first node (excluded
* the one being removed ) */
int lnode_edges = 0; /* number of edges on the last node (excluded
* the one being removed ) */
newface.face_id = 0;
i = 1;
edge = lwt_be_getEdgeById(topo, &edge_id, &i, LWT_COL_EDGE_ALL);
if (!edge)
{
LWDEBUGF(1, "lwt_be_getEdgeById returned NULL and set i=%d", i);
if (i == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if (i == 0)
{
lwerror("SQL/MM Spatial exception - non-existent edge %" LWTFMT_ELEMID, edge_id);
return -1;
}
else
{
lwerror(
"Backend coding error: getEdgeById callback returned NULL "
"but numelements output parameter has value %d "
"(expected 0 or 1)",
i);
return -1;
}
}
if ( ! lwt_be_checkTopoGeomRemEdge(topo, edge_id,
edge->face_left, edge->face_right) )
{
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
LWDEBUG(1, "Updating next_{right,left}_face of ring edges...");
/* Update edge linking */
nedges = 0;
node_ids[nedges++] = edge->start_node;
if ( edge->end_node != edge->start_node )
{
node_ids[nedges++] = edge->end_node;
}
fields = LWT_COL_EDGE_EDGE_ID | LWT_COL_EDGE_START_NODE |
LWT_COL_EDGE_END_NODE | LWT_COL_EDGE_NEXT_LEFT |
LWT_COL_EDGE_NEXT_RIGHT;
upd_edge = lwt_be_getEdgeByNode( topo, &(node_ids[0]), &nedges, fields );
if (nedges == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
nedge_left = nedge_right = 0;
for ( i=0; i<nedges; ++i )
{
LWT_ISO_EDGE *e = &(upd_edge[i]);
if ( e->edge_id == edge_id ) continue;
if ( e->start_node == edge->start_node || e->end_node == edge->start_node )
{
++fnode_edges;
}
if ( e->start_node == edge->end_node || e->end_node == edge->end_node )
{
++lnode_edges;
}
if ( e->next_left == -edge_id )
{
upd_edge_left[nedge_left].edge_id = e->edge_id;
upd_edge_left[nedge_left++].next_left =
edge->next_left != edge_id ? edge->next_left : edge->next_right;
}
else if ( e->next_left == edge_id )
{
upd_edge_left[nedge_left].edge_id = e->edge_id;
upd_edge_left[nedge_left++].next_left =
edge->next_right != -edge_id ? edge->next_right : edge->next_left;
}
if ( e->next_right == -edge_id )
{
upd_edge_right[nedge_right].edge_id = e->edge_id;
upd_edge_right[nedge_right++].next_right =
edge->next_left != edge_id ? edge->next_left : edge->next_right;
}
else if ( e->next_right == edge_id )
{
upd_edge_right[nedge_right].edge_id = e->edge_id;
upd_edge_right[nedge_right++].next_right =
edge->next_right != -edge_id ? edge->next_right : edge->next_left;
}
}
if ( nedge_left )
{
LWDEBUGF(1, "updating %d 'next_left' edges", nedge_left);
/* update edges in upd_edge_left set next_left */
int result = lwt_be_updateEdgesById(topo, &(upd_edge_left[0]), nedge_left, LWT_COL_EDGE_NEXT_LEFT);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwfree(upd_edge);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
if ( nedge_right )
{
LWDEBUGF(1, "updating %d 'next_right' edges", nedge_right);
/* update edges in upd_edge_right set next_right */
int result = lwt_be_updateEdgesById(topo, &(upd_edge_right[0]), nedge_right, LWT_COL_EDGE_NEXT_RIGHT);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwfree(upd_edge);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
LWDEBUGF(1, "releasing %d updateable edges in %p", nedges, upd_edge);
lwfree(upd_edge);
/* Id of face that will take up all the space previously
* taken by left and right faces of the edge */
LWT_ELEMID floodface;
/* Find floodface, and update its mbr if != 0 */
if ( edge->face_left == edge->face_right )
{
floodface = edge->face_right;
}
else
{
/* Two faces healed */
if ( edge->face_left == 0 || edge->face_right == 0 )
{
floodface = 0;
LWDEBUG(1, "floodface is universe");
}
else
{
/* we choose right face as the face that will remain
* to be symmetric with ST_AddEdgeModFace */
floodface = edge->face_right;
LWDEBUGF(1, "floodface is %" LWTFMT_ELEMID, floodface);
/* update mbr of floodface as union of mbr of both faces */
face_ids[0] = edge->face_left;
face_ids[1] = edge->face_right;
nfaces = 2;
fields = LWT_COL_FACE_ALL;
faces = lwt_be_getFaceById(topo, face_ids, &nfaces, fields);
if (nfaces == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
GBOX *box1=NULL;
GBOX *box2=NULL;
for ( i=0; i<nfaces; ++i )
{
if ( faces[i].face_id == edge->face_left )
{
if ( ! box1 ) box1 = faces[i].mbr;
else
{
i = edge->face_left;
_lwt_release_edges(edge, 1);
_lwt_release_faces(faces, nfaces);
lwerror("corrupted topology: more than 1 face have face_id=%"
LWTFMT_ELEMID, i);
return -1;
}
}
else if ( faces[i].face_id == edge->face_right )
{
if ( ! box2 ) box2 = faces[i].mbr;
else
{
i = edge->face_right;
_lwt_release_edges(edge, 1);
_lwt_release_faces(faces, nfaces);
lwerror("corrupted topology: more than 1 face have face_id=%"
LWTFMT_ELEMID, i);
return -1;
}
}
else
{
i = faces[i].face_id;
_lwt_release_edges(edge, 1);
_lwt_release_faces(faces, nfaces);
lwerror("Backend coding error: getFaceById returned face "
"with non-requested id %" LWTFMT_ELEMID, i);
return -1;
}
}
if ( ! box1 ) {
i = edge->face_left;
_lwt_release_edges(edge, 1);
if ( nfaces ) _lwt_release_faces(faces, nfaces);
lwerror("corrupted topology: no face have face_id=%"
LWTFMT_ELEMID " (left face for edge %"
LWTFMT_ELEMID ")", i, edge_id);
return -1;
}
if ( ! box2 ) {
i = edge->face_right;
_lwt_release_edges(edge, 1);
if ( nfaces ) _lwt_release_faces(faces, nfaces);
lwerror("corrupted topology: no face have face_id=%"
LWTFMT_ELEMID " (right face for edge %"
LWTFMT_ELEMID ")", i, edge_id);
return -1;
}
gbox_merge(box2, box1); /* box1 is now the union of the two */
newface.mbr = box1;
if ( modFace )
{
newface.face_id = floodface;
int result = lwt_be_updateFacesById(topo, &newface, 1);
_lwt_release_faces(faces, 2);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if (result != 1)
{
_lwt_release_edges(edge, 1);
lwerror("Unexpected error: %d faces updated when expecting 1", i);
return -1;
}
}
else
{
/* New face replaces the old two faces */
newface.face_id = -1;
int result = lwt_be_insertFaces(topo, &newface, 1);
_lwt_release_faces(faces, 2);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if (result != 1)
{
_lwt_release_edges(edge, 1);
lwerror("Unexpected error: %d faces inserted when expecting 1", result);
return -1;
}
floodface = newface.face_id;
}
}
/* Update face references for edges and nodes still referencing
* the removed face(s) */
if ( edge->face_left != floodface )
{
if ( -1 == _lwt_UpdateEdgeFaceRef(topo, edge->face_left, floodface) )
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( -1 == _lwt_UpdateNodeFaceRef(topo, edge->face_left, floodface) )
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
if ( edge->face_right != floodface )
{
if ( -1 == _lwt_UpdateEdgeFaceRef(topo, edge->face_right, floodface) )
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( -1 == _lwt_UpdateNodeFaceRef(topo, edge->face_right, floodface) )
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
/* Update topogeoms on heal */
if ( ! lwt_be_updateTopoGeomFaceHeal(topo,
edge->face_right, edge->face_left,
floodface) )
{
_lwt_release_edges(edge, 1);
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
} /* two faces healed */
/* Delete the edge */
int result = lwt_be_deleteEdges(topo, edge, LWT_COL_EDGE_EDGE_ID);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* If any of the edge nodes remained isolated, set
* containing_face = floodface
*/
if ( ! fnode_edges )
{
upd_node[nnode].node_id = edge->start_node;
upd_node[nnode].containing_face = floodface;
++nnode;
}
if ( edge->end_node != edge->start_node && ! lnode_edges )
{
upd_node[nnode].node_id = edge->end_node;
upd_node[nnode].containing_face = floodface;
++nnode;
}
if ( nnode )
{
int result = lwt_be_updateNodesById(topo, upd_node, nnode, LWT_COL_NODE_CONTAINING_FACE);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
if ( edge->face_left != edge->face_right )
/* or there'd be no face to remove */
{
LWT_ELEMID ids[2];
int nids = 0;
if ( edge->face_right != floodface )
ids[nids++] = edge->face_right;
if ( edge->face_left != floodface )
ids[nids++] = edge->face_left;
int result = lwt_be_deleteFacesById(topo, ids, nids);
if (result == -1)
{
_lwt_release_edges(edge, 1);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
_lwt_release_edges(edge, 1);
return modFace ? floodface : newface.face_id;
}
LWT_ELEMID
lwt_RemEdgeModFace( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id )
{
return _lwt_RemEdge( topo, edge_id, 1 );
}
LWT_ELEMID
lwt_RemEdgeNewFace( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id )
{
return _lwt_RemEdge( topo, edge_id, 0 );
}
static LWT_ELEMID
_lwt_HealEdges( LWT_TOPOLOGY* topo, LWT_ELEMID eid1, LWT_ELEMID eid2,
int modEdge )
{
LWT_ELEMID ids[2];
LWT_ELEMID commonnode = -1;
int caseno = 0;
LWT_ISO_EDGE *node_edges;
uint64_t num_node_edges;
LWT_ISO_EDGE *edges;
LWT_ISO_EDGE *e1 = NULL;
LWT_ISO_EDGE *e2 = NULL;
LWT_ISO_EDGE newedge, updedge, seledge;
uint64_t nedges, i;
int e1freenode;
int e2sign, e2freenode;
POINTARRAY *pa;
char buf[256];
char *ptr;
size_t bufleft = 256;
ptr = buf;
/* NOT IN THE SPECS: see if the same edge is given twice.. */
if ( eid1 == eid2 )
{
lwerror("Cannot heal edge %" LWTFMT_ELEMID
" with itself, try with another", eid1);
return -1;
}
ids[0] = eid1;
ids[1] = eid2;
nedges = 2;
edges = lwt_be_getEdgeById(topo, ids, &nedges, LWT_COL_EDGE_ALL);
if ((nedges == UINT64_MAX) || (edges == NULL))
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for ( i=0; i<nedges; ++i )
{
if ( edges[i].edge_id == eid1 ) {
if ( e1 ) {
_lwt_release_edges(edges, nedges);
lwerror("Corrupted topology: multiple edges have id %"
LWTFMT_ELEMID, eid1);
return -1;
}
e1 = &(edges[i]);
}
else if ( edges[i].edge_id == eid2 ) {
if ( e2 ) {
_lwt_release_edges(edges, nedges);
lwerror("Corrupted topology: multiple edges have id %"
LWTFMT_ELEMID, eid2);
return -1;
}
e2 = &(edges[i]);
}
}
if ( ! e1 )
{
_lwt_release_edges(edges, nedges);
lwerror(
"SQL/MM Spatial exception - non-existent edge %" LWTFMT_ELEMID,
eid1);
return -1;
}
if ( ! e2 )
{
_lwt_release_edges(edges, nedges);
lwerror(
"SQL/MM Spatial exception - non-existent edge %" LWTFMT_ELEMID,
eid2);
return -1;
}
/* NOT IN THE SPECS: See if any of the two edges are closed. */
if ( e1->start_node == e1->end_node )
{
_lwt_release_edges(edges, nedges);
lwerror("Edge %" LWTFMT_ELEMID " is closed, cannot heal to edge %"
LWTFMT_ELEMID, eid1, eid2);
return -1;
}
if ( e2->start_node == e2->end_node )
{
_lwt_release_edges(edges, nedges);
lwerror("Edge %" LWTFMT_ELEMID " is closed, cannot heal to edge %"
LWTFMT_ELEMID, eid2, eid1);
return -1;
}
/* Find common node */
if ( e1->end_node == e2->start_node )
{
commonnode = e1->end_node;
caseno = 1;
}
else if ( e1->end_node == e2->end_node )
{
commonnode = e1->end_node;
caseno = 2;
}
/* Check if any other edge is connected to the common node, if found */
if ( commonnode != -1 )
{
num_node_edges = 1;
node_edges = lwt_be_getEdgeByNode( topo, &commonnode,
&num_node_edges, LWT_COL_EDGE_EDGE_ID );
if (num_node_edges == UINT64_MAX)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for (i=0; i<num_node_edges; ++i)
{
int r;
if ( node_edges[i].edge_id == eid1 ) continue;
if ( node_edges[i].edge_id == eid2 ) continue;
commonnode = -1;
/* append to string, for error message */
if ( bufleft > 0 ) {
r = snprintf(ptr, bufleft, "%s%" LWTFMT_ELEMID,
( ptr==buf ? "" : "," ), node_edges[i].edge_id);
if ( r >= (int) bufleft )
{
bufleft = 0;
buf[252] = '.';
buf[253] = '.';
buf[254] = '.';
buf[255] = '\0';
}
else
{
bufleft -= r;
ptr += r;
}
}
}
lwfree(node_edges);
}
if ( commonnode == -1 )
{
if ( e1->start_node == e2->start_node )
{
commonnode = e1->start_node;
caseno = 3;
}
else if ( e1->start_node == e2->end_node )
{
commonnode = e1->start_node;
caseno = 4;
}
/* Check if any other edge is connected to the common node, if found */
if ( commonnode != -1 )
{
num_node_edges = 1;
node_edges = lwt_be_getEdgeByNode( topo, &commonnode,
&num_node_edges, LWT_COL_EDGE_EDGE_ID );
if (num_node_edges == UINT64_MAX)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for (i=0; i<num_node_edges; ++i)
{
int r;
if ( node_edges[i].edge_id == eid1 ) continue;
if ( node_edges[i].edge_id == eid2 ) continue;
commonnode = -1;
/* append to string, for error message */
if ( bufleft > 0 ) {
r = snprintf(ptr, bufleft, "%s%" LWTFMT_ELEMID,
( ptr==buf ? "" : "," ), node_edges[i].edge_id);
if ( r >= (int) bufleft )
{
bufleft = 0;
buf[252] = '.';
buf[253] = '.';
buf[254] = '.';
buf[255] = '\0';
}
else
{
bufleft -= r;
ptr += r;
}
}
}
if ( num_node_edges ) lwfree(node_edges);
}
}
if ( commonnode == -1 )
{
_lwt_release_edges(edges, nedges);
if ( ptr != buf )
{
lwerror("SQL/MM Spatial exception - other edges connected (%s)",
buf);
}
else
{
lwerror("SQL/MM Spatial exception - non-connected edges");
}
return -1;
}
if ( ! lwt_be_checkTopoGeomRemNode(topo, commonnode,
eid1, eid2 ) )
{
_lwt_release_edges(edges, nedges);
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* Construct the geometry of the new edge */
switch (caseno)
{
case 1: /* e1.end = e2.start */
pa = ptarray_clone_deep(e1->geom->points);
//pa = ptarray_merge(pa, e2->geom->points);
ptarray_append_ptarray(pa, e2->geom->points, 0);
newedge.start_node = e1->start_node;
newedge.end_node = e2->end_node;
newedge.next_left = e2->next_left;
newedge.next_right = e1->next_right;
e1freenode = 1;
e2freenode = -1;
e2sign = 1;
break;
case 2: /* e1.end = e2.end */
{
POINTARRAY *pa2;
pa2 = ptarray_clone_deep(e2->geom->points);
ptarray_reverse_in_place(pa2);
pa = ptarray_clone_deep(e1->geom->points);
//pa = ptarray_merge(e1->geom->points, pa);
ptarray_append_ptarray(pa, pa2, 0);
ptarray_free(pa2);
newedge.start_node = e1->start_node;
newedge.end_node = e2->start_node;
newedge.next_left = e2->next_right;
newedge.next_right = e1->next_right;
e1freenode = 1;
e2freenode = 1;
e2sign = -1;
break;
}
case 3: /* e1.start = e2.start */
pa = ptarray_clone_deep(e2->geom->points);
ptarray_reverse_in_place(pa);
//pa = ptarray_merge(pa, e1->geom->points);
ptarray_append_ptarray(pa, e1->geom->points, 0);
newedge.end_node = e1->end_node;
newedge.start_node = e2->end_node;
newedge.next_left = e1->next_left;
newedge.next_right = e2->next_left;
e1freenode = -1;
e2freenode = -1;
e2sign = -1;
break;
case 4: /* e1.start = e2.end */
pa = ptarray_clone_deep(e2->geom->points);
//pa = ptarray_merge(pa, e1->geom->points);
ptarray_append_ptarray(pa, e1->geom->points, 0);
newedge.end_node = e1->end_node;
newedge.start_node = e2->start_node;
newedge.next_left = e1->next_left;
newedge.next_right = e2->next_right;
e1freenode = -1;
e2freenode = 1;
e2sign = 1;
break;
default:
pa = NULL;
e1freenode = 0;
e2freenode = 0;
e2sign = 0;
_lwt_release_edges(edges, nedges);
lwerror("Coding error: caseno=%d should never happen", caseno);
return -1;
break;
}
newedge.geom = lwline_construct(topo->srid, NULL, pa);
if ( modEdge )
{
/* Update data of the first edge */
newedge.edge_id = eid1;
int result = lwt_be_updateEdgesById(topo,
&newedge,
1,
LWT_COL_EDGE_NEXT_LEFT | LWT_COL_EDGE_NEXT_RIGHT | LWT_COL_EDGE_START_NODE |
LWT_COL_EDGE_END_NODE | LWT_COL_EDGE_GEOM);
if (result == -1)
{
lwline_free(newedge.geom);
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if (result != 1)
{
lwline_free(newedge.geom);
if ( edges ) _lwt_release_edges(edges, nedges);
lwerror("Unexpected error: %d edges updated when expecting 1", i);
return -1;
}
}
else
{
/* Add new edge */
newedge.edge_id = -1;
newedge.face_left = e1->face_left;
newedge.face_right = e1->face_right;
int result = lwt_be_insertEdges(topo, &newedge, 1);
if (result == -1)
{
lwline_free(newedge.geom);
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if (result == 0)
{
lwline_free(newedge.geom);
_lwt_release_edges(edges, nedges);
lwerror("Insertion of split edge failed (no reason)");
return -1;
}
}
lwline_free(newedge.geom);
/*
-- Update next_left_edge/next_right_edge for
-- any edge having them still pointing at the edge being removed
-- (eid2 only when modEdge, or both otherwise)
--
-- NOTE:
-- e#freenode is 1 when edge# end node was the common node
-- and -1 otherwise. This gives the sign of possibly found references
-- to its "free" (non connected to other edge) endnode.
-- e2sign is -1 if edge1 direction is opposite to edge2 direction,
-- or 1 otherwise.
--
*/
/* update edges connected to e2's boundary from their end node */
seledge.next_left = e2freenode * eid2;
updedge.next_left = e2freenode * newedge.edge_id * e2sign;
int result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_LEFT, &updedge, LWT_COL_EDGE_NEXT_LEFT, NULL, 0);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* update edges connected to e2's boundary from their start node */
seledge.next_right = e2freenode * eid2;
updedge.next_right = e2freenode * newedge.edge_id * e2sign;
result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_RIGHT, &updedge, LWT_COL_EDGE_NEXT_RIGHT, NULL, 0);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ! modEdge )
{
/* update edges connected to e1's boundary from their end node */
seledge.next_left = e1freenode * eid1;
updedge.next_left = e1freenode * newedge.edge_id;
result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_LEFT, &updedge, LWT_COL_EDGE_NEXT_LEFT, NULL, 0);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/* update edges connected to e1's boundary from their start node */
seledge.next_right = e1freenode * eid1;
updedge.next_right = e1freenode * newedge.edge_id;
result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_RIGHT, &updedge, LWT_COL_EDGE_NEXT_RIGHT, NULL, 0);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
/* delete the edges (only second on modEdge or both) */
result = lwt_be_deleteEdges(topo, e2, LWT_COL_EDGE_EDGE_ID);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ! modEdge ) {
i = lwt_be_deleteEdges(topo, e1, LWT_COL_EDGE_EDGE_ID);
if (result == -1)
{
_lwt_release_edges(edges, nedges);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
}
_lwt_release_edges(edges, nedges);
/* delete the common node */
i = lwt_be_deleteNodesById( topo, &commonnode, 1 );
if (result == -1)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
/*
--
-- NOT IN THE SPECS:
-- Drop composition rows involving second
-- edge, as the first edge took its space,
-- and all affected TopoGeom have been previously checked
-- for being composed by both edges.
*/
if ( ! lwt_be_updateTopoGeomEdgeHeal(topo,
eid1, eid2, newedge.edge_id) )
{
lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
return modEdge ? commonnode : newedge.edge_id;
}
LWT_ELEMID
lwt_ModEdgeHeal( LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2 )
{
return _lwt_HealEdges( topo, e1, e2, 1 );
}
LWT_ELEMID
lwt_NewEdgeHeal( LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2 )
{
return _lwt_HealEdges( topo, e1, e2, 0 );
}
LWT_ELEMID
lwt_GetNodeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol)
{
LWT_ISO_NODE *elem;
uint64_t num;
int flds = LWT_COL_NODE_NODE_ID|LWT_COL_NODE_GEOM; /* geom not needed */
LWT_ELEMID id = 0;
POINT2D qp; /* query point */
if ( ! getPoint2d_p(pt->point, 0, &qp) )
{
lwerror("Empty query point");
return -1;
}
elem = lwt_be_getNodeWithinDistance2D(topo, pt, tol, &num, flds, 0);
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
else if ( num )
{
if ( num > 1 )
{
_lwt_release_nodes(elem, num);
lwerror("Two or more nodes found");
return -1;
}
id = elem[0].node_id;
_lwt_release_nodes(elem, num);
}
return id;
}
LWT_ELEMID
lwt_GetEdgeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol)
{
LWT_ISO_EDGE *elem;
uint64_t num, i;
int flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM; /* GEOM is not needed */
LWT_ELEMID id = 0;
LWGEOM *qp = lwpoint_as_lwgeom(pt); /* query point */
if ( lwgeom_is_empty(qp) )
{
lwerror("Empty query point");
return -1;
}
elem = lwt_be_getEdgeWithinDistance2D(topo, pt, tol, &num, flds, 0);
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for (i=0; i<num;++i)
{
LWT_ISO_EDGE *e = &(elem[i]);
#if 0
LWGEOM* geom;
double dist;
if ( ! e->geom )
{
_lwt_release_edges(elem, num);
lwnotice("Corrupted topology: edge %" LWTFMT_ELEMID
" has null geometry", e->edge_id);
continue;
}
/* Should we check for intersection not being on an endpoint
* as documented ? */
geom = lwline_as_lwgeom(e->geom);
dist = lwgeom_mindistance2d_tolerance(geom, qp, tol);
if ( dist > tol ) continue;
#endif
if ( id )
{
_lwt_release_edges(elem, num);
lwerror("Two or more edges found");
return -1;
}
else id = e->edge_id;
}
if ( num ) _lwt_release_edges(elem, num);
return id;
}
LWT_ELEMID
lwt_GetFaceByPoint(LWT_TOPOLOGY *topo, const LWPOINT *pt, double tol)
{
LWT_ELEMID id = 0;
LWT_ISO_EDGE *elem;
uint64_t num, i;
int flds = LWT_COL_EDGE_EDGE_ID |
LWT_COL_EDGE_GEOM |
LWT_COL_EDGE_FACE_LEFT |
LWT_COL_EDGE_FACE_RIGHT;
LWGEOM *qp = lwpoint_as_lwgeom(pt);
id = lwt_GetFaceContainingPoint(topo, pt);
if ( id == -1 ) {
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( id > 0 )
{
return id;
}
if ( tol == 0 )
{
return id;
}
LWDEBUG(1, "No face properly contains query point,"
" looking for edges");
/* Not in a face, may be in universe or on edge, let's check
* for distance */
/* NOTE: we never pass a tolerance of 0 to avoid ever using
* ST_Within, which doesn't include endpoints matches */
elem = lwt_be_getEdgeWithinDistance2D(topo, pt, tol?tol:1e-5, &num, flds, 0);
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
for (i=0; i<num; ++i)
{
LWT_ISO_EDGE *e = &(elem[i]);
LWT_ELEMID eface = 0;
LWGEOM* geom;
double dist;
if ( ! e->geom )
{
_lwt_release_edges(elem, num);
lwnotice("Corrupted topology: edge %" LWTFMT_ELEMID
" has null geometry", e->edge_id);
continue;
}
/* don't consider dangling edges */
if ( e->face_left == e->face_right )
{
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID
" is dangling, won't consider it", e->edge_id);
continue;
}
geom = lwline_as_lwgeom(e->geom);
dist = lwgeom_mindistance2d_tolerance(geom, qp, tol);
LWDEBUGF(1, "Distance from edge %" LWTFMT_ELEMID
" is %g (tol=%g)", e->edge_id, dist, tol);
/* we won't consider edges too far */
if ( dist > tol ) continue;
if ( e->face_left == 0 ) {
eface = e->face_right;
}
else if ( e->face_right == 0 ) {
eface = e->face_left;
}
else {
_lwt_release_edges(elem, num);
lwerror("Two or more faces found");
return -1;
}
if ( id && id != eface )
{
_lwt_release_edges(elem, num);
lwerror("Two or more faces found"
#if 0 /* debugging */
" (%" LWTFMT_ELEMID
" and %" LWTFMT_ELEMID ")", id, eface
#endif
);
return -1;
}
else id = eface;
}
if ( num ) _lwt_release_edges(elem, num);
return id;
}
/* Return the smallest delta that can perturbate
* the given value */
static inline double
_lwt_minToleranceDouble( double d )
{
double ret = 3.6 * pow(10, - ( 15 - log10(d?d:1.0) ) );
return ret;
}
/* Return the smallest delta that can perturbate
* the given point
static inline double
_lwt_minTolerancePoint2d( const POINT2D* p )
{
double max = FP_ABS(p->x);
if ( max < FP_ABS(p->y) ) max = FP_ABS(p->y);
return _lwt_minToleranceDouble(max);
}
*/
/* Return the smallest delta that can perturbate
* the maximum absolute value of a geometry ordinate
*/
static double
_lwt_minTolerance( LWGEOM *g )
{
const GBOX* gbox;
double max;
double ret;
gbox = lwgeom_get_bbox(g);
if ( ! gbox ) return 0; /* empty */
max = FP_ABS(gbox->xmin);
if ( max < FP_ABS(gbox->xmax) ) max = FP_ABS(gbox->xmax);
if ( max < FP_ABS(gbox->ymin) ) max = FP_ABS(gbox->ymin);
if ( max < FP_ABS(gbox->ymax) ) max = FP_ABS(gbox->ymax);
ret = _lwt_minToleranceDouble(max);
return ret;
}
#define _LWT_MINTOLERANCE( topo, geom ) ( \
topo->precision ? topo->precision : _lwt_minTolerance(geom) )
typedef struct scored_pointer_t {
void *ptr;
double score;
} scored_pointer;
static int
compare_scored_pointer(const void *si1, const void *si2)
{
double a = ((scored_pointer *)si1)->score;
double b = ((scored_pointer *)si2)->score;
if ( a < b )
return -1;
else if ( a > b )
return 1;
else
return 0;
}
/*
* @param findFace if non-zero the code will determine which face
* contains the given point (unless it is known to be NOT
* isolated)
* @param moved if not-null will be set to 0 if the point was added
* w/out any snapping or 1 otherwise.
*/
static LWT_ELEMID
_lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int
findFace, int *moved)
{
uint64_t num, i;
double mindist = FLT_MAX;
LWT_ISO_NODE *nodes, *nodes2;
LWT_ISO_EDGE *edges, *edges2;
LWGEOM *pt = lwpoint_as_lwgeom(point);
int flds;
LWT_ELEMID id = 0;
scored_pointer *sorted;
/* Get tolerance, if 0 was given */
if (!tol)
tol = _LWT_MINTOLERANCE(topo, pt);
LWDEBUGG(1, pt, "Adding point");
/*
-- 1. Check if any existing node is closer than the given precision
-- and if so pick the closest
TODO: use WithinBox2D
*/
flds = LWT_COL_NODE_NODE_ID | LWT_COL_NODE_GEOM;
nodes = lwt_be_getNodeWithinDistance2D(topo, point, tol, &num, flds, 0);
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( num )
{
LWDEBUGF(1, "New point is within %.15g units of %d nodes", tol, num);
/* Order by distance if there are more than a single return */
if ( num > 1 )
{{
sorted= lwalloc(sizeof(scored_pointer)*num);
for (i=0; i<num; ++i)
{
sorted[i].ptr = nodes+i;
sorted[i].score = lwgeom_mindistance2d(lwpoint_as_lwgeom(nodes[i].geom), pt);
LWDEBUGF(1, "Node %" LWTFMT_ELEMID " distance: %.15g",
((LWT_ISO_NODE*)(sorted[i].ptr))->node_id, sorted[i].score);
}
qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer);
nodes2 = lwalloc(sizeof(LWT_ISO_NODE)*num);
for (i=0; i<num; ++i)
{
nodes2[i] = *((LWT_ISO_NODE*)sorted[i].ptr);
}
lwfree(sorted);
lwfree(nodes);
nodes = nodes2;
}}
for ( i=0; i<num; ++i )
{
LWT_ISO_NODE *n = &(nodes[i]);
LWGEOM *g = lwpoint_as_lwgeom(n->geom);
double dist = lwgeom_mindistance2d(g, pt);
/* TODO: move this check in the previous sort scan ... */
/* must be closer than tolerated, unless distance is zero */
if ( dist && dist >= tol ) continue;
if ( ! id || dist < mindist )
{
id = n->node_id;
mindist = dist;
}
}
if ( id )
{
/* found an existing node */
if ( nodes ) _lwt_release_nodes(nodes, num);
if ( moved ) *moved = mindist == 0 ? 0 : 1;
return id;
}
}
initGEOS(lwnotice, lwgeom_geos_error);
/*
-- 2. Check if any existing edge falls within tolerance
-- and if so split it by a point projected on it
TODO: use WithinBox2D
*/
flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM;
edges = lwt_be_getEdgeWithinDistance2D(topo, point, tol, &num, flds, 0);
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( num )
{
LWDEBUGF(1, "New point is within %.15g units of %d edges", tol, num);
/* Order by distance if there are more than a single return */
if ( num > 1 )
{{
int j;
sorted = lwalloc(sizeof(scored_pointer)*num);
for (i=0; i<num; ++i)
{
sorted[i].ptr = edges+i;
sorted[i].score = lwgeom_mindistance2d(lwline_as_lwgeom(edges[i].geom), pt);
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " distance: %.15g",
((LWT_ISO_EDGE*)(sorted[i].ptr))->edge_id, sorted[i].score);
}
qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer);
edges2 = lwalloc(sizeof(LWT_ISO_EDGE)*num);
for (j=0, i=0; i<num; ++i)
{
if ( sorted[i].score == sorted[0].score )
{
edges2[j++] = *((LWT_ISO_EDGE*)sorted[i].ptr);
}
else
{
lwline_free(((LWT_ISO_EDGE*)sorted[i].ptr)->geom);
}
}
num = j;
lwfree(sorted);
lwfree(edges);
edges = edges2;
}}
for (i=0; i<num; ++i)
{
/* The point is on or near an edge, split the edge */
LWT_ISO_EDGE *e = &(edges[i]);
LWGEOM *g = lwline_as_lwgeom(e->geom);
LWGEOM *prj;
int contains;
LWT_ELEMID edge_id = e->edge_id;
LWDEBUGF(1, "Splitting edge %" LWTFMT_ELEMID, edge_id);
/* project point to line, split edge by point */
prj = lwgeom_closest_point(g, pt);
if ( moved ) *moved = lwgeom_same(prj,pt) ? 0 : 1;
if ( lwgeom_has_z(pt) )
{{
/*
-- This is a workaround for ClosestPoint lack of Z support:
-- http://trac.osgeo.org/postgis/ticket/2033
*/
LWGEOM *tmp;
double z;
POINT4D p4d;
LWPOINT *prjpt;
/* add Z to "prj" */
tmp = lwgeom_force_3dz(prj, 0);
prjpt = lwgeom_as_lwpoint(tmp);
getPoint4d_p(point->point, 0, &p4d);
z = p4d.z;
getPoint4d_p(prjpt->point, 0, &p4d);
p4d.z = z;
ptarray_set_point4d(prjpt->point, 0, &p4d);
lwgeom_free(prj);
prj = tmp;
}}
const POINT2D *pt = getPoint2d_cp(lwgeom_as_lwpoint(prj)->point, 0);
contains = ptarray_contains_point_partial(e->geom->points, pt, 0, NULL) == LW_BOUNDARY;
if ( ! contains )
{{
double snaptol;
LWGEOM *snapedge;
LWLINE *snapline;
POINT4D p1, p2;
LWDEBUGF(1, "Edge %" LWTFMT_ELEMID
" does not contain projected point to it",
edge_id);
/* In order to reduce the robustness issues, we'll pick
* an edge that contains the projected point, if possible */
if ( i+1 < num )
{
LWDEBUG(1, "But there's another to check");
lwgeom_free(prj);
continue;
}
/*
-- The tolerance must be big enough for snapping to happen
-- and small enough to snap only to the projected point.
-- Unfortunately ST_Distance returns 0 because it also uses
-- a projected point internally, so we need another way.
*/
snaptol = _lwt_minTolerance(prj);
snapedge = _lwt_toposnap(g, prj, snaptol);
snapline = lwgeom_as_lwline(snapedge);
LWDEBUGF(1, "Edge snapped with tolerance %g", snaptol);
/* TODO: check if snapping did anything ? */
#if POSTGIS_DEBUG_LEVEL > 0
{
size_t sz;
char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz);
char *wkt2 = lwgeom_to_wkt(snapedge, WKT_EXTENDED, 15, &sz);
LWDEBUGF(1, "Edge %s snapped became %s", wkt1, wkt2);
lwfree(wkt1);
lwfree(wkt2);
}
#endif
/*
-- Snapping currently snaps the first point below tolerance
-- so may possibly move first point. See ticket #1631
*/
getPoint4d_p(e->geom->points, 0, &p1);
getPoint4d_p(snapline->points, 0, &p2);
LWDEBUGF(1, "Edge first point is %g %g, "
"snapline first point is %g %g",
p1.x, p1.y, p2.x, p2.y);
if ( p1.x != p2.x || p1.y != p2.y )
{
LWDEBUG(1, "Snapping moved first point, re-adding it");
if ( LW_SUCCESS != ptarray_insert_point(snapline->points, &p1, 0) )
{
lwgeom_free(prj);
lwgeom_free(snapedge);
_lwt_release_edges(edges, num);
lwerror("GEOS exception on Contains: %s", lwgeom_geos_errmsg);
return -1;
}
#if POSTGIS_DEBUG_LEVEL > 0
{
size_t sz;
char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz);
LWDEBUGF(1, "Tweaked snapline became %s", wkt1);
lwfree(wkt1);
}
#endif
}
#if POSTGIS_DEBUG_LEVEL > 0
else {
LWDEBUG(1, "Snapping did not move first point");
}
#endif
if ( -1 == lwt_ChangeEdgeGeom( topo, edge_id, snapline ) )
{
/* TODO: should have invoked lwerror already, leaking memory */
lwgeom_free(prj);
lwgeom_free(snapedge);
_lwt_release_edges(edges, num);
lwerror("lwt_ChangeEdgeGeom failed");
return -1;
}
lwgeom_free(snapedge);
}}
#if POSTGIS_DEBUG_LEVEL > 0
else
{{
size_t sz;
char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz);
char *wkt2 = lwgeom_to_wkt(prj, WKT_EXTENDED, 15, &sz);
LWDEBUGF(1, "Edge %s contains projected point %s", wkt1, wkt2);
lwfree(wkt1);
lwfree(wkt2);
}}
#endif
/* TODO: pass 1 as last argument (skipChecks) ? */
id = lwt_ModEdgeSplit( topo, edge_id, lwgeom_as_lwpoint(prj), 0 );
if ( -1 == id )
{
/* TODO: should have invoked lwerror already, leaking memory */
lwgeom_free(prj);
_lwt_release_edges(edges, num);
lwerror("lwt_ModEdgeSplit failed");
return -1;
}
lwgeom_free(prj);
/*
* TODO: decimate the two new edges with the given tolerance ?
*
* the edge identifiers to decimate would be: edge_id and "id"
* The problem here is that decimation of existing edges
* may introduce intersections or topological inconsistencies,
* for example:
*
* - A node may end up falling on the other side of the edge
* - The decimated edge might intersect another existing edge
*
*/
break; /* we only want to snap a single edge */
}
_lwt_release_edges(edges, num);
}
else
{
/* The point is isolated, add it as such */
/* TODO: pass 1 as last argument (skipChecks) ? */
id = _lwt_AddIsoNode(topo, -1, point, 0, findFace);
if ( moved ) *moved = 0;
if ( -1 == id )
{
/* should have invoked lwerror already, leaking memory */
lwerror("lwt_AddIsoNode failed");
return -1;
}
}
return id;
}
LWT_ELEMID
lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol)
{
return _lwt_AddPoint(topo, point, tol, 1, NULL);
}
/* Return identifier of an equal edge, 0 if none or -1 on error
* (and lwerror gets called on error)
*
* If an equal edge is found, specify, in "forward" variable whether
* the edge is also equal direction-wise
*
*/
static LWT_ELEMID
_lwt_GetEqualEdge( LWT_TOPOLOGY *topo, LWLINE *edge, int *forward )
{
LWT_ELEMID id;
LWT_ISO_EDGE *edges;
uint64_t num, i;
const GBOX *qbox = lwgeom_get_bbox( lwline_as_lwgeom(edge) );
GEOSGeometry *edgeg;
const int flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM;
edges = lwt_be_getEdgeWithinBox2D( topo, qbox, &num, flds, 0 );
if (num == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( num )
{
initGEOS(lwnotice, lwgeom_geos_error);
edgeg = LWGEOM2GEOS( lwline_as_lwgeom(edge), 0 );
if ( ! edgeg )
{
_lwt_release_edges(edges, num);
lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
return -1;
}
for (i=0; i<num; ++i)
{
LWT_ISO_EDGE *e = &(edges[i]);
LWGEOM *g = lwline_as_lwgeom(e->geom);
GEOSGeometry *gg;
int equals;
gg = LWGEOM2GEOS( g, 0 );
if ( ! gg )
{
GEOSGeom_destroy(edgeg);
_lwt_release_edges(edges, num);
lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
return -1;
}
equals = GEOSEquals(gg, edgeg);
GEOSGeom_destroy(gg);
if ( equals == 2 )
{
GEOSGeom_destroy(edgeg);
_lwt_release_edges(edges, num);
lwerror("GEOSEquals exception: %s", lwgeom_geos_errmsg);
return -1;
}
if ( equals )
{
id = e->edge_id;
/* Check if direction also matches */
if ( forward )
{
/* If input line is closed, we use winding order */
if ( lwline_is_closed(edge) )
{
if ( ptarray_isccw(edge->points) == ptarray_isccw(e->geom->points) )
{
*forward = 1;
}
else
{
*forward = 0;
}
}
else
{
/* Input line is not closed, checking fist point is enough */
if (
memcmp(
getPoint_internal(edge->points, 0),
getPoint_internal(e->geom->points, 0),
sizeof(POINT2D)
) == 0
)
{
*forward = 1;
}
else
{
*forward = 0;
}
}
}
GEOSGeom_destroy(edgeg);
_lwt_release_edges(edges, num);
return id;
}
}
GEOSGeom_destroy(edgeg);
_lwt_release_edges(edges, num);
}
return 0;
}
/*
* Add a pre-noded pre-split line edge. Used by lwt_AddLine
* Return edge id, 0 if none added (empty edge), -1 on error
*
* @param handleFaceSplit if non-zero the code will check
* if the newly added edge would split a face and if so
* would create new faces accordingly. Otherwise it will
* set left_face and right_face to null (-1)
*
* @param forward output parameter, will be populated if
* a pre-existing edge was found in the topology,
* in which case a value of 1 means the incoming
* line will have the same direction of the edge,
* and 0 that the incomine line has opposite direction
*/
static LWT_ELEMID
_lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
int handleFaceSplit, int *forward )
{
LWCOLLECTION *col;
LWPOINT *start_point, *end_point;
LWGEOM *tmp = 0, *tmp2;
LWT_ISO_NODE *node;
LWT_ELEMID nid[2]; /* start_node, end_node */
LWT_ELEMID id; /* edge id */
POINT4D p4d;
uint64_t nn, i;
int moved=0, mm;
LWDEBUGG(1, lwline_as_lwgeom(edge), "_lwtAddLineEdge");
LWDEBUGF(1, "_lwtAddLineEdge with tolerance %g", tol);
start_point = lwline_get_lwpoint(edge, 0);
if ( ! start_point )
{
lwnotice("Empty component of noded line");
return 0; /* must be empty */
}
nid[0] = _lwt_AddPoint( topo, start_point,
_lwt_minTolerance(lwpoint_as_lwgeom(start_point)),
handleFaceSplit, &mm );
lwpoint_free(start_point); /* too late if lwt_AddPoint calls lwerror */
if ( nid[0] == -1 ) return -1; /* lwerror should have been called */
moved += mm;
end_point = lwline_get_lwpoint(edge, edge->points->npoints-1);
if ( ! end_point )
{
lwerror("could not get last point of line "
"after successfully getting first point !?");
return -1;
}
nid[1] = _lwt_AddPoint( topo, end_point,
_lwt_minTolerance(lwpoint_as_lwgeom(end_point)),
handleFaceSplit, &mm );
moved += mm;
lwpoint_free(end_point); /* too late if lwt_AddPoint calls lwerror */
if ( nid[1] == -1 ) return -1; /* lwerror should have been called */
/*
-- Added endpoints may have drifted due to tolerance, so
-- we need to re-snap the edge to the new nodes before adding it
*/
if ( moved )
{
nn = nid[0] == nid[1] ? 1 : 2;
node = lwt_be_getNodeById( topo, nid, &nn,
LWT_COL_NODE_NODE_ID|LWT_COL_NODE_GEOM );
if (nn == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
start_point = NULL; end_point = NULL;
for (i=0; i<nn; ++i)
{
if ( node[i].node_id == nid[0] ) start_point = node[i].geom;
if ( node[i].node_id == nid[1] ) end_point = node[i].geom;
}
if ( ! start_point || ! end_point )
{
if ( nn ) _lwt_release_nodes(node, nn);
lwerror("Could not find just-added nodes % " LWTFMT_ELEMID
" and %" LWTFMT_ELEMID, nid[0], nid[1]);
return -1;
}
/* snap */
getPoint4d_p( start_point->point, 0, &p4d );
lwline_setPoint4d(edge, 0, &p4d);
getPoint4d_p( end_point->point, 0, &p4d );
lwline_setPoint4d(edge, edge->points->npoints-1, &p4d);
if ( nn ) _lwt_release_nodes(node, nn);
/* make valid, after snap (to handle collapses) */
tmp = lwgeom_make_valid(lwline_as_lwgeom(edge));
col = lwgeom_as_lwcollection(tmp);
if ( col )
{{
LWCOLLECTION *colex = lwcollection_extract(col, LINETYPE);
/* Check if the so-snapped edge collapsed (see #1650) */
if ( colex->ngeoms == 0 )
{
lwcollection_free(colex);
lwgeom_free(tmp);
LWDEBUG(1, "Made-valid snapped edge collapsed");
return 0;
}
tmp2 = lwgeom_clone_deep(colex->geoms[0]);
lwgeom_free(tmp);
tmp = tmp2;
edge = lwgeom_as_lwline(tmp);
lwcollection_free(colex);
if ( ! edge )
{
/* should never happen */
lwerror("lwcollection_extract(LINETYPE) returned a non-line?");
return -1;
}
}}
else
{
edge = lwgeom_as_lwline(tmp);
if ( ! edge )
{
LWDEBUGF(1, "Made-valid snapped edge collapsed to %s",
lwtype_name(lwgeom_get_type(tmp)));
lwgeom_free(tmp);
return 0;
}
}
}
/* check if the so-snapped edge _now_ exists */
id = _lwt_GetEqualEdge ( topo, edge, forward );
LWDEBUGF(1, "_lwt_GetEqualEdge returned %" LWTFMT_ELEMID, id);
if ( id == -1 )
{
if ( tmp ) lwgeom_free(tmp); /* probably too late, due to internal lwerror */
return -1;
}
if ( id )
{
if ( tmp ) lwgeom_free(tmp); /* possibly takes "edge" down with it */
return id;
}
/* No previously existing edge was found, we'll add one */
/* Remove consecutive vertices below given tolerance
* on edge addition */
if ( tol )
{{
tmp2 = lwline_remove_repeated_points(edge, tol);
LWDEBUGG(1, tmp2, "Repeated-point removed");
edge = lwgeom_as_lwline(tmp2);
if ( tmp ) lwgeom_free(tmp);
tmp = tmp2;
/* check if the so-decimated edge collapsed to single-point */
if ( nid[0] == nid[1] && edge->points->npoints == 2 )
{
lwgeom_free(tmp);
LWDEBUG(1, "Repeated-point removed edge collapsed");
return 0;
}
/* check if the so-decimated edge _now_ exists */
id = _lwt_GetEqualEdge ( topo, edge, forward );
LWDEBUGF(1, "_lwt_GetEqualEdge returned %" LWTFMT_ELEMID, id);
if ( id == -1 )
{
lwgeom_free(tmp); /* probably too late, due to internal lwerror */
return -1;
}
if ( id )
{
lwgeom_free(tmp); /* takes "edge" down with it */
return id;
}
}}
/* TODO: skip checks ? */
id = _lwt_AddEdge( topo, nid[0], nid[1], edge, 0, handleFaceSplit ? 1 : -1 );
LWDEBUGF(1, "lwt_AddEdgeModFace returned %" LWTFMT_ELEMID, id);
if ( id == -1 )
{
lwgeom_free(tmp); /* probably too late, due to internal lwerror */
return -1;
}
lwgeom_free(tmp); /* possibly takes "edge" down with it */
*forward = 1;
return id;
}
/* Simulate split-loop as it was implemented in pl/pgsql version
* of TopoGeo_addLinestring */
static LWGEOM *
_lwt_split_by_nodes(const LWGEOM *g, const LWGEOM *nodes)
{
LWCOLLECTION *col = lwgeom_as_lwcollection(nodes);
uint32_t i;
LWGEOM *bg;
bg = lwgeom_clone_deep(g);
if ( ! col->ngeoms ) return bg;
for (i=0; i<col->ngeoms; ++i)
{
LWGEOM *g2;
g2 = lwgeom_split(bg, col->geoms[i]);
lwgeom_free(bg);
bg = g2;
}
bg->srid = nodes->srid;
return bg;
}
static LWT_ELEMID*
_lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
int handleFaceSplit)
{
LWGEOM *geomsbuf[1];
LWGEOM **geoms;
uint32_t ngeoms;
LWGEOM *noded, *tmp;
LWCOLLECTION *col;
LWT_ELEMID *ids;
LWT_ISO_EDGE *edges;
LWT_ISO_NODE *nodes;
uint64_t num, numedges = 0, numnodes = 0;
uint64_t i;
GBOX qbox;
int forward;
int input_was_closed = 0;
POINT4D originalStartPoint;
if ( lwline_is_closed(line) )
{
input_was_closed = 1;
getPoint4d_p( line->points, 0, &originalStartPoint);
LWDEBUGF(1, "Input line is closed, original point is %g,%g", originalStartPoint.x, originalStartPoint.y);
}
*nedges = -1; /* error condition, by default */
/* Get tolerance, if 0 was given */
if ( ! tol ) tol = _LWT_MINTOLERANCE( topo, (LWGEOM*)line );
LWDEBUGF(1, "Working tolerance:%.15g", tol);
LWDEBUGF(1, "Input line has srid=%d", line->srid);
/* Remove consecutive vertices below given tolerance upfront */
if ( tol )
{{
LWLINE *clean = lwgeom_as_lwline(lwline_remove_repeated_points(line, tol));
tmp = lwline_as_lwgeom(clean); /* NOTE: might collapse to non-simple */
LWDEBUGG(1, tmp, "Repeated-point removed");
}} else tmp=(LWGEOM*)line;
/* 1. Self-node */
noded = lwgeom_node((LWGEOM*)tmp);
if ( tmp != (LWGEOM*)line ) lwgeom_free(tmp);
if ( ! noded ) return NULL; /* should have called lwerror already */
LWDEBUGG(1, noded, "Noded");
qbox = *lwgeom_get_bbox( lwline_as_lwgeom(line) );
LWDEBUGF(1, "Line BOX is %.15g %.15g, %.15g %.15g", qbox.xmin, qbox.ymin,
qbox.xmax, qbox.ymax);
gbox_expand(&qbox, tol);
LWDEBUGF(1, "BOX expanded by %g is %.15g %.15g, %.15g %.15g",
tol, qbox.xmin, qbox.ymin, qbox.xmax, qbox.ymax);
LWGEOM **nearby = 0;
int nearbyindex = 0;
int nearbycount = 0;
/* 2.0. Find edges falling within tol distance */
edges = lwt_be_getEdgeWithinBox2D( topo, &qbox, &numedges, LWT_COL_EDGE_ALL, 0 );
if (numedges == UINT64_MAX)
{
lwgeom_free(noded);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
LWDEBUGF(1, "Line has %d points, its bbox intersects %d edges bboxes",
line->points->npoints, numedges);
if ( numedges )
{{
/* collect those whose distance from us is < tol */
nearbycount += numedges;
nearby = lwalloc(numedges * sizeof(LWGEOM *));
for (i=0; i<numedges; ++i)
{
LW_ON_INTERRUPT(return NULL);
LWT_ISO_EDGE *e = &(edges[i]);
LWGEOM *g = lwline_as_lwgeom(e->geom);
LWDEBUGF(2, "Computing distance from edge %d having %d points", i, e->geom->points->npoints);
double dist = lwgeom_mindistance2d(g, noded);
/* must be closer than tolerated, unless distance is zero */
if ( dist && dist >= tol ) continue;
nearby[nearbyindex++] = g;
}
LWDEBUGF(1, "Found %d edges closer than tolerance (%g)", nearbyindex, tol);
}}
int nearbyedgecount = nearbyindex;
/* 2.1. Find isolated nodes falling within tol distance
*
* TODO: add backend-interface support for only getting isolated nodes
*/
nodes = lwt_be_getNodeWithinBox2D( topo, &qbox, &numnodes, LWT_COL_NODE_ALL, 0 );
if (numnodes == UINT64_MAX)
{
lwgeom_free(noded);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
LWDEBUGF(1, "Line bbox intersects %d nodes bboxes", numnodes);
if ( numnodes )
{{
/* collect those whose distance from us is < tol */
nearbycount = nearbyedgecount + numnodes;
nearby = nearby ?
lwrealloc(nearby, nearbycount * sizeof(LWGEOM *))
:
lwalloc(nearbycount * sizeof(LWGEOM *))
;
int nn = 0;
for (i=0; i<numnodes; ++i)
{
LWT_ISO_NODE *n = &(nodes[i]);
if ( n->containing_face == -1 ) continue; /* skip not-isolated nodes */
LWGEOM *g = lwpoint_as_lwgeom(n->geom);
double dist = lwgeom_mindistance2d(g, noded);
/* must be closer than tolerated, unless distance is zero */
if ( dist && dist >= tol )
{
LWDEBUGF(1, "Node %d is %g units away, we tolerate only %g", n->node_id, dist, tol);
continue;
}
nearby[nearbyindex++] = g;
++nn;
}
LWDEBUGF(1, "Found %d isolated nodes closer than tolerance (%g)", nn, tol);
}}
int nearbynodecount = nearbyindex - nearbyedgecount;
nearbycount = nearbyindex;
LWDEBUGF(1, "Number of nearby elements is %d", nearbycount);
/* 2.2. Snap to nearby elements */
if ( nearbycount )
{{
LWCOLLECTION *col;
LWGEOM *elems;
col = lwcollection_construct(COLLECTIONTYPE, topo->srid,
NULL, nearbycount, nearby);
elems = lwcollection_as_lwgeom(col);
LWDEBUGG(1, elems, "Collected nearby elements");
tmp = _lwt_toposnap(noded, elems, tol);
lwgeom_free(noded);
noded = tmp;
LWDEBUGG(1, noded, "Elements-snapped");
if ( input_was_closed )
{{
/* Recompute start point in case it moved */
LWLINE *scrolled = lwgeom_as_lwline(noded);
if (scrolled)
{
getPoint4d_p( scrolled->points, 0, &originalStartPoint);
LWDEBUGF(1, "Closed input line start point after snap %g,%g", originalStartPoint.x, originalStartPoint.y);
}
}}
/* will not release the geoms array */
lwcollection_release(col);
/*
-- re-node to account for ST_Snap introduced self-intersections
-- See http://trac.osgeo.org/postgis/ticket/1714
-- TODO: consider running UnaryUnion once after all noding
*/
tmp = lwgeom_unaryunion(noded);
lwgeom_free(noded);
noded = tmp;
LWDEBUGG(1, noded, "Unary-unioned");
}}
/* 2.3. Node with nearby edges */
if ( nearbyedgecount )
{{
LWCOLLECTION *col;
LWGEOM *iedges; /* just an alias for col */
LWGEOM *diff, *xset;
LWDEBUGF(1, "Line intersects %d edges", nearbyedgecount);
col = lwcollection_construct(COLLECTIONTYPE, topo->srid,
NULL, nearbyedgecount, nearby);
iedges = lwcollection_as_lwgeom(col);
LWDEBUGG(1, iedges, "Collected edges");
LWDEBUGF(1, "Diffing noded, with srid=%d "
"and interesecting edges, with srid=%d",
noded->srid, iedges->srid);
diff = lwgeom_difference(noded, iedges);
LWDEBUGG(1, diff, "Differenced");
LWDEBUGF(1, "Intersecting noded, with srid=%d "
"and interesecting edges, with srid=%d",
noded->srid, iedges->srid);
xset = lwgeom_intersection(noded, iedges);
LWDEBUGG(1, xset, "Intersected");
lwgeom_free(noded);
/* We linemerge here because INTERSECTION, as of GEOS 3.8,
* will result in shared segments being output as multiple
* lines rather than a single line. Example:
INTERSECTION(
'LINESTRING(0 0, 5 0, 8 0, 10 0,12 0)',
'LINESTRING(5 0, 8 0, 10 0)'
)
==
MULTILINESTRING((5 0,8 0),(8 0,10 0))
* We will re-split in a subsequent step, by splitting
* the final line with pre-existing nodes
*/
LWDEBUG(1, "Linemerging intersection");
tmp = lwgeom_linemerge(xset);
LWDEBUGG(1, tmp, "Linemerged");
lwgeom_free(xset);
xset = tmp;
/*
* Here we union the (linemerged) intersection with
* the difference (new lines)
*/
LWDEBUG(1, "Unioning difference and (linemerged) intersection");
noded = lwgeom_union(diff, xset);
LWDEBUGG(1, noded, "Diff-Xset Unioned");
lwgeom_free(xset);
lwgeom_free(diff);
/* will not release the geoms array */
lwcollection_release(col);
if ( input_was_closed )
{{
LWLINE *scrolled = lwgeom_as_lwline(noded);
if (scrolled) {
if ( lwline_is_closed(scrolled) ) {
ptarray_scroll_in_place(scrolled->points, &originalStartPoint);
}
else {
LWDEBUGG(1, lwline_as_lwgeom(scrolled), "Linemerged intersected input is not closed anymore");
}
}
else {
LWDEBUGG(1, xset, "Linemerged intersected input is not a line anymore");
}
}}
}}
/* 2.4. Split by pre-existing nodes
*
* Pre-existing nodes are isolated nodes AND endpoints
* of intersecting edges
*/
if ( nearbyedgecount )
{
nearbycount += nearbyedgecount * 2; /* make space for endpoints */
nearby = lwrealloc(nearby, nearbycount * sizeof(LWGEOM *));
for (int i=0; i<nearbyedgecount; i++)
{
LWLINE *edge = lwgeom_as_lwline(nearby[i]);
LWPOINT *startNode = lwline_get_lwpoint(edge, 0);
LWPOINT *endNode = lwline_get_lwpoint(edge, edge->points->npoints-1);
/* TODO: only add if within distance to noded AND if not duplicated */
nearby[nearbyindex++] = lwpoint_as_lwgeom(startNode);
nearbynodecount++;
nearby[nearbyindex++] = lwpoint_as_lwgeom(endNode);
nearbynodecount++;
}
}
if ( nearbynodecount )
{
col = lwcollection_construct(MULTIPOINTTYPE, topo->srid,
NULL, nearbynodecount,
nearby + nearbyedgecount);
LWGEOM *inodes = lwcollection_as_lwgeom(col);
/* TODO: use lwgeom_split of lwgeom_union ... */
tmp = _lwt_split_by_nodes(noded, inodes);
lwgeom_free(noded);
noded = tmp;
LWDEBUGG(1, noded, "Node-split");
/* will not release the geoms array */
lwcollection_release(col);
}
LWDEBUG(1, "Freeing up nearby elements");
/* TODO: free up endpoints of nearbyedges */
if ( nearby ) lwfree(nearby);
if ( nodes ) _lwt_release_nodes(nodes, numnodes);
if ( edges ) _lwt_release_edges(edges, numedges);
LWDEBUGG(1, noded, "Finally-noded");
/* 3. For each (now-noded) segment, insert an edge */
col = lwgeom_as_lwcollection(noded);
if ( col )
{
LWDEBUG(1, "Noded line was a collection");
geoms = col->geoms;
ngeoms = col->ngeoms;
}
else
{
LWDEBUG(1, "Noded line was a single geom");
geomsbuf[0] = noded;
geoms = geomsbuf;
ngeoms = 1;
}
LWDEBUGF(1, "Line was split into %d edges", ngeoms);
/* TODO: refactor to first add all nodes (re-snapping edges if
* needed) and then check all edges for existing already
* ( so to save a DB scan for each edge to be added )
*/
ids = lwalloc(sizeof(LWT_ELEMID)*ngeoms);
num = 0;
for ( i=0; i<ngeoms; ++i )
{
LWT_ELEMID id;
LWGEOM *g = geoms[i];
g->srid = noded->srid;
#if POSTGIS_DEBUG_LEVEL > 0
{
size_t sz;
char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz);
LWDEBUGF(1, "Component %d of split line is: %s", i, wkt1);
lwfree(wkt1);
}
#endif
id = _lwt_AddLineEdge( topo, lwgeom_as_lwline(g), tol, handleFaceSplit, &forward );
LWDEBUGF(1, "_lwt_AddLineEdge returned %" LWTFMT_ELEMID, id);
if ( id < 0 )
{
lwgeom_free(noded);
lwfree(ids);
return NULL;
}
if ( ! id )
{
LWDEBUGF(1, "Component %d of split line collapsed", i);
continue;
}
LWDEBUGF(1, "Component %d of split line is %s edge %" LWTFMT_ELEMID,
i, forward ? "forward" : "backward", id);
ids[num++] = forward ? id : -id; /* TODO: skip duplicates */
}
LWDEBUGG(1, noded, "Noded before free");
lwgeom_free(noded);
/* TODO: XXX remove duplicated ids if not done before */
*nedges = num;
return ids;
}
LWT_ELEMID*
lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges)
{
return _lwt_AddLine(topo, line, tol, nedges, 1);
}
LWT_ELEMID*
lwt_AddLineNoFace(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges)
{
return _lwt_AddLine(topo, line, tol, nedges, 0);
}
LWT_ELEMID*
lwt_AddPolygon(LWT_TOPOLOGY* topo, LWPOLY* poly, double tol, int* nfaces)
{
uint32_t i;
*nfaces = -1; /* error condition, by default */
int num;
LWT_ISO_FACE *faces;
uint64_t nfacesinbox;
uint64_t j;
LWT_ELEMID *ids = NULL;
GBOX qbox;
const GEOSPreparedGeometry *ppoly;
GEOSGeometry *polyg;
/* Get tolerance, if 0 was given */
if ( ! tol ) tol = _LWT_MINTOLERANCE( topo, (LWGEOM*)poly );
LWDEBUGF(1, "Working tolerance:%.15g", tol);
/* Add each ring as an edge */
for ( i=0; i<poly->nrings; ++i )
{
LWLINE *line;
POINTARRAY *pa;
LWT_ELEMID *eids;
int nedges;
pa = ptarray_clone(poly->rings[i]);
line = lwline_construct(topo->srid, NULL, pa);
eids = lwt_AddLine( topo, line, tol, &nedges );
if ( nedges < 0 ) {
/* probably too late as lwt_AddLine invoked lwerror */
lwline_free(line);
lwerror("Error adding ring %d of polygon", i);
return NULL;
}
lwline_free(line);
lwfree(eids);
}
/*
-- Find faces covered by input polygon
-- NOTE: potential snapping changed polygon edges
*/
qbox = *lwgeom_get_bbox( lwpoly_as_lwgeom(poly) );
gbox_expand(&qbox, tol);
faces = lwt_be_getFaceWithinBox2D( topo, &qbox, &nfacesinbox,
LWT_COL_FACE_ALL, 0 );
if (nfacesinbox == UINT64_MAX)
{
lwfree(ids);
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
num = 0;
if ( nfacesinbox )
{
polyg = LWGEOM2GEOS(lwpoly_as_lwgeom(poly), 0);
if ( ! polyg )
{
_lwt_release_faces(faces, nfacesinbox);
lwerror("Could not convert poly geometry to GEOS: %s", lwgeom_geos_errmsg);
return NULL;
}
ppoly = GEOSPrepare(polyg);
ids = lwalloc(sizeof(LWT_ELEMID)*nfacesinbox);
for ( j=0; j<nfacesinbox; ++j )
{
LWT_ISO_FACE *f = &(faces[j]);
LWGEOM *fg;
GEOSGeometry *fgg, *sp;
int covers;
/* check if a point on this face surface is covered by our polygon */
fg = lwt_GetFaceGeometry( topo, f->face_id );
if ( ! fg )
{
j = f->face_id; /* so we can destroy faces */
GEOSPreparedGeom_destroy(ppoly);
GEOSGeom_destroy(polyg);
lwfree(ids);
_lwt_release_faces(faces, nfacesinbox);
lwerror("Could not get geometry of face %" LWTFMT_ELEMID, j);
return NULL;
}
/* check if a point on this face's surface is covered by our polygon */
fgg = LWGEOM2GEOS(fg, 0);
lwgeom_free(fg);
if ( ! fgg )
{
GEOSPreparedGeom_destroy(ppoly);
GEOSGeom_destroy(polyg);
_lwt_release_faces(faces, nfacesinbox);
lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
return NULL;
}
sp = GEOSPointOnSurface(fgg);
GEOSGeom_destroy(fgg);
if ( ! sp )
{
GEOSPreparedGeom_destroy(ppoly);
GEOSGeom_destroy(polyg);
_lwt_release_faces(faces, nfacesinbox);
lwerror("Could not find point on face surface: %s", lwgeom_geos_errmsg);
return NULL;
}
covers = GEOSPreparedCovers( ppoly, sp );
GEOSGeom_destroy(sp);
if (covers == 2)
{
GEOSPreparedGeom_destroy(ppoly);
GEOSGeom_destroy(polyg);
_lwt_release_faces(faces, nfacesinbox);
lwerror("PreparedCovers error: %s", lwgeom_geos_errmsg);
return NULL;
}
if ( ! covers )
{
continue; /* we're not composed by this face */
}
/* TODO: avoid duplicates ? */
ids[num++] = f->face_id;
}
GEOSPreparedGeom_destroy(ppoly);
GEOSGeom_destroy(polyg);
_lwt_release_faces(faces, nfacesinbox);
}
/* possibly 0 if non face's surface point was found
* to be covered by input polygon */
*nfaces = num;
return ids;
}
/*
*---- polygonizer
*/
/* An array of pointers to EDGERING structures */
typedef struct LWT_ISO_EDGE_TABLE_T {
LWT_ISO_EDGE *edges;
int size;
} LWT_ISO_EDGE_TABLE;
static int
compare_iso_edges_by_id(const void *si1, const void *si2)
{
int a = ((LWT_ISO_EDGE *)si1)->edge_id;
int b = ((LWT_ISO_EDGE *)si2)->edge_id;
if ( a < b )
return -1;
else if ( a > b )
return 1;
else
return 0;
}
static LWT_ISO_EDGE *
_lwt_getIsoEdgeById(LWT_ISO_EDGE_TABLE *tab, LWT_ELEMID id)
{
LWT_ISO_EDGE key;
key.edge_id = id;
void *match = bsearch( &key, tab->edges, tab->size,
sizeof(LWT_ISO_EDGE),
compare_iso_edges_by_id);
return match;
}
typedef struct LWT_EDGERING_ELEM_T {
/* externally owned */
LWT_ISO_EDGE *edge;
/* 0 if false, 1 if true */
int left;
} LWT_EDGERING_ELEM;
/* A ring of edges */
typedef struct LWT_EDGERING_T {
/* Signed edge identifiers
* positive ones are walked in their direction, negative ones
* in the opposite direction */
LWT_EDGERING_ELEM **elems;
/* Number of edges in the ring */
int size;
int capacity;
/* Bounding box of the ring */
GBOX *env;
/* Bounding box of the ring in GEOS format (for STRTree) */
GEOSGeometry *genv;
} LWT_EDGERING;
#define LWT_EDGERING_INIT(a) { \
(a)->size = 0; \
(a)->capacity = 1; \
(a)->elems = lwalloc(sizeof(LWT_EDGERING_ELEM *) * (a)->capacity); \
(a)->env = NULL; \
(a)->genv = NULL; \
}
#define LWT_EDGERING_PUSH(a, r) { \
if ( (a)->size + 1 > (a)->capacity ) { \
(a)->capacity *= 2; \
(a)->elems = lwrealloc((a)->elems, sizeof(LWT_EDGERING_ELEM *) * (a)->capacity); \
} \
/* lwdebug(1, "adding elem %d (%p) of edgering %p", (a)->size, (r), (a)); */ \
(a)->elems[(a)->size++] = (r); \
}
#define LWT_EDGERING_CLEAN(a) { \
int i; for (i=0; i<(a)->size; ++i) { \
if ( (a)->elems[i] ) { \
/* lwdebug(1, "freeing elem %d (%p) of edgering %p", i, (a)->elems[i], (a)); */ \
lwfree((a)->elems[i]); \
} \
} \
if ( (a)->elems ) { lwfree((a)->elems); (a)->elems = NULL; } \
(a)->size = 0; \
(a)->capacity = 0; \
if ( (a)->env ) { lwfree((a)->env); (a)->env = NULL; } \
if ( (a)->genv ) { GEOSGeom_destroy((a)->genv); (a)->genv = NULL; } \
}
/* An array of pointers to EDGERING structures */
typedef struct LWT_EDGERING_ARRAY_T {
LWT_EDGERING **rings;
int size;
int capacity;
GEOSSTRtree* tree;
} LWT_EDGERING_ARRAY;
#define LWT_EDGERING_ARRAY_INIT(a) { \
(a)->size = 0; \
(a)->capacity = 1; \
(a)->rings = lwalloc(sizeof(LWT_EDGERING *) * (a)->capacity); \
(a)->tree = NULL; \
}
/* WARNING: use of 'j' is intentional, not to clash with
* 'i' used in LWT_EDGERING_CLEAN */
#define LWT_EDGERING_ARRAY_CLEAN(a) { \
int j; for (j=0; j<(a)->size; ++j) { \
LWT_EDGERING_CLEAN((a)->rings[j]); \
} \
if ( (a)->capacity ) lwfree((a)->rings); \
if ( (a)->tree ) { \
GEOSSTRtree_destroy( (a)->tree ); \
(a)->tree = NULL; \
} \
}
#define LWT_EDGERING_ARRAY_PUSH(a, r) { \
if ( (a)->size + 1 > (a)->capacity ) { \
(a)->capacity *= 2; \
(a)->rings = lwrealloc((a)->rings, sizeof(LWT_EDGERING *) * (a)->capacity); \
} \
(a)->rings[(a)->size++] = (r); \
}
typedef struct LWT_EDGERING_POINT_ITERATOR_T {
LWT_EDGERING *ring;
LWT_EDGERING_ELEM *curelem;
int curelemidx;
int curidx;
} LWT_EDGERING_POINT_ITERATOR;
static int
_lwt_EdgeRingIterator_next(LWT_EDGERING_POINT_ITERATOR *it, POINT2D *pt)
{
LWT_EDGERING_ELEM *el = it->curelem;
POINTARRAY *pa;
if ( ! el ) return 0; /* finished */
pa = el->edge->geom->points;
int tonext = 0;
LWDEBUGF(3, "iterator fetching idx %d from pa of %d points", it->curidx, pa->npoints);
getPoint2d_p(pa, it->curidx, pt);
if ( el->left ) {
it->curidx++;
if ( it->curidx >= (int) pa->npoints ) tonext = 1;
} else {
it->curidx--;
if ( it->curidx < 0 ) tonext = 1;
}
if ( tonext )
{
LWDEBUG(3, "iterator moving to next element");
it->curelemidx++;
if ( it->curelemidx < it->ring->size )
{
el = it->curelem = it->ring->elems[it->curelemidx];
it->curidx = el->left ? 0 : el->edge->geom->points->npoints - 1;
}
else
{
it->curelem = NULL;
}
}
return 1;
}
/* Release return with lwfree */
static LWT_EDGERING_POINT_ITERATOR *
_lwt_EdgeRingIterator_begin(LWT_EDGERING *er)
{
LWT_EDGERING_POINT_ITERATOR *ret = lwalloc(sizeof(LWT_EDGERING_POINT_ITERATOR));
ret->ring = er;
if ( er->size ) ret->curelem = er->elems[0];
else ret->curelem = NULL;
ret->curelemidx = 0;
ret->curidx = (ret->curelem == NULL || ret->curelem->left) ? 0 : ret->curelem->edge->geom->points->npoints - 1;
return ret;
}
/* Identifier for a placeholder face that will be
* used to mark hole rings */
#define LWT_HOLES_FACE_PLACEHOLDER INT32_MIN
static int
_lwt_FetchNextUnvisitedEdge(__attribute__((__unused__)) LWT_TOPOLOGY *topo, LWT_ISO_EDGE_TABLE *etab, int from)
{
while (
from < etab->size &&
etab->edges[from].face_left != -1 &&
etab->edges[from].face_right != -1
) from++;
return from < etab->size ? from : -1;
}
static LWT_ISO_EDGE *
_lwt_FetchAllEdges(LWT_TOPOLOGY *topo, int *numedges)
{
LWT_ISO_EDGE *edge;
int fields = LWT_COL_EDGE_ALL;
uint64_t nelems = 1;
edge = lwt_be_getEdgeWithinBox2D( topo, NULL, &nelems, fields, 0);
*numedges = nelems;
if (nelems == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return NULL;
}
return edge;
}
/* Update the side face of given ring edges
*
* Edge identifiers are signed, those with negative identifier
* need to be updated their right_face, those with positive
* identifier need to be updated their left_face.
*
* @param face identifier of the face bound by the ring
* @return 0 on success, -1 on error
*/
static int
_lwt_UpdateEdgeRingSideFace(LWT_TOPOLOGY *topo, LWT_EDGERING *ring,
LWT_ELEMID face)
{
LWT_ISO_EDGE *forward_edges = NULL;
int forward_edges_count = 0;
LWT_ISO_EDGE *backward_edges = NULL;
int backward_edges_count = 0;
int i, ret;
/* Make a list of forward_edges and backward_edges */
forward_edges = lwalloc(sizeof(LWT_ISO_EDGE) * ring->size);
forward_edges_count = 0;
backward_edges = lwalloc(sizeof(LWT_ISO_EDGE) * ring->size);
backward_edges_count = 0;
for ( i=0; i<ring->size; ++i )
{
LWT_EDGERING_ELEM *elem = ring->elems[i];
LWT_ISO_EDGE *edge = elem->edge;
LWT_ELEMID id = edge->edge_id;
if ( elem->left )
{
LWDEBUGF(3, "Forward edge %d is %d", forward_edges_count, id);
forward_edges[forward_edges_count].edge_id = id;
forward_edges[forward_edges_count++].face_left = face;
edge->face_left = face;
}
else
{
LWDEBUGF(3, "Backward edge %d is %d", forward_edges_count, id);
backward_edges[backward_edges_count].edge_id = id;
backward_edges[backward_edges_count++].face_right = face;
edge->face_right = face;
}
}
/* 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 )
{
lwfree( forward_edges );
lwfree( backward_edges );
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ret != forward_edges_count )
{
lwfree( forward_edges );
lwfree( backward_edges );
lwerror("Unexpected error: %d edges updated when expecting %d (forward)",
ret, forward_edges_count);
return -1;
}
}
/* 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 )
{
lwfree( forward_edges );
lwfree( backward_edges );
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ret != backward_edges_count )
{
lwfree( forward_edges );
lwfree( backward_edges );
lwerror("Unexpected error: %d edges updated when expecting %d (backward)",
ret, backward_edges_count);
return -1;
}
}
lwfree( forward_edges );
lwfree( backward_edges );
return 0;
}
/*
* @param side 1 for left side, -1 for right side
*/
static LWT_EDGERING *
_lwt_BuildEdgeRing(__attribute__((__unused__)) LWT_TOPOLOGY *topo, LWT_ISO_EDGE_TABLE *edges,
LWT_ISO_EDGE *edge, int side)
{
LWT_EDGERING *ring;
LWT_EDGERING_ELEM *elem;
LWT_ISO_EDGE *cur;
int curside;
ring = lwalloc(sizeof(LWT_EDGERING));
LWT_EDGERING_INIT(ring);
cur = edge;
curside = side;
LWDEBUGF(2, "Building rings for edge %d (side %d)", cur->edge_id, side);
do {
LWT_ELEMID next;
elem = lwalloc(sizeof(LWT_EDGERING_ELEM));
elem->edge = cur;
elem->left = ( curside == 1 );
/* Mark edge as "visited" */
if ( elem->left ) cur->face_left = LWT_HOLES_FACE_PLACEHOLDER;
else cur->face_right = LWT_HOLES_FACE_PLACEHOLDER;
LWT_EDGERING_PUSH(ring, elem);
next = elem->left ? cur->next_left : cur->next_right;
LWDEBUGF(3, " next edge is %d", next);
if ( next > 0 ) curside = 1;
else { curside = -1; next = -next; }
cur = _lwt_getIsoEdgeById(edges, next);
if ( ! cur )
{
lwerror("Could not find edge with id %d", next);
break;
}
} while (cur != edge || curside != side);
LWDEBUGF(1, "Ring for edge %d has %d elems", edge->edge_id*side, ring->size);
return ring;
}
static double
_lwt_EdgeRingSignedArea(LWT_EDGERING_POINT_ITERATOR *it)
{
POINT2D P1;
POINT2D P2;
POINT2D P3;
double sum = 0.0;
double x0, x, y1, y2;
if ( ! _lwt_EdgeRingIterator_next(it, &P1) ) return 0.0;
if ( ! _lwt_EdgeRingIterator_next(it, &P2) ) return 0.0;
LWDEBUG(2, "_lwt_EdgeRingSignedArea");
x0 = P1.x;
while ( _lwt_EdgeRingIterator_next(it, &P3) )
{
x = P2.x - x0;
y1 = P3.y;
y2 = P1.y;
sum += x * (y2-y1);
/* Move forwards! */
P1 = P2;
P2 = P3;
}
return sum / 2.0;
}
/* Return 1 for true, 0 for false */
static int
_lwt_EdgeRingIsCCW(LWT_EDGERING *ring)
{
double sa;
LWDEBUGF(2, "_lwt_EdgeRingIsCCW, ring has %d elems", ring->size);
LWT_EDGERING_POINT_ITERATOR *it = _lwt_EdgeRingIterator_begin(ring);
sa = _lwt_EdgeRingSignedArea(it);
LWDEBUGF(2, "_lwt_EdgeRingIsCCW, signed area is %g", sa);
lwfree(it);
if ( sa >= 0 ) return 0;
else return 1;
}
static int
_lwt_EdgeRingCrossingCount(const POINT2D *p, LWT_EDGERING_POINT_ITERATOR *it)
{
int cn = 0; /* the crossing number counter */
POINT2D v1, v2;
#ifndef RELAX
POINT2D v0;
#endif
if ( ! _lwt_EdgeRingIterator_next(it, &v1) ) return cn;
v0 = v1;
while ( _lwt_EdgeRingIterator_next(it, &v2) )
{
double vt;
/* edge from vertex i to vertex i+1 */
if
(
/* an upward crossing */
((v1.y <= p->y) && (v2.y > p->y))
/* a downward crossing */
|| ((v1.y > p->y) && (v2.y <= p->y))
)
{
vt = (double)(p->y - v1.y) / (v2.y - v1.y);
/* P->x <intersect */
if (p->x < v1.x + vt * (v2.x - v1.x))
{
/* a valid crossing of y=p->y right of p->x */
++cn;
}
}
v1 = v2;
}
LWDEBUGF(3, "_lwt_EdgeRingCrossingCount returning %d", cn);
#ifndef RELAX
if ( memcmp(&v1, &v0, sizeof(POINT2D)) )
{
lwerror("_lwt_EdgeRingCrossingCount: V[n] != V[0] (%g %g != %g %g)",
v1.x, v1.y, v0.x, v0.y);
return -1;
}
#endif
return cn;
}
/* Return 1 for true, 0 for false */
static int
_lwt_EdgeRingContainsPoint(LWT_EDGERING *ring, POINT2D *p)
{
int cn = 0;
LWT_EDGERING_POINT_ITERATOR *it = _lwt_EdgeRingIterator_begin(ring);
cn = _lwt_EdgeRingCrossingCount(p, it);
lwfree(it);
return (cn&1); /* 0 if even (out), and 1 if odd (in) */
}
static GBOX *
_lwt_EdgeRingGetBbox(LWT_EDGERING *ring)
{
int i;
if ( ! ring->env )
{
LWDEBUGF(2, "Computing GBOX for ring %p", ring);
for (i=0; i<ring->size; ++i)
{
LWT_EDGERING_ELEM *elem = ring->elems[i];
LWLINE *g = elem->edge->geom;
const GBOX *newbox = lwgeom_get_bbox(lwline_as_lwgeom(g));
if ( ! i ) ring->env = gbox_clone( newbox );
else gbox_merge( newbox, ring->env );
}
}
return ring->env;
}
static LWT_ELEMID
_lwt_EdgeRingGetFace(LWT_EDGERING *ring)
{
LWT_EDGERING_ELEM *el = ring->elems[0];
return el->left ? el->edge->face_left : el->edge->face_right;
}
/*
* Register a face on an edge side
*
* Create and register face to shell (CCW) walks,
* register arbitrary negative face_id to CW rings.
*
* Push CCW rings to shells, CW rings to holes.
*
* The ownership of the "geom" and "ids" members of the
* LWT_EDGERING pushed to the given LWT_EDGERING_ARRAYS
* are transferred to caller.
*
* @param side 1 for left side, -1 for right side
*
* @param holes an array where holes will be pushed
*
* @param shells an array where shells will be pushed
*
* @param registered id of registered face. It will be a negative number
* for holes or isolated edge strips (still registered in the face
* table, but only temporary).
*
* @return 0 on success, -1 on error.
*
*/
static int
_lwt_RegisterFaceOnEdgeSide(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge,
int side, LWT_ISO_EDGE_TABLE *edges,
LWT_EDGERING_ARRAY *holes,
LWT_EDGERING_ARRAY *shells,
LWT_ELEMID *registered)
{
const LWT_BE_IFACE *iface = topo->be_iface;
/* this is arbitrary, could be taken as parameter */
static const int placeholder_faceid = LWT_HOLES_FACE_PLACEHOLDER;
LWT_EDGERING *ring;
/* Get edge ring */
ring = _lwt_BuildEdgeRing(topo, edges, edge, side);
LWDEBUG(2, "Ring built, calling EdgeRingIsCCW");
/* Compute winding (CW or CCW?) */
int isccw = _lwt_EdgeRingIsCCW(ring);
if ( isccw )
{
/* Create new face */
LWT_ISO_FACE newface;
LWDEBUGF(1, "Ring of edge %d is a shell (shell %d)", edge->edge_id * side, shells->size);
newface.mbr = _lwt_EdgeRingGetBbox(ring);
newface.face_id = -1;
/* Insert the new face */
int ret = lwt_be_insertFaces( topo, &newface, 1 );
newface.mbr = NULL;
if ( ret == -1 )
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( ret != 1 )
{
lwerror("Unexpected error: %d faces inserted when expecting 1", ret);
return -1;
}
/* return new face_id */
*registered = newface.face_id;
LWT_EDGERING_ARRAY_PUSH(shells, ring);
/* update ring edges set new face_id on resp. side to *registered */
ret = _lwt_UpdateEdgeRingSideFace(topo, ring, *registered);
if ( ret )
{
lwerror("Errors updating edgering side face: %s",
lwt_be_lastErrorMessage(iface));
return -1;
}
}
else /* cw, so is an hole */
{
LWDEBUGF(1, "Ring of edge %d is a hole (hole %d)", edge->edge_id * side, holes->size);
*registered = placeholder_faceid;
LWT_EDGERING_ARRAY_PUSH(holes, ring);
}
return 0;
}
static void
_lwt_AccumulateCanditates(void* item, void* userdata)
{
LWT_EDGERING_ARRAY *candidates = userdata;
LWT_EDGERING *sring = item;
LWT_EDGERING_ARRAY_PUSH(candidates, sring);
}
static LWT_ELEMID
_lwt_FindFaceContainingRing(LWT_TOPOLOGY* topo, LWT_EDGERING *ring,
LWT_EDGERING_ARRAY *shells)
{
LWT_ELEMID foundInFace = -1;
int i;
const GBOX *minenv = NULL;
POINT2D pt;
const GBOX *testbox;
GEOSGeometry *ghole;
getPoint2d_p( ring->elems[0]->edge->geom->points, 0, &pt );
testbox = _lwt_EdgeRingGetBbox(ring);
/* Create a GEOS Point from a vertex of the hole ring */
{
LWPOINT *point = lwpoint_make2d(topo->srid, pt.x, pt.y);
ghole = LWGEOM2GEOS( lwpoint_as_lwgeom(point), 1 );
lwpoint_free(point);
if ( ! ghole ) {
lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
return -1;
}
}
/* Build STRtree of shell envelopes */
if ( ! shells->tree )
{
static const int STRTREE_NODE_CAPACITY = 10;
LWDEBUG(1, "Building STRtree");
shells->tree = GEOSSTRtree_create(STRTREE_NODE_CAPACITY);
if (shells->tree == NULL)
{
lwerror("Could not create GEOS STRTree: %s", lwgeom_geos_errmsg);
return -1;
}
for (i=0; i<shells->size; ++i)
{
LWT_EDGERING *sring = shells->rings[i];
const GBOX* shellbox = _lwt_EdgeRingGetBbox(sring);
LWDEBUGF(2, "GBOX of shell %p for edge %d is %g %g,%g %g",
sring, sring->elems[0]->edge->edge_id, shellbox->xmin,
shellbox->ymin, shellbox->xmax, shellbox->ymax);
POINTARRAY *pa = ptarray_construct(0, 0, 2);
POINT4D pt;
LWLINE *diag;
pt.x = shellbox->xmin;
pt.y = shellbox->ymin;
ptarray_set_point4d(pa, 0, &pt);
pt.x = shellbox->xmax;
pt.y = shellbox->ymax;
ptarray_set_point4d(pa, 1, &pt);
diag = lwline_construct(topo->srid, NULL, pa);
/* Record just envelope in ggeom */
/* making valid, probably not needed */
sring->genv = LWGEOM2GEOS( lwline_as_lwgeom(diag), 1 );
lwline_free(diag);
GEOSSTRtree_insert(shells->tree, sring->genv, sring);
}
LWDEBUG(1, "STRtree build completed");
}
LWT_EDGERING_ARRAY candidates;
LWT_EDGERING_ARRAY_INIT(&candidates);
GEOSSTRtree_query(shells->tree, ghole, &_lwt_AccumulateCanditates, &candidates);
LWDEBUGF(1, "Found %d candidate shells containing first point of ring's originating edge %d",
candidates.size, ring->elems[0]->edge->edge_id * ( ring->elems[0]->left ? 1 : -1 ) );
/* TODO: sort candidates by bounding box size */
for (i=0; i<candidates.size; ++i)
{
LWT_EDGERING *sring = candidates.rings[i];
const GBOX* shellbox = _lwt_EdgeRingGetBbox(sring);
int contains = 0;
if ( sring->elems[0]->edge->edge_id == ring->elems[0]->edge->edge_id )
{
LWDEBUGF(1, "Shell %d is on other side of ring",
_lwt_EdgeRingGetFace(sring));
continue;
}
/* The hole envelope cannot equal the shell envelope */
if ( gbox_same(shellbox, testbox) )
{
LWDEBUGF(1, "Bbox of shell %d equals that of hole ring",
_lwt_EdgeRingGetFace(sring));
continue;
}
/* Skip if ring box is not in shell box */
if ( ! gbox_contains_2d(shellbox, testbox) )
{
LWDEBUGF(1, "Bbox of shell %d does not contain bbox of ring point",
_lwt_EdgeRingGetFace(sring));
continue;
}
/* Skip test if a containing shell was already found
* and this shell's bbox is not contained in the other */
if ( minenv && ! gbox_contains_2d(minenv, shellbox) )
{
LWDEBUGF(2, "Bbox of shell %d (face %d) not contained by bbox "
"of last shell found to contain the point",
i, _lwt_EdgeRingGetFace(sring));
continue;
}
contains = _lwt_EdgeRingContainsPoint(sring, &pt);
if ( contains )
{
/* Continue until all shells are tested, as we want to
* use the one with the smallest bounding box */
/* IDEA: sort shells by bbox size, stopping on first match */
LWDEBUGF(1, "Shell %d contains hole of edge %d",
_lwt_EdgeRingGetFace(sring),
ring->elems[0]->edge->edge_id);
minenv = shellbox;
foundInFace = _lwt_EdgeRingGetFace(sring);
}
}
if ( foundInFace == -1 ) foundInFace = 0;
candidates.size = 0; /* Avoid destroying the actual shell rings */
LWT_EDGERING_ARRAY_CLEAN(&candidates);
GEOSGeom_destroy(ghole);
return foundInFace;
}
/*
* @return -1 on error (and report error),
* 1 if faces beside the universal one exist
* 0 otherwise
*/
static int
_lwt_CheckFacesExist(LWT_TOPOLOGY *topo)
{
LWT_ISO_FACE *faces;
int fields = LWT_COL_FACE_FACE_ID;
uint64_t nelems = 1;
GBOX qbox;
qbox.xmin = qbox.ymin = -DBL_MAX;
qbox.xmax = qbox.ymax = DBL_MAX;
faces = lwt_be_getFaceWithinBox2D( topo, &qbox, &nelems, fields, 1);
if (nelems == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
return -1;
}
if ( faces ) _lwt_release_faces(faces, nelems);
return nelems;
}
int
lwt_Polygonize(LWT_TOPOLOGY* topo)
{
/*
Fetch all edges
Sort edges by edge_id
Mark all edges' left and right face as -1
Iteratively:
Fetch next edge with left or right face == -1
For each side with face == -1:
Find ring on its side
If ring is CCW:
create a new face, assign to the ring edges' appropriate side
If ring is CW (face needs to be same of external):
assign a negative face_id the ring edges' appropriate side
Now for each edge with a negative face_id on the side:
Find containing face (mbr cache and all)
Update with id of containing face
*/
const LWT_BE_IFACE *iface = topo->be_iface;
LWT_ISO_EDGE *edge;
int numfaces = -1;
LWT_ISO_EDGE_TABLE edgetable;
LWT_EDGERING_ARRAY holes, shells;
int i;
int err = 0;
LWT_EDGERING_ARRAY_INIT(&holes);
LWT_EDGERING_ARRAY_INIT(&shells);
initGEOS(lwnotice, lwgeom_geos_error);
/*
Check if Topology already contains some Face
(ignoring the Universal Face)
*/
numfaces = _lwt_CheckFacesExist(topo);
if ( numfaces != 0 ) {
if ( numfaces > 0 ) {
/* Faces exist */
lwerror("Polygonize: face table is not empty.");
}
/* Backend error, message should have been printed already */
return -1;
}
edgetable.edges = _lwt_FetchAllEdges(topo, &(edgetable.size));
if ( ! edgetable.edges ) {
if (edgetable.size == 0) {
/* not an error: no Edges */
return 0;
}
/* error should have been printed already */
return -1;
}
/* Sort edges by ID (to allow btree searches) */
qsort(edgetable.edges, edgetable.size, sizeof(LWT_ISO_EDGE), compare_iso_edges_by_id);
/* Mark all edges as unvisited */
for (i=0; i<edgetable.size; ++i)
edgetable.edges[i].face_left = edgetable.edges[i].face_right = -1;
i = 0;
while (1)
{
i = _lwt_FetchNextUnvisitedEdge(topo, &edgetable, i);
if ( i < 0 ) break; /* end of unvisited */
edge = &(edgetable.edges[i]);
LWT_ELEMID newface = -1;
LWDEBUGF(1, "Next face-missing edge has id:%d, face_left:%d, face_right:%d",
edge->edge_id, edge->face_left, edge->face_right);
if ( edge->face_left == -1 )
{
err = _lwt_RegisterFaceOnEdgeSide(topo, edge, 1, &edgetable,
&holes, &shells, &newface);
if ( err ) break;
LWDEBUGF(1, "New face on the left of edge %d is %d",
edge->edge_id, newface);
edge->face_left = newface;
}
if ( edge->face_right == -1 )
{
err = _lwt_RegisterFaceOnEdgeSide(topo, edge, -1, &edgetable,
&holes, &shells, &newface);
if ( err ) break;
LWDEBUGF(1, "New face on the right of edge %d is %d",
edge->edge_id, newface);
edge->face_right = newface;
}
}
if ( err )
{
_lwt_release_edges(edgetable.edges, edgetable.size);
LWT_EDGERING_ARRAY_CLEAN( &holes );
LWT_EDGERING_ARRAY_CLEAN( &shells );
lwerror("Errors fetching or registering face-missing edges: %s",
lwt_be_lastErrorMessage(iface));
return -1;
}
LWDEBUGF(1, "Found %d holes and %d shells", holes.size, shells.size);
/* TODO: sort holes by pt.x, sort shells by bbox.xmin */
/* Assign shells to holes */
for (i=0; i<holes.size; ++i)
{
LWT_ELEMID containing_face;
LWT_EDGERING *ring = holes.rings[i];
containing_face = _lwt_FindFaceContainingRing(topo, ring, &shells);
LWDEBUGF(1, "Ring %d contained by face %" LWTFMT_ELEMID, i, containing_face);
if ( containing_face == -1 )
{
_lwt_release_edges(edgetable.edges, edgetable.size);
LWT_EDGERING_ARRAY_CLEAN( &holes );
LWT_EDGERING_ARRAY_CLEAN( &shells );
lwerror("Errors finding face containing ring: %s",
lwt_be_lastErrorMessage(iface));
return -1;
}
int ret = _lwt_UpdateEdgeRingSideFace(topo, holes.rings[i], containing_face);
if ( ret )
{
_lwt_release_edges(edgetable.edges, edgetable.size);
LWT_EDGERING_ARRAY_CLEAN( &holes );
LWT_EDGERING_ARRAY_CLEAN( &shells );
lwerror("Errors updating edgering side face: %s",
lwt_be_lastErrorMessage(iface));
return -1;
}
}
LWDEBUG(1, "All holes assigned, cleaning up");
_lwt_release_edges(edgetable.edges, edgetable.size);
/* delete all shell and hole EDGERINGS */
LWT_EDGERING_ARRAY_CLEAN( &holes );
LWT_EDGERING_ARRAY_CLEAN( &shells );
return 0;
}
LWT_ELEMID
lwt_GetFaceContainingPoint(LWT_TOPOLOGY* topo, const LWPOINT* pt)
{
LWT_ISO_EDGE* closestEdge;
LWT_ISO_EDGE* edges;
uint64_t numedges, i;
const POINT2D *queryPoint;
const POINT2D *closestPointOnEdge = NULL;
uint32_t closestSegmentIndex;
int closestSegmentSide;
uint32_t closestPointVertex;
const POINT2D *closestSegmentP0, *closestSegmentP1;
LWT_ELEMID closestNode = 0;
double dist;
int containingFace = -1;
closestEdge = lwt_be_getClosestEdge( topo, pt, &numedges,
LWT_COL_EDGE_GEOM|
LWT_COL_EDGE_EDGE_ID|
LWT_COL_EDGE_START_NODE|
LWT_COL_EDGE_END_NODE|
LWT_COL_EDGE_FACE_LEFT|
LWT_COL_EDGE_FACE_RIGHT
);
if (numedges == UINT64_MAX)
{
lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
/* cberror(topo->be_data, "Error from cb_getClosestEdge"); */
return -1;
}
if (numedges == 0)
{
/* If there are no edges the point is in the universal face */
return 0;
}
LWDEBUGGF(2, lwline_as_lwgeom(closestEdge->geom), "Closest edge %" LWTFMT_ELEMID, closestEdge->edge_id);
/* Find closest segment of edge to the point */
queryPoint = getPoint2d_cp(pt->point, 0);
closestSegmentIndex = ptarray_closest_segment_2d(closestEdge->geom->points, queryPoint, &dist);
LWDEBUGF(1, "Closest segment on edge %" LWTFMT_ELEMID " is %d (dist %g)", closestEdge->edge_id, closestSegmentIndex, dist);
closestSegmentP0 = getPoint2d_cp(closestEdge->geom->points, closestSegmentIndex);
closestSegmentP1 = getPoint2d_cp(closestEdge->geom->points, closestSegmentIndex + 1);
LWDEBUGF(1, "Closest segment on edge %" LWTFMT_ELEMID " is LINESTRING(%g %g, %g %g)",
closestEdge->edge_id,
closestSegmentP0->x,
closestSegmentP0->y,
closestSegmentP1->x,
closestSegmentP1->y
);
/*
* We use comp.graphics.algorithms Frequently Asked Questions method
*
* (1) AC dot AB
* r = ----------
* ||AB||^2
* r has the following meaning:
* r=0 P = A
* r=1 P = B
* r<0 P is on the backward extension of AB
* r>1 P is on the forward extension of AB
* 0<r<1 P is interior to AB
*
*/
const POINT2D *p = queryPoint;
const POINT2D *A = closestSegmentP0;
const POINT2D *B = closestSegmentP1;
double r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
if ( r <= 0 )
{
closestPointOnEdge = A;
closestPointVertex = closestSegmentIndex;
if ( closestSegmentIndex == 0 )
{
closestNode = closestEdge->start_node;
}
}
else if (r >= 1 )
{
closestPointOnEdge = B;
closestPointVertex = closestSegmentIndex + 1;
if ( closestSegmentIndex + 2 == closestEdge->geom->points->npoints )
{
closestNode = closestEdge->end_node;
}
}
else
{
closestPointVertex = closestEdge->geom->points->npoints;
}
if ( closestNode != 0 )
{
LWDEBUGF(1, "Closest point is node %d", closestNode);
if ( dist == 0 )
{
LWDEBUGF(1, "Query point is node %d", closestNode);
/* Query point is the node
*
* If all edges incident to the node are
* dangling, we can return their common
* side face, otherwise the point will be
* on multiple face boundaries
*/
if ( closestEdge->face_left != closestEdge->face_right )
{
_lwt_release_edges(closestEdge, 1);
lwerror("Two or more faces found");
return -1;
}
containingFace = closestEdge->face_left;
/* Check other incident edges */
numedges = 1;
edges = lwt_be_getEdgeByNode( topo, &closestNode, &numedges, LWT_COL_EDGE_FACE_LEFT|LWT_COL_EDGE_FACE_RIGHT );
if (numedges == UINT64_MAX)
{
lwerror("Backend error from getEdgeByNode: %s", lwt_be_lastErrorMessage(topo->be_iface));
/* cberror(topo->be_data, "Error from cb_getClosestEdge"); */
_lwt_release_edges(closestEdge, 1);
return -1;
}
for (i=0; i<numedges; ++i)
{
if ( edges[i].face_left != containingFace ||
edges[i].face_right != containingFace )
{
_lwt_release_edges(edges, numedges);
_lwt_release_edges(closestEdge, 1);
lwerror("Two or more faces found");
return -1;
}
}
if (numedges < 1 )
{
lwerror("Unexpected backend return: getEdgeByNode(%d) returns no edges when we previously found edge %d ending on that node",
closestNode, closestEdge->edge_id);
_lwt_release_edges(edges, numedges);
_lwt_release_edges(closestEdge, 1);
return -1;
}
LWDEBUGF(1, "lwt_be_getEdgeByNode returned %d edges", numedges);
_lwt_release_edges(edges, numedges);
_lwt_release_edges(closestEdge, 1);
return containingFace;
}
/* Closest point is a node, but query point is NOT on the node */
/* let's do azimuth computation */
edgeend ee;
if ( ! azimuth_pt_pt(closestPointOnEdge, queryPoint, &ee.myaz) ) {
lwerror("error computing azimuth of query point [%.15g %.15g,%.15g %.15g]",
closestPointOnEdge->x, closestPointOnEdge->y,
queryPoint->x, queryPoint->y);
_lwt_release_edges(closestEdge, 1);
return -1;
}
LWDEBUGF(1, "Query point azimuth is %g", ee.myaz);
int found = _lwt_FindAdjacentEdges( topo, closestNode, &ee, NULL, -1 );
if ( ! found ) {
lwerror("Unexpected backend return: _lwt_FindAdjacentEdges(%d) found no edges when we previously found edge %d ending on that node",
closestNode, closestEdge->edge_id);
_lwt_release_edges(closestEdge, 1);
return -1;
}
_lwt_release_edges(closestEdge, 1);
return ee.cwFace;
}
LWDEBUG(1, "Closest point is NOT a node");
/* If this edge has the same face on the left and right sides
* we found the face containing our query point */
if ( closestEdge->face_left == closestEdge->face_right )
{
containingFace = closestEdge->face_left;
_lwt_release_edges(closestEdge, 1);
return containingFace;
}
if ( dist == 0 )
{
/* We checked the dangling case above */
_lwt_release_edges(closestEdge, 1);
lwerror("Two or more faces found");
return -1;
}
/* Find on which side of the segment the query point lays */
if ( closestPointVertex != closestEdge->geom->points->npoints )
{
/* Closest point is a vertex of the closest segment */
LWDEBUGF(1, "Closest point is vertex %d of closest segment", closestPointVertex);
/*
* We need to check if rotating clockwise the line
* from previous vertex to closest vertex clockwise
* around the closest vertex encounters
* the line to query point first (which means it's on the left
* of the closest edge) or the line to next vertex first (which
* means the query point is on the right)
*/
uint32_t prevVertexIndex = closestPointVertex > 0 ?
closestPointVertex - 1u :
closestEdge->geom->points->npoints - 2u; /* last vertex would be == first vertex, this being a closed edge */
const POINT2D *prevVertex = getPoint2d_cp(
closestEdge->geom->points,
prevVertexIndex
);
LWDEBUGF(1, "Previous vertex %u is POINT(%.15g %.15g)",
prevVertexIndex,
prevVertex->x,
prevVertex->y
);
uint32_t nextVertexIndex = closestPointVertex == closestEdge->geom->points->npoints - 1u ?
1u : /* first point would be == last point, this being a closed edge */
closestPointVertex + 1u;
const POINT2D *nextVertex = getPoint2d_cp(
closestEdge->geom->points,
nextVertexIndex
);
LWDEBUGF(1, "Next vertex %u is POINT(%.15g %.15g)",
nextVertexIndex,
nextVertex->x,
nextVertex->y
);
double azS0; /* azimuth from previous vertex to closestPointVertex */
double azS1; /* azimuth from closestPointVertex to next vertex */
double azSL; /* azimuth from closestPointVertex to query point */
if ( ! azimuth_pt_pt(closestPointOnEdge, prevVertex, &azS0)) {
lwerror("error computing azimuth of segment to closest point [%.15g %.15g,%.15g %.15g]",
closestPointOnEdge->x, closestPointOnEdge->y,
prevVertex->x, prevVertex->y);
_lwt_release_edges(closestEdge, 1);
return -1;
}
if ( ! azimuth_pt_pt(closestPointOnEdge, nextVertex, &azS1)) {
lwerror("error computing azimuth of segment from closest point [%.15g %.15g,%.15g %.15g]",
closestPointOnEdge->x, closestPointOnEdge->y,
nextVertex->x, nextVertex->y);
_lwt_release_edges(closestEdge, 1);
return -1;
}
if ( ! azimuth_pt_pt(closestPointOnEdge, queryPoint, &azSL) ) {
lwerror("error computing azimuth of queryPoint [%.15g %.15g,%.15g %.15g]",
closestPointOnEdge->x, closestPointOnEdge->y,
queryPoint->x, queryPoint->y);
_lwt_release_edges(closestEdge, 1);
return -1;
}
double angle_S0_S1 = azS1 - azS0;
if ( angle_S0_S1 < 0 ) angle_S0_S1 += 2 * M_PI;
double angle_S0_SL = azSL - azS0;
if ( angle_S0_SL < 0 ) angle_S0_SL += 2 * M_PI;
LWDEBUGF(1, "Azimuths from closest (vertex) point: P:%g, N:%g (+%g), Q:%g (+%g)",
azS0,
azS1, angle_S0_S1,
azSL, angle_S0_SL
);
if ( angle_S0_SL < angle_S0_S1 )
{
/* line to query point was encountered first, is on the left */
containingFace = closestEdge->face_left;
}
else
{
/* line to query point was NOT encountered first, is on the right */
containingFace = closestEdge->face_right;
}
}
else
{
/* Closest point is internal to closest segment, we can use
* lw_segment_side */
LWDEBUGF(1, "Closest point is internal to closest segment, calling lw_segment_side((%g,%g),(%g,%g),(%g,%g)",
closestSegmentP0->x,
closestSegmentP0->y,
closestSegmentP1->x,
closestSegmentP1->y,
queryPoint->x,
queryPoint->y
);
closestSegmentSide = lw_segment_side(closestSegmentP0, closestSegmentP1, queryPoint);
LWDEBUGF(1, "Side of closest segment query point falls on: %d", closestSegmentSide);
if ( closestSegmentSide == -1 ) /* left */
{
containingFace = closestEdge->face_left;
}
else if ( closestSegmentSide == 1 ) /* right */
{
containingFace = closestEdge->face_right;
}
else
{
lwerror("Unexpected collinearity reported from lw_segment_side");
_lwt_release_edges(closestEdge, 1);
return -1;
}
}
_lwt_release_edges(closestEdge, 1);
return containingFace;
}