[SCM] PostGIS branch stable-3.5 updated. 3.5.2-31-g507c7d251

git at osgeo.org git at osgeo.org
Thu Apr 24 09:30:20 PDT 2025


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".

The branch, stable-3.5 has been updated
       via  507c7d251f1028280e7f4f4a4f7e6274a3655f32 (commit)
      from  cf9783c57ca376cc7246c1c4b1c56e89cec59d8f (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 507c7d251f1028280e7f4f4a4f7e6274a3655f32
Author: Sandro Santilli <strk at kbt.io>
Date:   Thu Apr 24 16:14:20 2025 +0200

    Check motion range for edge-snapping with no collapses too
    
    Avoid silent topology corruptions.
    Includes tests.
    Closes #5862 in stable-3.5 branch (3.5.3dev)

diff --git a/NEWS b/NEWS
index 1d5ebd279..17f42b368 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,9 @@ PostgreSQL 12-18 required. GEOS 3.8+ required. Proj 6.1+ required.
 
 * Bug fixes *
 
-- #5841, Change approach to interrupt handling to conform to PgSQL 
+- #5862, [topology] Prevent another topology corruption with
+         TopoGeo_addPoint near almost collinear edges (Sandro Santilli)
+- #5841, Change approach to interrupt handling to conform to PgSQL
          recommended practice (Paul Ramsey)
 - #5855, Fix index binding in ST_DFullyWithin (Paul Ramsey)
 - #5819, Support longer names in estimated extent (Paul Ramsey)
diff --git a/liblwgeom/topo/lwgeom_topo.c b/liblwgeom/topo/lwgeom_topo.c
index 9833c23f8..f419aa821 100644
--- a/liblwgeom/topo/lwgeom_topo.c
+++ b/liblwgeom/topo/lwgeom_topo.c
@@ -5184,6 +5184,104 @@ _lwt_GetEqualEdge( LWT_TOPOLOGY *topo, LWLINE *edge, int *forward )
   return 0;
 }
 
