[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