[SCM] PostGIS branch master updated. 3.6.0rc2-55-gfda22140e
git at osgeo.org
git at osgeo.org
Tue Sep 23 09:34:40 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, master has been updated
via fda22140ee28de287fd61bb943cad155dc277ee2 (commit)
from e587b6f655b37d925edcd7df08d77cf9e76295de (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 fda22140ee28de287fd61bb943cad155dc277ee2
Author: Sandro Santilli <strk at kbt.io>
Date: Mon Sep 22 17:38:53 2025 +0200
Add max_edges parameter to TopoGeo_AddLinestring
Includes documentation and regression tests
Closes #5993
diff --git a/NEWS b/NEWS
index 980a3c7b4..47a5314c6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,11 @@
PostGIS 3.7.0dev
xxxx/xx/xx
+* New Features *
+
+ - #5993, [topology] Add max_edges parameter to TopoGeo_AddLinestring
+ (Sandro Santilli)
+
PostGIS 3.6.0
2025/09/01
diff --git a/doc/extras_topology.xml b/doc/extras_topology.xml
index 3854a4a93..edd46320d 100644
--- a/doc/extras_topology.xml
+++ b/doc/extras_topology.xml
@@ -1669,6 +1669,7 @@ Adds a linestring to an existing topology using a tolerance and possibly splitti
<paramdef><type>varchar </type> <parameter>atopology</parameter></paramdef>
<paramdef><type>geometry </type> <parameter>aline</parameter></paramdef>
<paramdef choice="opt"><type>float8 </type> <parameter>tolerance</parameter></paramdef>
+ <paramdef choice="opt"><type>int </type> <parameter>max_edges</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
@@ -1678,10 +1679,24 @@ Adds a linestring to an existing topology using a tolerance and possibly splitti
<para>
Adds a linestring to an existing topology and returns a set of signed edge identifiers forming it up
-(negative identifies mean the edge goes in the opposite direction of
-the input linestring).
+(negative identifies mean the edge goes in the opposite direction of the input linestring).
+ </para>
+
+ <para>
The given line will snap to existing nodes or edges within given tolerance.
-Existing edges and faces may be split by the line. New nodes and faces may be added.
+Existing edges and faces may be split by the line. New nodes and faces may be added, in addition to new edges.
+ </para>
+
+ <para>
+The returned edge identifiers may be either existing edges or newly
+created edges as needed to fully represent the input line as closely
+as possible.
+ </para>
+
+<para>
+The number of newly created edges (either covering space previously
+uncovered or resulting from split of existing edges) may be limited
+by the <varname>max_edges</varname> parameter.
</para>
<note><para>
@@ -1692,6 +1707,7 @@ up to caller, see <xref linkend="Topology_StatsManagement"/>.
<!-- use this format if new function -->
<para role="availability" conformance="2.0.0">Availability: 2.0.0</para>
<para role="enhanced" conformance="3.2.0">Enhanced: 3.2.0 added support for returning signed identifier.</para>
+ <para role="enhanced" conformance="3.7.0">Enhanced: 3.7.0 added support for limiting the number of new edges created in the topology.</para>
</refsection>
diff --git a/liblwgeom/topo/liblwgeom_topo.h b/liblwgeom/topo/liblwgeom_topo.h
index b1278aaf5..65c9f4efb 100644
--- a/liblwgeom/topo/liblwgeom_topo.h
+++ b/liblwgeom/topo/liblwgeom_topo.h
@@ -1060,13 +1060,17 @@ LWT_ELEMID lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol);
* @param nedges output parameter, will be set to number of edges the
* line was split into, or -1 on error
* (liblwgeom error handler will be invoked with error message)
+ * @param max_edges the maximum number of edges to allow the input
+ * line to be split into. 0 for no limits.
+ * An error will be returned if number of edges would
+ * exceed this number.
*
* @return an array of <nedges> edge identifiers that sewed together
* will build up the input linestring (after snapping). Caller
* will need to free the array using lwfree(), if not null.
*/
LWT_ELEMID* lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol,
- int* nedges);
+ int* nedges, int max_edges);
/**
* Adds a linestring to the topology without determining generated faces
diff --git a/liblwgeom/topo/lwgeom_topo.c b/liblwgeom/topo/lwgeom_topo.c
index 7f2df42c7..c0d6d5356 100644
--- a/liblwgeom/topo/lwgeom_topo.c
+++ b/liblwgeom/topo/lwgeom_topo.c
@@ -6747,10 +6747,12 @@ _lwt_SplitAllEdgesToNewNode(LWT_TOPOLOGY* topo, LWT_ISO_EDGE *edges, uint64_t nu
* isolated)
* @param moved if not-null will be set to 0 if the point was added
* w/out any snapping or 1 otherwise.
+ * @param numSplitEdges if not-null will be set to the number of edges
+ * split by this point.
*/
static LWT_ELEMID
_lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int
- findFace, int *moved)
+ findFace, int *moved, int *numSplitEdges)
{
uint64_t num, i;
double mindist = FLT_MAX;
@@ -6779,6 +6781,9 @@ _lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int
PGTOPO_BE_ERROR();
return -1;
}
+
+ if ( numSplitEdges ) *numSplitEdges = 0;
+
if ( num )
{
LWDEBUGF(1, "New point is within %.15g units of %llu nodes", tol, num);
@@ -6846,6 +6851,7 @@ _lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int
{
id = _lwt_SplitAllEdgesToNewNode(topo, edges, num, lwgeom_as_lwpoint(pt), tol, moved);
_lwt_release_edges(edges, num);
+ if ( numSplitEdges ) *numSplitEdges = num;
}
if ( id == 0 )
@@ -6869,7 +6875,7 @@ _lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int
LWT_ELEMID
lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol)
{
- return _lwt_AddPoint(topo, point, tol, 1, NULL);
+ return _lwt_AddPoint(topo, point, tol, 1, NULL, NULL);
}
/*
@@ -6881,15 +6887,21 @@ lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol)
* 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
+ * @param forward output parameter, will be set to 1
+ * if the returned edge has the same direction
+ * as the requested line and 0 if it goes the
+ * opposite direction. For new edges this will
+ * always be 1.
+ *
+ * @param numNewEdges output parameter, if not-null will be set to
+ * the number of new edges resulting from this
+ * incoming new edge, taking into account edges
+ * created due to splitting of existing edges.
+ *
*/
static LWT_ELEMID
_lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
- int handleFaceSplit, int *forward )
+ int handleFaceSplit, int *forward, int *numNewEdges )
{
LWCOLLECTION *col;
LWPOINT *start_point, *end_point;
@@ -6900,6 +6912,9 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
POINT4D p4d;
uint64_t nn, i;
int moved=0, mm;
+ int pointSplitEdges = -666;
+
+ if ( numNewEdges ) *numNewEdges = 0;
LWDEBUGG(1, lwline_as_lwgeom(edge), "_lwtAddLineEdge");
LWDEBUGF(1, "_lwtAddLineEdge with tolerance %g", tol);
@@ -6912,11 +6927,12 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
}
nid[0] = _lwt_AddPoint( topo, start_point,
_lwt_minTolerance(lwpoint_as_lwgeom(start_point)),
- handleFaceSplit, &mm );
+ handleFaceSplit, &mm, &pointSplitEdges );
lwpoint_free(start_point); /* too late if lwt_AddPoint calls lwerror */
if ( nid[0] == -1 ) return -1; /* lwerror should have been called */
+ if ( numNewEdges ) *numNewEdges += pointSplitEdges;
moved += mm;
- LWDEBUGF(1, "node for start point added or found to be %" LWTFMT_ELEMID " (moved ? %d)", nid[0], mm);
+ LWDEBUGF(1, "node for start point added or found to be %" LWTFMT_ELEMID " (moved ? %d; split %d edges)", nid[0], mm, pointSplitEdges);
end_point = lwline_get_lwpoint(edge, edge->points->npoints-1);
@@ -6928,11 +6944,12 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
}
nid[1] = _lwt_AddPoint( topo, end_point,
_lwt_minTolerance(lwpoint_as_lwgeom(end_point)),
- handleFaceSplit, &mm );
+ handleFaceSplit, &mm, &pointSplitEdges );
lwpoint_free(end_point); /* too late if lwt_AddPoint calls lwerror */
if ( nid[1] == -1 ) return -1; /* lwerror should have been called */
+ if ( numNewEdges ) *numNewEdges += pointSplitEdges;
moved += mm;
- LWDEBUGF(1, "node for end point added or found to be %" LWTFMT_ELEMID " (moved ? %d)", nid[1], mm);
+ LWDEBUGF(1, "node for end point added or found to be %" LWTFMT_ELEMID " (moved ? %d; split %d edges)", nid[1], mm, pointSplitEdges);
/*
-- Added endpoints may have drifted due to tolerance, so
@@ -7037,7 +7054,8 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
return id;
}
- /* No previously existing edge was found, we'll add one */
+ /* No previously existing edge was found, we'll check after
+ * decimating, if a tolerance was given */
/* Remove consecutive vertices below given tolerance
* on edge addition */
@@ -7072,6 +7090,7 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
}
}}
+ /* No previously existing edge was found, we'll add one */
/* TODO: skip checks ? */
id = _lwt_AddEdge( topo, nid[0], nid[1], edge, 0, handleFaceSplit ? 1 : -1 );
@@ -7083,7 +7102,9 @@ _lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol,
}
lwgeom_free(tmp); /* possibly takes "edge" down with it */
+ if ( numNewEdges ) ++*numNewEdges;
*forward = 1;
+
return id;
}
@@ -7113,7 +7134,7 @@ _lwt_split_by_nodes(const LWGEOM *g, const LWGEOM *nodes)
static LWT_ELEMID*
_lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
- int handleFaceSplit)
+ int handleFaceSplit, int maxNewEdges)
{
LWGEOM *geomsbuf[1];
LWGEOM **geoms;
@@ -7124,6 +7145,7 @@ _lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
LWT_ISO_EDGE *edges;
LWT_ISO_NODE *nodes;
uint64_t num, numedges = 0, numnodes = 0;
+ int num_new_edges = 0;
uint64_t i;
GBOX qbox;
int forward;
@@ -7453,6 +7475,7 @@ _lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
num = 0;
for ( i=0; i<ngeoms; ++i )
{
+ int edgeNewEdges;
LWT_ELEMID id;
LWGEOM *g = geoms[i];
g->srid = noded->srid;
@@ -7466,14 +7489,33 @@ _lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
}
#endif
- id = _lwt_AddLineEdge( topo, lwgeom_as_lwline(g), tol, handleFaceSplit, &forward );
- LWDEBUGF(1, "_lwt_AddLineEdge returned %" LWTFMT_ELEMID, id);
+ forward = -1; /* will be set to either 0 or 1 if the edge already existed */
+ id = _lwt_AddLineEdge( topo, lwgeom_as_lwline(g), tol, handleFaceSplit, &forward, &edgeNewEdges );
+ num_new_edges += edgeNewEdges;
+ /* if forward is still == -1 this was NOT an existing edge ? */
+ if ( forward == -1 )
+ {
+ ++num_new_edges;
+ }
+
+ LWDEBUGF(1, "_lwt_AddLineEdge returned %" LWTFMT_ELEMID
+ " (forward ? %d), reported to create %d new edges (total new edges: %d)",
+ id, forward, edgeNewEdges, num_new_edges);
if ( id < 0 )
{
lwgeom_free(noded);
lwfree(ids);
return NULL;
}
+
+ if ( maxNewEdges >= 0 && num_new_edges > maxNewEdges )
+ {
+ lwgeom_free(noded);
+ lwfree(ids);
+ lwerror("Adding line to topology requires creating more edges than the requested limit of %d", maxNewEdges);
+ return NULL;
+ }
+
if ( ! id )
{
LWDEBUGF(1, "Component %llu of split line collapsed", i);
@@ -7495,31 +7537,31 @@ _lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges,
}
LWT_ELEMID*
-lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges)
+lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges, int max_new_edges)
{
- return _lwt_AddLine(topo, line, tol, nedges, 1);
+ return _lwt_AddLine(topo, line, tol, nedges, 1, max_new_edges);
}
LWT_ELEMID*
lwt_AddLineNoFace(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges)
{
- return _lwt_AddLine(topo, line, tol, nedges, 0);
+ return _lwt_AddLine(topo, line, tol, nedges, 0, -1);
}
static void
lwt_LoadPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol)
{
- _lwt_AddPoint(topo, point, tol, 1, NULL);
+ _lwt_AddPoint(topo, point, tol, 1, NULL, NULL);
}
static void
-lwt_LoadLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol)
+lwt_LoadLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int max_new_edges)
{
LWT_ELEMID* ids;
int nedges;
/* TODO: avoid allocating edge ids */
- ids = lwt_AddLine(topo, line, tol, &nedges);
+ ids = lwt_AddLine(topo, line, tol, &nedges, max_new_edges);
if ( nedges > 0 ) lwfree(ids);
}
@@ -7537,7 +7579,7 @@ lwt_LoadPolygon(LWT_TOPOLOGY* topo, const LWPOLY* poly, double tol)
/* TODO: avoid the clone here */
pa = ptarray_clone(poly->rings[i]);
line = lwline_construct(topo->srid, NULL, pa);
- lwt_LoadLine(topo, line, tol);
+ lwt_LoadLine(topo, line, tol, -1);
lwline_free(line);
}
}
@@ -8035,7 +8077,7 @@ _lwt_LoadGeometryRecursive(LWT_TOPOLOGY* topo, LWGEOM* geom, double tol)
return;
case LINETYPE:
- lwt_LoadLine(topo, lwgeom_as_lwline(geom), tol);
+ lwt_LoadLine(topo, lwgeom_as_lwline(geom), tol, -1);
return;
case POLYGONTYPE:
diff --git a/topology/postgis_topology.c b/topology/postgis_topology.c
index 4a8b82438..a244e8974 100644
--- a/topology/postgis_topology.c
+++ b/topology/postgis_topology.c
@@ -5087,6 +5087,7 @@ Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS)
GSERIALIZED *geom;
LWGEOM *lwgeom;
LWLINE *ln;
+ int max_new_edges = -1; /* defaults to unlimited */
LWT_TOPOLOGY *topo;
FuncCallContext *funcctx;
MemoryContext oldcontext, newcontext;
@@ -5106,6 +5107,11 @@ Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
+ if (PG_NARGS() > 3 && !PG_ARGISNULL(3))
+ {
+ max_new_edges = PG_GETARG_INT32(3);
+ }
+
toponame_text = PG_GETARG_TEXT_P(0);
toponame = text_to_cstring(toponame_text);
PG_FREE_IF_COPY(toponame_text, 0);
@@ -5166,7 +5172,7 @@ Datum TopoGeo_AddLinestring(PG_FUNCTION_ARGS)
}
POSTGIS_DEBUG(1, "Calling lwt_AddLine");
- elems = lwt_AddLine(topo, ln, tol, &nelems);
+ elems = lwt_AddLine(topo, ln, tol, &nelems, max_new_edges);
POSTGIS_DEBUG(1, "lwt_AddLine returned");
lwgeom_free(lwgeom);
PG_FREE_IF_COPY(geom, 1);
diff --git a/topology/sql/populate.sql.in b/topology/sql/populate.sql.in
index 87506e36c..d24ef61db 100644
--- a/topology/sql/populate.sql.in
+++ b/topology/sql/populate.sql.in
@@ -741,7 +741,7 @@ CREATE OR REPLACE FUNCTION topology.TopoGeo_AddPoint(atopology varchar, apoint g
--
-- Changed: 3.6.0 uses bigint for IDs
-- Replaces topogeo_addlinestring(varchar, geometry, float8) deprecated in 3.6.0
-CREATE OR REPLACE FUNCTION topology.TopoGeo_AddLinestring(atopology varchar, aline geometry, tolerance float8 DEFAULT 0)
+CREATE OR REPLACE FUNCTION topology.TopoGeo_AddLinestring(atopology varchar, aline geometry, tolerance float8 DEFAULT 0, max_edges int DEFAULT -1)
RETURNS SETOF bigint AS
'MODULE_PATHNAME', 'TopoGeo_AddLinestring'
LANGUAGE 'c' VOLATILE;
diff --git a/topology/test/regress/topogeo_addlinestring.sql b/topology/test/regress/topogeo_addlinestring.sql
index 1b35ba150..7acb1f350 100644
--- a/topology/test/regress/topogeo_addlinestring.sql
+++ b/topology/test/regress/topogeo_addlinestring.sql
@@ -556,3 +556,36 @@ SELECT NULL FROM topology.TopoGeo_addLinestring('t5782',
);
SELECT '#5782', 'valid_after', * FROM topology.ValidateTopology('t5782');
ROLLBACK;
+
+-- See https://trac.osgeo.org/postgis/ticket/5993
+SELECT NULL FROM topology.CreateTopology ('t5993');
+SELECT NULL FROM topology.TopoGeo_addLinestring('t5993',
+'LINESTRING(0 2, 100 2)'
+);
+-- We expect this to succeed, as we added no new edges
+SELECT '#5993.0', 'existing-data', count(*) FROM topology.TopoGeo_addLinestring('t5993',
+ 'LINESTRING(0 2,100 2)', max_edges => 0
+);
+-- This should fail because the existing edge is split
+-- creating a new edge while we limit to 0
+SELECT '#5993.1', 'single-split', count(*) FROM topology.TopoGeo_addLinestring('t5993',
+ 'LINESTRING(0 2,80 2)', max_edges => 0
+);
+-- This should fail because the existing edge is split
+-- creating two new edge while we limit to 1
+SELECT '#5993.2', 'double-split-off-limit', count(*) FROM topology.TopoGeo_addLinestring('t5993',
+ 'LINESTRING(10 2,80 2)', max_edges => 1
+);
+-- This should succeed because the existing edge is split
+-- creating two new edge while we limit to 1
+SELECT '#5993.3', 'double-split-in-limit', count(*) FROM topology.TopoGeo_addLinestring('t5993',
+ 'LINESTRING(10 2,80 2)', max_edges => 2
+);
+-- This should fail because the existing edge is snap-split
+-- creating two new edge while we limit to 1
+SELECT '#5993.4', 'snap-split-off-limit', * FROM topology.TopoGeo_addLinestring('t5993',
+ ST_MakeLine(ST_MakePoint(5, 0), ST_MakePoint(5, 2 - 1e-20)),
+ max_edges => 1,
+ tolerance => 2
+);
+SELECT NULL FROM topology.DropTopology ('t5993');
diff --git a/topology/test/regress/topogeo_addlinestring_expected b/topology/test/regress/topogeo_addlinestring_expected
index 6757356c1..a9ecae7b9 100644
--- a/topology/test/regress/topogeo_addlinestring_expected
+++ b/topology/test/regress/topogeo_addlinestring_expected
@@ -228,3 +228,8 @@ b5234.0|1
b5234.1|1
t5568|invalidities at start|
t5568|invalidities at end|
+#5993.0|existing-data|1
+ERROR: Adding line to topology requires creating more edges than the requested limit of 0
+ERROR: Adding line to topology requires creating more edges than the requested limit of 1
+#5993.3|double-split-in-limit|1
+ERROR: Adding line to topology requires creating more edges than the requested limit of 1
-----------------------------------------------------------------------
Summary of changes:
NEWS | 5 ++
doc/extras_topology.xml | 22 +++++-
liblwgeom/topo/liblwgeom_topo.h | 6 +-
liblwgeom/topo/lwgeom_topo.c | 90 ++++++++++++++++------
topology/postgis_topology.c | 8 +-
topology/sql/populate.sql.in | 2 +-
topology/test/regress/topogeo_addlinestring.sql | 33 ++++++++
.../test/regress/topogeo_addlinestring_expected | 5 ++
8 files changed, 141 insertions(+), 30 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list