[SCM] PostGIS branch master updated. 3.7.0rc1-95-g33904db91

git at osgeo.org git at osgeo.org
Wed Oct 7 00:39:35 PDT 2026


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  33904db915bb3c2ff0f23a69f047bfdb5b93fcc0 (commit)
      from  0264638140f7c516a5dca4387e7c742822488c3f (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 33904db915bb3c2ff0f23a69f047bfdb5b93fcc0
Author: Sandro Santilli <strk at kbt.io>
Date:   Wed Sep 30 11:06:26 2026 +0200

    Add ST_IsSimpleDetail
    
    Includes documentation and regress test
    Relies on GEOS-3.16 new function GEOSisSimpleWithParams
    
    Closes #6131

diff --git a/NEWS b/NEWS
index ce2670387..3c03c50db 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,7 @@ PostgreSQL instance.
 
 * New Features *
 
+ - #6132, ST_IsSimpleDetail (Sandro Santilli)
  - #1124, #2935, #3033, #4658, #4659, [loader] Rework loader arguments:
           shp2pgsql can create UNLOGGED tables and choose the feature id column
           name, shp2pgsql --drop-table can emit DROP TABLE before prepare
diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml
index a82717960..df968563b 100644
--- a/doc/reference_accessor.xml
+++ b/doc/reference_accessor.xml
@@ -2004,6 +2004,72 @@ FROM (SELECT 'LINESTRING(0 0,0 1,1 0,1 1,0 0)'::geometry AS geom) AS foo;</progr
 		</refsection>
 	</refentry>
 
+  <refentry xml:id="ST_IsSimpleDetail">
+	  <refnamediv>
+		<refname>ST_IsSimpleDetail</refname>
+
+		<refpurpose>Report locations of self-intersection or self-tangency, if any.</refpurpose>
+	  </refnamediv>
+
+	  <refsynopsisdiv>
+		<funcsynopsis>
+		  <funcprototype>
+			<funcdef>simple_detail <function>ST_IsSimpleDetail</function></funcdef>
+			<paramdef><type>geometry </type> <parameter>geom</parameter></paramdef>
+			<paramdef choice="opt"><type>integer </type> <parameter>findAllLocations = 0</parameter></paramdef>
+			<paramdef choice="opt"><type>integer </type> <parameter>boundaryNodeRule = 1</parameter></paramdef>
+		  </funcprototype>
+		</funcsynopsis>
+	  </refsynopsisdiv>
+
+	  <refsection>
+		<title>Description</title>
+
+		<para>Returns a <varname>simple_detail</varname> row,
+        containing a boolean (<varname>simple</varname>) stating if a geometry is simple
+        and a geometry (<varname>location</varname>) pointing out where it is non-simple.</para>
+
+		<para>
+The optional <varname>findAllLocations</varname> parameter can be set to 1 to report all non-simple locations, rather than just one.
+        </para>
+
+        <para>
+The optional <varname>boundaryNodeRule</varname> values are:
+            </para>
+        <itemizedlist>
+            <listitem><para><code>1</code>: <emphasis role="bold">OGC-Mod2</emphasis> - line endpoints are in the Boundary if they occur an odd number of times.
+                This is the rule defined by the OGC SFS standard, and is the default for <function>ST_Relate</function>.
+                </para></listitem>
+            <listitem><para><code>2</code>: <emphasis role="bold">Endpoint</emphasis> - all endpoints are in the Boundary.
+                </para></listitem>
+            <listitem><para><code>3</code>: <emphasis role="bold">MultivalentEndpoint</emphasis> - endpoints are in the Boundary if they occur more than once.
+                In other words, the boundary is all the "attached" or "inner" endpoints (but not the "unattached/outer" ones).
+                </para></listitem>
+            <listitem><para><code>4</code>: <emphasis role="bold">MonovalentEndpoint</emphasis> - endpoints are in the Boundary if they occur only once.
+                In other words, the boundary is all the "unattached" or "outer" endpoints.
+                </para></listitem>
+        </itemizedlist>
+
+		<para role="availability" conformance="3.7.0">Availability: 3.7.0</para>
+
+	  </refsection>
+
+	  <refsection>
+		<title>Examples</title>
+
+		<programlisting>SELECT ST_IsSimple('POLYGON((1 2,3 4,5 6,1 2))');</programlisting>
+<screen>f</screen>
+<programlisting>SELECT ST_IsSimple('LINESTRING(1 1,2 2,2 3.5,1 3,1 2,2 1)');</programlisting>
+<screen>f</screen>
+		</refsection>
+
+		<refsection>
+			<title>See Also</title>
+
+			<para><xref linkend="ST_IsSimple"/></para>
+		</refsection>
+	</refentry>
+
 	<refentry xml:id="ST_M">
 	  <refnamediv>
 		<refname>ST_M</refname>
diff --git a/postgis/lwgeom_geos.c b/postgis/lwgeom_geos.c
index 89d0501f5..9bd495bae 100644
--- a/postgis/lwgeom_geos.c
+++ b/postgis/lwgeom_geos.c
@@ -53,6 +53,7 @@
 Datum isvalid(PG_FUNCTION_ARGS);
 Datum isvalidreason(PG_FUNCTION_ARGS);
 Datum isvaliddetail(PG_FUNCTION_ARGS);
+Datum issimpledetail(PG_FUNCTION_ARGS);
 Datum buffer(PG_FUNCTION_ARGS);
 Datum ST_Intersection(PG_FUNCTION_ARGS);
 Datum convexhull(PG_FUNCTION_ARGS);
@@ -1782,6 +1783,92 @@ Datum issimple(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(result);
 }
 
+PG_FUNCTION_INFO_V1(issimpledetail);
+Datum issimpledetail(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_GEOS_VERSION < 31600
+	lwpgerror("The GEOS version this PostGIS binary "
+				"was compiled against (%d) doesn't support "
+				"'GEOSisSimpleWithParams' function (3.16.0+ required)",
+				POSTGIS_GEOS_VERSION);
+	PG_RETURN_NULL();
+#else /* POSTGIS_GEOS_VERSION >= 31600 */
+	GSERIALIZED *geom = NULL;
+	const GEOSGeometry *g1 = NULL;
+	char *values[2]; /* simple bool, location geometry */
+	GEOSGeometry *geos_location = NULL;
+	LWGEOM *location = NULL;
+	int8_t simple = 0;
+	HeapTupleHeader result;
+	TupleDesc tupdesc;
+	HeapTuple tuple;
+	AttInMetadata *attinmeta;
+	int findAllLocations = 0;
+	int boundaryNodeRule = 0;
+
+	/*
+	 * Build a tuple description for a
+	 * simple_detail tuple
+	 */
+	get_call_result_type(fcinfo, 0, &tupdesc);
+	BlessTupleDesc(tupdesc);
+
+	/*
+	 * generate attribute metadata needed later to produce
+	 * tuples from raw C strings
+	 */
+	attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+	geom = PG_GETARG_GSERIALIZED_P(0);
+	findAllLocations = PG_GETARG_INT32(1);
+	boundaryNodeRule = PG_GETARG_INT32(2);
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	g1 = POSTGIS2GEOS(geom);
+
+	if ( ! g1 )
+	{
+		lwpgerror("Geometry could not be converted to GEOS: %s", lwgeom_geos_errmsg);
+		PG_RETURN_NULL();
+	}
+
+	{
+		GEOSisSimpleParams *params = GEOSisSimpleParams_create();
+		GEOSisSimpleParams_setBoundaryNodeRule(params, boundaryNodeRule);
+		GEOSisSimpleParams_setFindAllLocations(params, findAllLocations);
+		simple = GEOSisSimpleWithParams(g1, params, &geos_location);
+		GEOSisSimpleParams_destroy(params);
+		GEOSGeom_destroy((GEOSGeometry *)g1);
+	}
+
+	if (simple == 2)
+	{
+		/* NOTE: should only happen on OOM or similar */
+		lwpgerror("GEOSisSimpleDetail threw an exception!");
+		PG_RETURN_NULL(); /* never gets here */
+	}
+	if ( geos_location )
+	{
+		location = GEOS2LWGEOM(geos_location, GEOSHasZ(geos_location));
+		GEOSGeom_destroy(geos_location);
+	}
+
+	/* the boolean simplicity */
+	values[0] =  simple ? "t" : "f";
+
+	/* the location */
+	values[1] = location ? lwgeom_to_hexwkb_buffer(location, WKB_EXTENDED) : 0;
+
+	tuple = BuildTupleFromCStrings(attinmeta, values);
+	result = (HeapTupleHeader) palloc(tuple->t_len);
+	memcpy(result, tuple->t_data, tuple->t_len);
+	heap_freetuple(tuple);
+
+	PG_RETURN_HEAPTUPLEHEADER(result);
+#endif /* POSTGIS_GEOS_VERSION >= 31400 */
+}
+
 PG_FUNCTION_INFO_V1(isring);
 Datum isring(PG_FUNCTION_ARGS)
 {
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 2dbfd3e74..34d50836a 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -5043,6 +5043,18 @@ CREATE OR REPLACE FUNCTION ST_IsSimple(geometry)
 	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
 	_COST_MEDIUM;
 
+-- Availability: 3.7.0
+CREATE TYPE simple_detail AS (
+	simple bool,
+	location geometry
+);
+
+-- Availability: 3.7.0
+CREATE OR REPLACE FUNCTION ST_IsSimpleDetail(geom geometry, findAllLocations integer DEFAULT 0, boundaryNodeRule integer DEFAULT 1, OUT simple boolean, OUT location geometry)
+	AS 'MODULE_PATHNAME', 'issimpledetail'
+	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+	_COST_MEDIUM;
+
 -- Availability: 2.0.0
 CREATE OR REPLACE FUNCTION ST_IsCollection(geometry)
 	RETURNS boolean
diff --git a/regress/core/issimpledetail.sql b/regress/core/issimpledetail.sql
new file mode 100644
index 000000000..00134a02d
--- /dev/null
+++ b/regress/core/issimpledetail.sql
@@ -0,0 +1,5 @@
+SELECT 't1', simple, ST_AsText(location)  FROM ST_IsSimpleDetail('LINESTRING(0 0, 10 0)'::geometry);
+SELECT 't2', simple, ST_AsText(location) FROM ST_IsSimpleDetail('LINESTRING(0 0, 10 0, 5 10, 5 -10)'::geometry);
+SELECT 't3', simple, ST_AsText(location) FROM ST_IsSimpleDetail('LINESTRING(0 0, 10 0, 5 10, 5 -10, 2 2)'::geometry, 1);
+SELECT 't4', simple, ST_AsText(location) FROM ST_IsSimpleDetail('MULTILINESTRING((0 0, -10 0, -10 -10, 0 0),(0 0,10 0,10 10,0 0))'::geometry, 1, 1);
+SELECT 't5', simple, ST_AsText(location) FROM ST_IsSimpleDetail('MULTILINESTRING((0 0, -10 0, -10 -10, 0 0),(0 0,10 0,10 10,0 0))'::geometry, 1, 2);
diff --git a/regress/core/issimpledetail_expected b/regress/core/issimpledetail_expected
new file mode 100644
index 000000000..13ae6182e
--- /dev/null
+++ b/regress/core/issimpledetail_expected
@@ -0,0 +1,5 @@
+t1|t|
+t2|f|POINT(5 0)
+t3|f|MULTIPOINT((2.5 0),(5 0))
+t4|f|POINT(0 0)
+t5|t|
diff --git a/regress/core/tests.mk.in b/regress/core/tests.mk.in
index c64c329e3..3940b4cb9 100644
--- a/regress/core/tests.mk.in
+++ b/regress/core/tests.mk.in
@@ -196,6 +196,11 @@ ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" "<=" 31200),1)
 		$(top_srcdir)/regress/core/relate_bnr
 endif
 
+ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" ">=" 31600),1)
+	TESTS += \
+    $(top_srcdir)/regress/core/issimpledetail
+endif
+
 ifeq ($(INTERRUPTTESTS),yes)
 	# Allow CI servers to configure --with-interrupt-tests
 	TESTS += \

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

Summary of changes:
 NEWS                                 |  1 +
 doc/reference_accessor.xml           | 66 +++++++++++++++++++++++++++
 postgis/lwgeom_geos.c                | 87 ++++++++++++++++++++++++++++++++++++
 postgis/postgis.sql.in               | 12 +++++
 regress/core/issimpledetail.sql      |  5 +++
 regress/core/issimpledetail_expected |  5 +++
 regress/core/tests.mk.in             |  5 +++
 7 files changed, 181 insertions(+)
 create mode 100644 regress/core/issimpledetail.sql
 create mode 100644 regress/core/issimpledetail_expected


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list