From 314c9948cd6df070a1689af51e519c90c9436755 Mon Sep 17 00:00:00 2001 From: Sandro Santilli Date: Sun, 16 Aug 2015 21:34:15 +0000 Subject: [PATCH] Implement ST_RemEdgeModFace in C Includes two new callbacks for TopoGeom management Funded by Tuscany Region (Italy) - SITA (CIG: 60351023B8) git-svn-id: http://svn.osgeo.org/postgis/trunk@13912 b70326c6-7e19-0410-871a-916f4a2858ee --- liblwgeom/TODO | 2 +- liblwgeom/liblwgeom_topo.h | 38 ++- liblwgeom/lwgeom_topo.c | 459 +++++++++++++++++++++++++++++++++++- topology/postgis_topology.c | 235 +++++++++++++++++- topology/sql/sqlmm.sql.in | 273 +-------------------- 5 files changed, 730 insertions(+), 277 deletions(-) diff --git a/liblwgeom/TODO b/liblwgeom/TODO index b1bce6547..f0dd96e15 100644 --- a/liblwgeom/TODO +++ b/liblwgeom/TODO @@ -17,8 +17,8 @@ lwt_GetFaceEdges X lwt_ChangeEdgeGeom X lwt_RemoveIsoNode X lwt_MoveIsoNode X +lwt_RemEdgeModFace X lwt_RemEdgeNewFace -lwt_RemEdgeModFace lwt_ModEdgeHeal lwt_NewEdgeHeal diff --git a/liblwgeom/liblwgeom_topo.h b/liblwgeom/liblwgeom_topo.h index a8aa2d5ca..51d8b3fb8 100644 --- a/liblwgeom/liblwgeom_topo.h +++ b/liblwgeom/liblwgeom_topo.h @@ -733,6 +733,38 @@ typedef struct LWT_BE_CALLBACKS_T { int numelems ); + /** + * Update TopoGeometry objects after an edge removal event + * + * @param topo the topology to act upon + * @param rem_edge identifier of the edge that's been removed + * + * @return 1 on success, 0 on error (@see lastErrorMessage) + * + */ + int (*updateTopoGeomRemEdge) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_edge + ); + + /** + * Update TopoGeometry objects after healing two faces + * + * @param topo the topology to act upon + * @param face1 identifier of the first face + * @param face2 identifier of the second face + * @param newface identifier of the new face + * + * @note that newface may or may not be equal to face1 or face2, + * while face1 should never be the same as face2. + * + * @return 1 on success, 0 on error (@see lastErrorMessage) + * + */ + int (*updateTopoGeomFaceHeal) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID face1, LWT_ELEMID face2, LWT_ELEMID newface + ); } LWT_BE_CALLBACKS; @@ -1067,7 +1099,8 @@ LWT_ELEMID lwt_AddEdgeNewFaces(LWT_TOPOLOGY* topo, * * @param topo the topology to operate on * @param edge identifier of the edge to be removed - * @return the id of newly created face or -1 if no new face was created + * @return the id of newly created face, 0 if no new face was created + * or -1 on error * */ LWT_ELEMID lwt_RemEdgeNewFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge); @@ -1082,7 +1115,8 @@ LWT_ELEMID lwt_RemEdgeNewFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge); * * @param topo the topology to operate on * @param edge identifier of the edge to be removed - * @return the id of newly created face or -1 if no new face was created + * @return the id of newly created face, 0 if no new face was created + * or -1 on error * */ LWT_ELEMID lwt_RemEdgeModFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge); diff --git a/liblwgeom/lwgeom_topo.c b/liblwgeom/lwgeom_topo.c index 325581989..00069eca1 100644 --- a/liblwgeom/lwgeom_topo.c +++ b/liblwgeom/lwgeom_topo.c @@ -323,11 +323,26 @@ lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_EL } static int -lwt_be_updateTopoGeomFaceSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2) +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_updateTopoGeomRemEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id) +{ + CBT1(topo, updateTopoGeomRemEdge, edge_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 LWT_ELEMID* lwt_be_getRingEdges( LWT_TOPOLOGY* topo, LWT_ELEMID edge, int *numedges, int limit ) @@ -368,12 +383,22 @@ lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt) * ************************************************************************/ +static void +_lwt_release_faces(LWT_ISO_FACE *faces, int num_faces) +{ + int i; + for ( i=0; ibe_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_updateTopoGeomRemEdge(topo, edge_id) ) + { + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + lwnotice("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 == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + nedge_left = nedge_right = 0; + for ( i=0; iedge_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 */ + i = lwt_be_updateEdgesById(topo, &(upd_edge_left[0]), nedge_left, + LWT_COL_EDGE_NEXT_LEFT); + if ( i == -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 */ + i = lwt_be_updateEdgesById(topo, &(upd_edge_right[0]), nedge_right, + LWT_COL_EDGE_NEXT_RIGHT); + if ( i == -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); + if ( modFace ) + { + /* 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 == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + GBOX *box1=NULL; + GBOX *box2=NULL; + for ( i=0; iface_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); + _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); + _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.face_id = edge->face_right; + newface.mbr = box1; + i = lwt_be_updateFacesById( topo, &newface, 1 ); + _lwt_release_faces(faces, 2); + if ( i == -1 ) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( i != 1 ) + { + _lwt_release_edges(edge, 1); + lwerror("Unexpected error: %d faces updated when expecting 1", i); + return -1; + } + } + } + + /* 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 */ + i = lwt_be_deleteEdges(topo, edge, LWT_COL_EDGE_EDGE_ID); + if ( i == -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 ) + { + i = lwt_be_updateNodesById(topo, upd_node, nnode, + LWT_COL_NODE_CONTAINING_FACE); + if ( i == -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; + i = lwt_be_deleteFacesById(topo, ids, nids); + if ( i == -1 ) { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + _lwt_release_edges(edge, 1); + return floodface; +} + +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 ); +} diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c index 1a10f70e7..ef38ce4b6 100644 --- a/topology/postgis_topology.c +++ b/topology/postgis_topology.c @@ -2006,6 +2006,185 @@ cb_updateTopoGeomFaceSplit ( const LWT_BE_TOPOLOGY* topo, return 1; } +static int +cb_updateTopoGeomRemEdge ( const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_edge ) +{ + MemoryContext oldcontext = CurrentMemoryContext; + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + + POSTGIS_DEBUG(1, "cb_updateTopoGeomRemEdge enter "); + + initStringInfo(sql); + appendStringInfo( sql, "SELECT r.topogeo_id, r.layer_id, " + "l.schema_name, l.table_name, l.feature_column FROM " + "topology.layer l INNER JOIN \"%s\".relation r " + "ON (l.layer_id = r.layer_id) WHERE l.level = 0 AND " + "l.feature_type = 2 AND l.topology_id = %d" + " AND abs(r.element_id) = " INT64_FORMAT, + topo->name, topo->id, rem_edge ); + + POSTGIS_DEBUGF(1, "cb_updateTopoGeomRemEdge query: %s", sql->data); + + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0); + MemoryContextSwitchTo( oldcontext ); /* switch back */ + if ( spi_result != SPI_OK_SELECT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + + if ( SPI_processed ) + {{ + const char *tg_id, *layer_id; + const char *schema_name, *table_name, *col_name; + HeapTuple row = SPI_tuptable->vals[0]; + TupleDesc tdesc = SPI_tuptable->tupdesc; + + tg_id = SPI_getvalue(row, tdesc, 1); + layer_id = SPI_getvalue(row, tdesc, 2); + schema_name = SPI_getvalue(row, tdesc, 3); + table_name = SPI_getvalue(row, tdesc, 4); + col_name = SPI_getvalue(row, tdesc, 5); + + cberror(topo->be_data, "TopoGeom %s in layer %s " + "(%s.%s.%s) cannot be represented " + "dropping edge " INT64_FORMAT, + tg_id, layer_id, schema_name, table_name, + col_name, rem_edge); + return 0; + }} + + return 1; +} + +static int +cb_updateTopoGeomFaceHeal ( const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID face1, LWT_ELEMID face2, LWT_ELEMID newface ) +{ + MemoryContext oldcontext = CurrentMemoryContext; + int spi_result; + StringInfoData sqldata; + StringInfo sql = &sqldata; + + POSTGIS_DEBUG(1, "cb_updateTopoGeomFaceHeal enter "); + + /* 1. check if any basic TopoGeometry is defined by one but not + * the other face, return 0 in that case */ + + initStringInfo(sql); + appendStringInfo( sql, "SELECT t.* FROM ( SELECT r.topogeo_id, " + "r.layer_id, l.schema_name, l.table_name, l.feature_column, " + "array_agg(r.element_id) as elems FROM topology.layer l " + " INNER JOIN \"%s\".relation r ON (l.layer_id = r.layer_id) " + "WHERE l.level = 0 and l.feature_type = 3 " + "AND l.topology_id = %d" + " AND r.element_id = ANY (ARRAY[" INT64_FORMAT "," INT64_FORMAT + "]::int4[]) group by r.topogeo_id, r.layer_id, l.schema_name, " + "l.table_name, l.feature_column ) t WHERE NOT t.elems @> ARRAY[" + INT64_FORMAT "," INT64_FORMAT "]::int4[]", + topo->name, topo->id, face1, face2, face1, face2 ); + + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 1: %s", sql->data); + + spi_result = SPI_execute(sql->data, !topo->be_data->data_changed, 0); + MemoryContextSwitchTo( oldcontext ); /* switch back */ + if ( spi_result != SPI_OK_SELECT ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + + if ( SPI_processed ) + {{ + const char *tg_id, *layer_id; + const char *schema_name, *table_name, *col_name; + HeapTuple row = SPI_tuptable->vals[0]; + TupleDesc tdesc = SPI_tuptable->tupdesc; + + tg_id = SPI_getvalue(row, tdesc, 1); + layer_id = SPI_getvalue(row, tdesc, 2); + schema_name = SPI_getvalue(row, tdesc, 3); + table_name = SPI_getvalue(row, tdesc, 4); + col_name = SPI_getvalue(row, tdesc, 5); + + cberror(topo->be_data, "TopoGeom %s in layer %s " + "(%s.%s.%s) cannot be represented " + "healing faces " INT64_FORMAT + " and " INT64_FORMAT, + tg_id, layer_id, schema_name, table_name, + col_name, face1, face2); + return 0; + }} + + /* 2. delete oldfaces (not equal to newface) from the + * set of primitives defining the TopoGeometries found before */ + + if ( newface == face1 || newface == face2 ) + { + initStringInfo(sql); + /* this query can be optimized */ + appendStringInfo( sql, "DELETE FROM \"%s\".relation r " + "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 3" + " AND l.topology_id = %d AND l.layer_id = r.layer_id " + " AND abs(r.element_id) IN ( " INT64_FORMAT "," INT64_FORMAT ")" + " AND abs(r.element_id) != " INT64_FORMAT, + topo->name, topo->id, face1, face2, newface ); + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 2a: %s", sql->data); + + spi_result = SPI_execute(sql->data, false, 0); + MemoryContextSwitchTo( oldcontext ); /* switch back */ + if ( spi_result != SPI_OK_DELETE ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + if ( SPI_processed ) topo->be_data->data_changed = true; + } + else + { + initStringInfo(sql); + /* delete face1 */ + appendStringInfo( sql, "DELETE FROM \"%s\".relation r " + "USING topology.layer l WHERE l.level = 0 AND l.feature_type = 3" + " AND l.topology_id = %d AND l.layer_id = r.layer_id " + " AND abs(r.element_id) = " INT64_FORMAT, + topo->name, topo->id, face1 ); + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 2b: %s", sql->data); + + spi_result = SPI_execute(sql->data, false, 0); + MemoryContextSwitchTo( oldcontext ); /* switch back */ + if ( spi_result != SPI_OK_DELETE ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + if ( SPI_processed ) topo->be_data->data_changed = true; + + initStringInfo(sql); + /* update face2 to newface */ + appendStringInfo( sql, "UPDATE \"%s\".relation r " + "SET element_id = " INT64_FORMAT " FROM topology.layer l " + "WHERE l.level = 0 AND l.feature_type = 3 AND l.topology_id = %d" + " AND l.layer_id = r.layer_id AND r.element_id = " INT64_FORMAT, + topo->name, newface, topo->id, face2 ); + POSTGIS_DEBUGF(1, "cb_updateTopoGeomFaceHeal query 3: %s", sql->data); + + spi_result = SPI_execute(sql->data, false, 0); + MemoryContextSwitchTo( oldcontext ); /* switch back */ + if ( spi_result != SPI_OK_UPDATE ) { + cberror(topo->be_data, "unexpected return (%d) from query execution: %s", + spi_result, sql->data); + return 0; + } + if ( SPI_processed ) topo->be_data->data_changed = true; + } + + return 1; +} + static LWT_ELEMID cb_getFaceContainingPoint( const LWT_BE_TOPOLOGY* topo, const LWPOINT* pt ) { @@ -2300,7 +2479,9 @@ static LWT_BE_CALLBACKS be_callbacks = { cb_topoGetSRID, cb_topoGetPrecision, cb_topoHasZ, - cb_deleteNodesById + cb_deleteNodesById, + cb_updateTopoGeomRemEdge, + cb_updateTopoGeomFaceHeal }; @@ -3174,3 +3355,55 @@ Datum ST_MoveIsoNode(PG_FUNCTION_ARGS) } PG_RETURN_TEXT_P(cstring2text(buf)); } + +/* ST_RemEdgeModFace(atopology, anedge) */ +Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(ST_RemEdgeModFace); +Datum ST_RemEdgeModFace(PG_FUNCTION_ARGS) +{ + text* toponame_text; + char* toponame; + int ret; + LWT_ELEMID node_id; + LWT_TOPOLOGY *topo; + + if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) ) { + lwpgerror("SQL/MM Spatial exception - null argument"); + PG_RETURN_NULL(); + } + + toponame_text = PG_GETARG_TEXT_P(0); + toponame = text2cstring(toponame_text); + PG_FREE_IF_COPY(toponame_text, 0); + + node_id = PG_GETARG_INT32(1) ; + + if ( SPI_OK_CONNECT != SPI_connect() ) { + lwpgerror("Could not connect to SPI"); + PG_RETURN_NULL(); + } + be_data.data_changed = false; + + topo = lwt_LoadTopology(be_iface, toponame); + pfree(toponame); + if ( ! topo ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + POSTGIS_DEBUG(1, "Calling lwt_RemEdgeModFace"); + ret = lwt_RemEdgeModFace(topo, node_id); + POSTGIS_DEBUG(1, "lwt_RemEdgeModFace returned"); + lwt_FreeTopology(topo); + + if ( ret == -1 ) { + /* should never reach this point, as lwerror would raise an exception */ + SPI_finish(); + PG_RETURN_NULL(); + } + + SPI_finish(); + + PG_RETURN_INT32(ret); +} diff --git a/topology/sql/sqlmm.sql.in b/topology/sql/sqlmm.sql.in index ed6cf1559..1437ac12b 100644 --- a/topology/sql/sqlmm.sql.in +++ b/topology/sql/sqlmm.sql.in @@ -1031,276 +1031,9 @@ LANGUAGE 'plpgsql' VOLATILE; -- -- }{ CREATE OR REPLACE FUNCTION topology.ST_RemEdgeModFace(toponame varchar, e1id integer) - RETURNS int -AS -$$ -DECLARE - e1rec RECORD; - rec RECORD; - fidary int[]; - topoid int; - sql text; - floodfaceid int; - elink int; -BEGIN - -- - -- toponame and face_id are required - -- - IF toponame IS NULL OR e1id IS NULL THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - null argument'; - END IF; - - -- Get topology id - BEGIN - SELECT id FROM topology.topology - INTO STRICT topoid WHERE name = toponame; - EXCEPTION - WHEN NO_DATA_FOUND THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; - END; - - BEGIN - EXECUTE 'SELECT * FROM ' || quote_ident(toponame) - || '.edge_data WHERE edge_id = ' || e1id - INTO STRICT e1rec; - EXCEPTION - WHEN NO_DATA_FOUND THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - non-existent edge %', e1id; - WHEN INVALID_SCHEMA_NAME THEN - RAISE EXCEPTION 'SQL/MM Spatial exception - invalid topology name'; - WHEN UNDEFINED_TABLE THEN - RAISE EXCEPTION 'corrupted topology "%" (missing edge_data table)', - toponame; - END; - - -- NOT IN THE SPECS: - -- Check that no TopoGeometry references the edge being removed - PERFORM topology._ST_RemEdgeCheck(toponame, topoid, e1id, e1rec.left_face, e1rec.right_face); - - -- Update next_left_edge and next_right_edge face - -- for all edges bounding the new face - RAISE NOTICE 'Updating next_{right,left}_face of ring edges...'; - - -- TODO: reduce the following to 2 UPDATE rather than 4 - - -- Update next_left_edge of previous edges in left face -- { - - elink := e1rec.next_left_edge; - - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET next_left_edge = ' - || elink - || ', abs_next_left_edge = ' - || abs(elink) - || ' WHERE next_left_edge < 0 AND abs(next_left_edge) = ' - || e1id; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'next_left_edge update: %', sql; -#endif - EXECUTE sql; - - -- If the edge being removed links to self, - -- we use the other face - IF e1rec.abs_next_right_edge = e1rec.edge_id THEN - elink := e1rec.next_left_edge; - ELSE - elink := e1rec.next_right_edge; - END IF; - - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET next_left_edge = ' - || elink - || ', abs_next_left_edge = ' - || abs(elink) - || ' WHERE next_left_edge > 0 AND abs(next_left_edge) = ' - || e1id; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'next_left_edge update: %', sql; -#endif - EXECUTE sql; - - -- } - - -- Update next_right_edge of previous edges in right face -- { - - elink := e1rec.next_left_edge; - - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET next_right_edge = ' - || elink - || ', abs_next_right_edge = ' - || abs(elink) - || ' WHERE next_right_edge < 0 AND abs(next_right_edge) = ' - || e1id; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'next_right_edge update: %', sql; -#endif - EXECUTE sql; - - -- If the edge being removed links to self, - -- we use the other face - IF e1rec.abs_next_right_edge = e1rec.edge_id THEN - elink := e1rec.next_left_edge; - ELSE - elink := e1rec.next_right_edge; - END IF; - - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET next_right_edge = ' - || elink - || ', abs_next_right_edge = ' - || abs(elink) - || ' WHERE next_right_edge > 0 AND abs(next_right_edge) = ' - || e1id; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'next_right_edge update: %', sql; -#endif - EXECUTE sql; - - -- } - - IF e1rec.left_face = e1rec.right_face THEN -- { - - floodfaceid = e1rec.left_face; - - ELSE -- }{ - - IF e1rec.left_face = 0 OR e1rec.right_face = 0 THEN -- { - - -- - -- We won't add any new face, but rather let the universe - -- flood the removed face. - -- - - floodfaceid = 0; - - ELSE -- }{ - - -- we choose right face as the face that will remain - -- to be symmetric with ST_AddEdgeModFace - floodfaceid = e1rec.right_face; - - sql := 'UPDATE ' - || quote_ident(toponame) - || '.face SET mbr = (SELECT ' - -- minimum bounding rectangle is the union of the old faces mbr - -- (doing this without GEOS would be faster) - || 'ST_Envelope(ST_Union(mbr)) FROM ' - || quote_ident(toponame) - || '.face WHERE face_id IN (' - || e1rec.left_face || ',' || e1rec.right_face - || ') ) WHERE face_id = ' || floodfaceid ; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'SQL: %', sql; -#endif - EXECUTE sql; - - END IF; -- } - - -- Update left_face for all edges still referencing old faces - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET left_face = ' || floodfaceid - || ' WHERE left_face IN (' - || e1rec.left_face || ',' || e1rec.right_face - || ')'; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'left_face update: %', sql; -#endif - EXECUTE sql; - - -- Update right_face for all edges still referencing old faces - sql := 'UPDATE ' || quote_ident(toponame) - || '.edge_data SET right_face = ' || floodfaceid - || ' WHERE right_face IN (' - || e1rec.left_face || ',' || e1rec.right_face - || ')'; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'right_face update: %', sql; -#endif - EXECUTE sql; - - -- Update containing_face for all nodes still referencing old faces - sql := 'UPDATE ' || quote_ident(toponame) - || '.node SET containing_face = ' || floodfaceid - || ' WHERE containing_face IN (' - || e1rec.left_face || ',' || e1rec.right_face - || ')'; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Isolated nodes update: %', sql; -#endif - EXECUTE sql; - - -- NOT IN THE SPECS: - -- Replace composition rows involving the two - -- faces as one involving the new face. - -- It takes a single DELETE to do that. - sql := 'DELETE FROM ' || quote_ident(toponame) - || '.relation r USING topology.layer l ' - || 'WHERE l.level = 0 AND l.feature_type = 3' - || ' AND l.topology_id = ' || topoid - || ' AND l.layer_id = r.layer_id AND abs(r.element_id) IN (' - || e1rec.left_face || ',' || e1rec.right_face - || ') AND abs(r.element_id) != ' - || floodfaceid; -- could be optimized.. -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'SQL: %', sql; -#endif - EXECUTE sql; - - END IF; -- } two faces healed... - - -- Delete the edge - sql := 'DELETE FROM ' || quote_ident(toponame) - || '.edge_data WHERE edge_id = ' || e1id; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Edge deletion: %', sql; -#endif - EXECUTE sql; - - -- Check if any of the edge nodes remains isolated, - -- set containing_face = floodfaceid in that case - sql := 'UPDATE ' || quote_ident(toponame) - || '.node n SET containing_face = ' || floodfaceid - || ' WHERE node_id IN (' - || e1rec.start_node || ',' - || e1rec.end_node || ') AND NOT EXISTS (SELECT edge_id FROM ' - || quote_ident(toponame) - || '.edge_data WHERE start_node = n.node_id OR end_node = n.node_id)'; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Checking for nodes left isolated: %', sql; -#endif - EXECUTE sql; - - IF e1rec.right_face != e1rec.left_face THEN -- { - - -- Delete left face, if not universe and not "flood" face - IF e1rec.left_face != 0 AND e1rec.left_face != floodfaceid - THEN - sql := 'DELETE FROM ' || quote_ident(toponame) - || '.face WHERE face_id = ' || e1rec.left_face; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Left face deletion: %', sql; -#endif - EXECUTE sql; - END IF; - - -- Delete right face, if not universe and not "flood" face - IF e1rec.right_face != 0 AND e1rec.right_face != floodfaceid - THEN - sql := 'DELETE FROM ' || quote_ident(toponame) - || '.face WHERE face_id = ' || e1rec.right_face; -#ifdef POSTGIS_TOPOLOGY_DEBUG - RAISE DEBUG 'Right face deletion: %', sql; -#endif - EXECUTE sql; - END IF; - - END IF; -- } - - RETURN floodfaceid; -END -$$ -LANGUAGE 'plpgsql' VOLATILE; + RETURNS int AS + 'MODULE_PATHNAME','ST_RemEdgeModFace' + LANGUAGE 'c' VOLATILE; --} ST_RemEdgeModFace -- 2.50.1