+
+/**
+ * Check the motion of a snapped edge, invoke lwerror if the movement
+ * hits any other edge or node
+ *
+ * @param topo the Topology we are working on
+ * @param splitC the result of the edge being split
+ * @param edge the edge before the split/snap
+ * @param existingEdge an edge on which one component of the split edge collapsed, or null if no collapse happened.
+ * @param splitNodeEdges all edges attached to the split node
+ *
+ * TODO: check that newSplitEdgeLine retains its position in the edge end star (see ticket #5786)
+ * TODO: check that the motion range does not contain any node
+ *
+ */
+static void
+_lwt_SnapEdge_checkMotion( LWT_TOPOLOGY* topo, const LWCOLLECTION *splitC, const LWT_ISO_EDGE *edge, LWT_ISO_EDGE *existingEdge, const LWT_NODE_EDGES *splitNodeEdges )
+{
+  // build the motion range shape: splitC->geoms[0] + splitC->geoms[1] - edge->geom
+  POINTARRAY *motionRange = ptarray_clone_deep(lwgeom_as_lwline(splitC->geoms[0])->points);
+  ptarray_append_ptarray(motionRange, lwgeom_as_lwline(splitC->geoms[1])->points, 0);
+  POINTARRAY *reverseNewLine = ptarray_clone_deep(edge->geom->points);
+  ptarray_reverse_in_place(reverseNewLine);
+  ptarray_append_ptarray(motionRange, reverseNewLine, 0);
+  ptarray_free(reverseNewLine);
+
+  // motionBounds takes ownership of motionRange
+  LWLINE *motionBounds = lwline_construct(topo->srid, NULL, motionRange);
+
+  // motionPolyBare takes ownership of motionBounds
+  LWGEOM *motionPolyBare = (LWGEOM *)lwpoly_from_lwlines(motionBounds, 0, NULL);
+  LWGEOM *motionPoly = lwgeom_make_valid(motionPolyBare);
+  lwgeom_free(motionPolyBare);
+
+  LWDEBUGG(1, motionPoly, "Motion range");
+
+  // check the Motion range doesn't cover any of
+  // the edges incident to the split node other
+  // than the existing edge
+  GEOSGeometry *motionPolyG = NULL;
+  for ( uint64_t t=0; t<splitNodeEdges->numEdges; t++ )
+  {
+    LWT_ISO_EDGE *e = &(splitNodeEdges->edges[t]);
+    GEOSGeometry *eg;
+    if ( e == existingEdge ) continue;
+    if ( e == edge ) continue;
+    if ( ! motionPolyG ) {
+      motionPolyG = LWGEOM2GEOS( motionPoly, 0 );
+      if ( ! motionPolyG )
+      {
+        lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
+        return -1;
+      }
+    }
+    eg = LWGEOM2GEOS( lwline_as_lwgeom(e->geom), 0 );
+    if ( ! eg )
+    {
+      GEOSGeom_destroy(motionPolyG);
+      lwgeom_free(motionPoly);
+      lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
+      return -1;
+    }
+
+    char *relate = GEOSRelate( motionPolyG, eg );
+    GEOSGeom_destroy(eg);
+    if ( ! relate )
+    {
+      GEOSGeom_destroy(motionPolyG);
+      lwgeom_free(motionPoly);
+      lwerror("Relate error: %s", lwgeom_geos_errmsg);
+      return -1;
+    }
+
+    int match = GEOSRelatePatternMatch(relate, "FF*F*****");
+    GEOSFree( relate );
+    if (match == 2)
+    {
+      GEOSGeom_destroy(motionPolyG);
+      lwgeom_free(motionPoly);
+      lwerror("RelateMatch error: %s", lwgeom_geos_errmsg);
+      return -1;
+    }
+    if ( ! match )
+    {
+      GEOSGeom_destroy(motionPolyG);
+      lwgeom_free(motionPoly);
+      lwerror("snapping edge %" LWTFMT_ELEMID
+        " to new node moves it past edge %" LWTFMT_ELEMID,
+        edge->edge_id, e->edge_id
+      );
+      return -1;
+    }
+  }
+  if ( motionPolyG ) GEOSGeom_destroy(motionPolyG);
+
+  lwgeom_free(motionPoly);
+}
+
 static int
 _lwt_SnapEdgeToExistingNode(
   LWT_TOPOLOGY* topo,
@@ -5401,77 +5499,7 @@ _lwt_SnapEdgeToExistingNode(
       splitNodeNewEdgeOutgoing = 0;
     }
 
-    /* TODO: check that newSplitEdgeLine part does not crosses any other edge ? */
-    /* TODO: check that newSplitEdgeLine retains its position in the edge end star (see ticket #5786) */
-    /* TODO: check that the motion range does not contain any node */
-    {{
-      // build the motion range shape: splitC->geoms[0] + splitC->geoms[1] - edge->geom
-      POINTARRAY *motionRange = ptarray_clone_deep(lwgeom_as_lwline(splitC->geoms[0])->points);
-      ptarray_append_ptarray(motionRange, lwgeom_as_lwline(splitC->geoms[1])->points, 0);
-      POINTARRAY *reverseNewLine = ptarray_clone_deep(edge->geom->points);
-      ptarray_reverse_in_place(reverseNewLine);
-      ptarray_append_ptarray(motionRange, reverseNewLine, 0);
-      ptarray_free(reverseNewLine);
-
-      // motionBounds takes ownership of motionRange
-      LWLINE *motionBounds = lwline_construct(topo->srid, NULL, motionRange);
-
-      // motionPolyBare takes ownership of motionBounds
-      LWGEOM *motionPolyBare = (LWGEOM *)lwpoly_from_lwlines(motionBounds, 0, NULL);
-      LWGEOM *motionPoly = lwgeom_make_valid(motionPolyBare);
-      lwgeom_free(motionPolyBare);
-
-      LWDEBUGG(1, motionPoly, "Motion range");
-
-      // check the Motion range doesn't cover any of
-      // the edges incident to the split node other
-      // than the existing edge
-      GEOSGeometry *motionPolyG = NULL;
-      for ( uint64_t t=0; t<splitNodeEdges->numEdges; t++ )
-      {
-        LWT_ISO_EDGE *e = &(splitNodeEdges->edges[t]);
-        GEOSGeometry *eg;
-        if ( e == existingEdge ) continue;
-        if ( e == edge ) continue;
-        if ( ! motionPolyG ) {
-          motionPolyG = LWGEOM2GEOS( motionPoly, 0 );
-          if ( ! motionPolyG )
-          {
-            lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
-            return -1;
-          }
-        }
-        eg = LWGEOM2GEOS( lwline_as_lwgeom(e->geom), 0 );
-        if ( ! eg )
-        {
-          lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg);
-          return -1;
-        }
-
-        int covers = GEOSCovers( motionPolyG, eg );
-        // TODO: use preparedCovers ?
-        GEOSGeom_destroy(eg);
-        if (covers == 2)
-        {
-          lwerror("Covers error: %s", lwgeom_geos_errmsg);
-          return -1;
-        }
-        if ( covers )
-        {
-          lwgeom_free(motionPoly);
-          lwerror("snapping edge %" LWTFMT_ELEMID
-            " to new node moves it past edge %" LWTFMT_ELEMID,
-            edge->edge_id, e->edge_id
-          );
-          return -1;
-        }
-      }
-      if ( motionPolyG ) GEOSGeom_destroy(motionPolyG);
-
-      lwgeom_free(motionPoly);
-    }}
-
-
+    _lwt_SnapEdge_checkMotion( topo, splitC, edge, existingEdge, splitNodeEdges );
 
     LWDEBUGF(1, "Existing edge %"
         LWTFMT_ELEMID " (post-modEdgeSplit) next_right:%"
@@ -5953,6 +5981,9 @@ _lwt_SnapEdgeToExistingNode(
   }
   else if ( replacedBy[0] == 0 && replacedBy[1] == 0 )
   {
+
+    /* Neither sides of the snapped edge collapsed to an existing edge */
+
     /* New edge is the outgoing one, by design */
     LWT_ISO_EDGE newEdge;
     newEdge.edge_id = lwt_be_getNextEdgeId( topo );
@@ -5987,6 +6018,8 @@ _lwt_SnapEdgeToExistingNode(
     lwt_edgeEndStar_addEdge( nodeStar, &updatedEdge );
     lwt_edgeEndStar_addEdge( nodeStar, &newEdge );
 
+    _lwt_SnapEdge_checkMotion( topo, splitC, edge, NULL, splitNodeEdges );
+
     /* There cannot be anything in the middle of the two components,
      * so both sides will give the same nextCCW and same nextCW */
 
diff --git a/topology/test/regress/topogeo_addpoint_merge_edges.sql b/topology/test/regress/topogeo_addpoint_merge_edges.sql
index 65f996a15..cf153c441 100644
--- a/topology/test/regress/topogeo_addpoint_merge_edges.sql
+++ b/topology/test/regress/topogeo_addpoint_merge_edges.sql
@@ -440,4 +440,12 @@ SELECT * FROM runTest('#5792.1',
   ], 'POINT(11.812029186127067 59.7793864213727)', 0
 ) WHERE true ;
 
+-- See https://trac.osgeo.org/postgis/ticket/5862
+SELECT * FROM runTest('#5862',
+  ARRAY[
+    'LINESTRING(22.780107846871616 70.70515928614921, 22.779899976871615 70.7046262461492)',
+    'LINESTRING(22.792170566871620 70.70247684614921, 22.779969266871618 70.70480392614921, 22.780038556871617 70.7049816061492, 22.796764346871615 70.7044482361492)'
+  ], 'POINT(22.780038556871617 70.7049816061492)', 0
+) WHERE true ;
+
 DROP FUNCTION runTest(text, geometry[], geometry, float8, bool);
diff --git a/topology/test/regress/topogeo_addpoint_merge_edges_expected b/topology/test/regress/topogeo_addpoint_merge_edges_expected
index 994187546..706a9b959 100644
--- a/topology/test/regress/topogeo_addpoint_merge_edges_expected
+++ b/topology/test/regress/topogeo_addpoint_merge_edges_expected
@@ -31,3 +31,4 @@ multi-merge-forward-backward|-checking-
 multi-merge-closest-not-containing-projected|-checking-
 ERROR:  snapping edge 2 to new node moves it past edge 3
 ERROR:  snapping edge 2 to new node moves it past edge 3
+ERROR:  snapping edge 2 to new node moves it past edge 3

-----------------------------------------------------------------------

Summary of changes:
 NEWS                                               |   4 +-
 liblwgeom/topo/lwgeom_topo.c                       | 175 ++++++++++++---------
 .../test/regress/topogeo_addpoint_merge_edges.sql  |   8 +
 .../regress/topogeo_addpoint_merge_edges_expected  |   1 +
 4 files changed, 116 insertions(+), 72 deletions(-)


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list