Implement ST_RemEdgeModFace in C
authorSandro Santilli <strk@keybit.net>
Sun, 16 Aug 2015 21:34:15 +0000 (21:34 +0000)
committerSandro Santilli <strk@keybit.net>
Sun, 16 Aug 2015 21:34:15 +0000 (21:34 +0000)
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
liblwgeom/liblwgeom_topo.h
liblwgeom/lwgeom_topo.c
topology/postgis_topology.c
topology/sql/sqlmm.sql.in

index b1bce6547b82e483ec28f345f24d0d0e315761f0..f0dd96e15ff1cb6365a1e5f7abea02804f522b1d 100644 (file)
@@ -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
 
index a8aa2d5ca52b5f9c247d30d0b4cf102069ce528e..51d8b3fb8cf22bf57aa37c81df8e887666972127 100644 (file)
@@ -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);
index 3255819890be4869ba0f06bb86baacf1e3a3d370..00069eca14673e9452343682f95950d741c0902c 100644 (file)
@@ -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; 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 ) {
-    lwline_release(edges[i].geom);
+    if ( edges[i].geom ) lwline_release(edges[i].geom);
   }
   lwfree(edges);
 }
@@ -383,7 +408,7 @@ _lwt_release_nodes(LWT_ISO_NODE *nodes, int num_nodes)
 {
   int i;
   for ( i=0; i<num_nodes; ++i ) {
-    lwpoint_release(nodes[i].geom);
+    if ( nodes[i].geom ) lwpoint_release(nodes[i].geom);
   }
   lwfree(nodes);
 }
@@ -870,6 +895,7 @@ _lwt_EdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipISOChe
       lwerror("Backend coding error: getEdgeById callback returned NULL "
               "but numelements output parameter has value %d "
               "(expected 0 or 1)", i);
+      return NULL;
     }
   }
 
@@ -3026,6 +3052,7 @@ lwt_ChangeEdgeGeom(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, LWLINE *geom)
       lwerror("Backend coding error: getEdgeById callback returned NULL "
               "but numelements output parameter has value %d "
               "(expected 0 or 1)", i);
+      return -1;
     }
   }
 
@@ -3445,3 +3472,429 @@ lwt_RemoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid)
 
   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 */
+static LWT_ELEMID
+_lwt_RemEdge( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, int modFace )
+{
+  int 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 ) */
+
+  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 == -1 )
+    {
+      lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    else if ( i == 0 )
+    {
+      lwerror("SQL/MM Spatial exception - non-existent edge %"
+              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; 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 */
+    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; 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);
+          _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 );
+}
index 1a10f70e78eefcbe3bc68afbb0ec507639ad385c..ef38ce4b64d148196d15bc3a42a99ace63c0b013 100644 (file)
@@ -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);
+}
index ed6cf155940a19c797d60dd68fc40ac2c258b1b5..1437ac12bfaddd1f0a4920a8abd97f38f5a789cc 100644 (file)
@@ -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