[SCM] PostGIS branch master updated. 3.6.0rc2-520-g91dd9dbfd

git at osgeo.org git at osgeo.org
Tue Jun 9 23:33:24 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  91dd9dbfd60c93b510160076a5b520dc6fc3854f (commit)
       via  b1e736c207d8068ffd6d0d476faaebc159a56ffb (commit)
       via  d83390ba48c0df2a58b13ae1894eeb6d9b72a047 (commit)
       via  ce1596f8f05cf524601e574f03ed8d4d73fe0a2d (commit)
       via  9b21502078522e9b5973120db2664731ac91b9cf (commit)
       via  55e186147d5add56e8203fbaff273e83f2003e35 (commit)
       via  afbe34be9ea5379debd594442ab53bda0f013c45 (commit)
       via  10ab504c6dd5036c1ff65db83e1e8e76ed713ada (commit)
       via  a63959a97b85bb029b8ab12712a14db8c4689653 (commit)
       via  ea9aa716c07a95b1ebd586cdf7e8ca6c506b3acf (commit)
      from  38faa8d5146b32dbc71a99e64208fd23719999e8 (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 91dd9dbfd60c93b510160076a5b520dc6fc3854f
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 12:52:27 2026 +0200

    feat(sfcgal): add CG_ApproximateMedialAxis(geometry, boolean) overload

diff --git a/sfcgal/lwgeom_sfcgal.c b/sfcgal/lwgeom_sfcgal.c
index 4ecaf0c9b..5ee90f878 100644
--- a/sfcgal/lwgeom_sfcgal.c
+++ b/sfcgal/lwgeom_sfcgal.c
@@ -450,16 +450,33 @@ sfcgal_approximate_medial_axis(PG_FUNCTION_ARGS)
 	GSERIALIZED *input, *output;
 	sfcgal_geometry_t *geom;
 	sfcgal_geometry_t *result;
+	bool projected = false;
 	srid_t srid;
 
 	sfcgal_postgis_init();
 
 	input = PG_GETARG_GSERIALIZED_P(0);
 	srid = gserialized_get_srid(input);
+
+	if (PG_NARGS() > 1 && !PG_ARGISNULL(1))
+		projected = PG_GETARG_BOOL(1);
+
 	geom = POSTGIS2SFCGALGeometry(input);
 	PG_FREE_IF_COPY(input, 0);
 
+#if POSTGIS_SFCGAL_VERSION >= 20300
+	if (projected)
+		result = sfcgal_geometry_projected_medial_axis(geom);
+	else
+		result = sfcgal_geometry_approximate_medial_axis(geom);
+#else
+	if (projected)
+		lwpgnotice(
+		    "CG_ApproximateMedialAxis with projected=true requires SFCGAL 2.3.0+, "
+		    "falling back to non-projected result.");
 	result = sfcgal_geometry_approximate_medial_axis(geom);
+#endif
+
 	sfcgal_geometry_delete(geom);
 
 	output = SFCGALGeometry2POSTGIS(result, 0, srid);
diff --git a/sfcgal/sfcgal.sql.in b/sfcgal/sfcgal.sql.in
index a8e9809a4..f0ca979f0 100644
--- a/sfcgal/sfcgal.sql.in
+++ b/sfcgal/sfcgal.sql.in
@@ -309,6 +309,14 @@ CREATE OR REPLACE FUNCTION CG_ApproximateMedialAxis(geometry)
        IMMUTABLE STRICT PARALLEL SAFE
        COST 100;
 
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0 when projected is true
+CREATE OR REPLACE FUNCTION CG_ApproximateMedialAxis(geom geometry, projected boolean)
+	RETURNS geometry
+	AS 'MODULE_PATHNAME','sfcgal_approximate_medial_axis'
+	LANGUAGE 'c'
+	IMMUTABLE STRICT PARALLEL SAFE
+	COST 100;
+
 -- Availability: 2.2.0
 -- Deprecation in 3.5.0
 CREATE OR REPLACE FUNCTION ST_ApproximateMedialAxis(geometry)

commit b1e736c207d8068ffd6d0d476faaebc159a56ffb
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 12:52:30 2026 +0200

    test(sfcgal): add tests for CG_ApproximateMedialAxis projected overload

diff --git a/sfcgal/regress/approximatemedialaxis.sql b/sfcgal/regress/approximatemedialaxis.sql
index 1b5c10519..dcc78b9a5 100644
--- a/sfcgal/regress/approximatemedialaxis.sql
+++ b/sfcgal/regress/approximatemedialaxis.sql
@@ -1,2 +1,5 @@
 SELECT 'square', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,1 0,1 1,0 1,0 0))'));
-SELECT 'rect1', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=4326;POLYGON((0 0,2 0,2 1,0 1,0 0))'));
+SELECT 'rect', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,2 0,2 1,0 1,0 0))'));
+-- projected=false must equal 1-arg overload
+SELECT 'square_proj_false', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,1 0,1 1,0 1,0 0))', false));
+SELECT 'rect_proj_false', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,2 0,2 1,0 1,0 0))', false));
diff --git a/sfcgal/regress/approximatemedialaxis_expected b/sfcgal/regress/approximatemedialaxis_expected
index 2677ed75b..7f5a721f6 100644
--- a/sfcgal/regress/approximatemedialaxis_expected
+++ b/sfcgal/regress/approximatemedialaxis_expected
@@ -1,2 +1,4 @@
 square|SRID=3857;MULTILINESTRING EMPTY
-rect1|SRID=4326;MULTILINESTRING((0.5 0.5,1.5 0.5))
+rect|SRID=3857;MULTILINESTRING((0.5 0.5,1.5 0.5))
+square_proj_false|SRID=3857;MULTILINESTRING EMPTY
+rect_proj_false|SRID=3857;MULTILINESTRING((0.5 0.5,1.5 0.5))
diff --git a/sfcgal/regress/approximatemedialaxis_projected.sql b/sfcgal/regress/approximatemedialaxis_projected.sql
new file mode 100644
index 000000000..42ac7ec19
--- /dev/null
+++ b/sfcgal/regress/approximatemedialaxis_projected.sql
@@ -0,0 +1,3 @@
+-- projected=true extends free endpoints to polygon boundary (requires SFCGAL >= 2.3.0)
+SELECT 'square_proj_true', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,1 0,1 1,0 1,0 0))', true));
+SELECT 'rect_proj_true', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,2 0,2 1,0 1,0 0))', true));
diff --git a/sfcgal/regress/approximatemedialaxis_projected_expected b/sfcgal/regress/approximatemedialaxis_projected_expected
new file mode 100644
index 000000000..302aab162
--- /dev/null
+++ b/sfcgal/regress/approximatemedialaxis_projected_expected
@@ -0,0 +1,2 @@
+square_proj_true|SRID=3857;MULTILINESTRING EMPTY
+rect_proj_true|SRID=3857;MULTILINESTRING((0 0.5,0.5 0.5,1.5 0.5,2 0.5))
diff --git a/sfcgal/regress/approximatemedialaxis_projected_pre230.sql b/sfcgal/regress/approximatemedialaxis_projected_pre230.sql
new file mode 100644
index 000000000..f70b45f62
--- /dev/null
+++ b/sfcgal/regress/approximatemedialaxis_projected_pre230.sql
@@ -0,0 +1,3 @@
+-- projected=true falls back to non-projected result with a notice on SFCGAL < 2.3.0
+SELECT 'square_proj_true', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,1 0,1 1,0 1,0 0))', true));
+SELECT 'rect_proj_true', ST_AsEwkt(CG_ApproximateMedialAxis('SRID=3857;POLYGON((0 0,2 0,2 1,0 1,0 0))', true));
diff --git a/sfcgal/regress/approximatemedialaxis_projected_pre230_expected b/sfcgal/regress/approximatemedialaxis_projected_pre230_expected
new file mode 100644
index 000000000..39a0a299d
--- /dev/null
+++ b/sfcgal/regress/approximatemedialaxis_projected_pre230_expected
@@ -0,0 +1,4 @@
+NOTICE:  CG_ApproximateMedialAxis with projected=true requires SFCGAL 2.3.0+, falling back to non-projected result.
+square_proj_true|SRID=3857;MULTILINESTRING EMPTY
+NOTICE:  CG_ApproximateMedialAxis with projected=true requires SFCGAL 2.3.0+, falling back to non-projected result.
+rect_proj_true|SRID=3857;MULTILINESTRING((0.5 0.5,1.5 0.5))
diff --git a/sfcgal/regress/tests.mk.in b/sfcgal/regress/tests.mk.in
index 053a1953a..19a6b037b 100644
--- a/sfcgal/regress/tests.mk.in
+++ b/sfcgal/regress/tests.mk.in
@@ -49,10 +49,14 @@ ifeq ($(shell expr "$(POSTGIS_SFCGAL_VERSION)" ">=" 20300),1)
 	TESTS += \
 		$(top_srcdir)/sfcgal/regress/alphashape_components.sql \
 		$(top_srcdir)/sfcgal/regress/roofgeneration.sql \
-		$(top_srcdir)/sfcgal/regress/polygonrepair.sql
+		$(top_srcdir)/sfcgal/regress/polygonrepair.sql \
+		$(top_srcdir)/sfcgal/regress/approximatemedialaxis_projected.sql
 	ifeq ($(shell expr "$(POSTGIS_CGAL_VERSION)" ">=" 601),1)
 		TESTS += $(top_srcdir)/sfcgal/regress/polygonrepair_union.sql
 	else
 		TESTS += $(top_srcdir)/sfcgal/regress/polygonrepair_union_pre61.sql
 	endif
+else
+	TESTS += \
+		$(top_srcdir)/sfcgal/regress/approximatemedialaxis_projected_pre230.sql
 endif

commit d83390ba48c0df2a58b13ae1894eeb6d9b72a047
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 12:52:34 2026 +0200

    docs(sfcgal): document CG_ApproximateMedialAxis projected parameter

diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml
index 0ebf6a4d3..2bcaa35a3 100644
--- a/doc/reference_sfcgal.xml
+++ b/doc/reference_sfcgal.xml
@@ -1755,6 +1755,11 @@ ST_GeomFromText('POLYHEDRALSURFACE Z( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
                     <funcdef>geometry <function>CG_ApproximateMedialAxis</function></funcdef>
                     <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
                 </funcprototype>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_ApproximateMedialAxis</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef><type>boolean</type> <parameter>projected</parameter></paramdef>
+                </funcprototype>
             </funcsynopsis>
         </refsynopsisdiv>
 
@@ -1767,8 +1772,16 @@ ST_GeomFromText('POLYHEDRALSURFACE Z( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
                 a capable version (1.2.0+). Otherwise the function is just a wrapper
                 around CG_StraightSkeleton (slower case).
             </para>
+            <para>
+                When <varname>projected</varname> is <varname>true</varname>, free
+                endpoints of the medial axis are extended to reach the polygon boundary
+                (projected medial axis). Requires SFCGAL 2.3.0+. When built against
+                an older SFCGAL version, a notice is emitted and the non-projected
+                result is returned instead.
+            </para>
 
             <para role="availability" conformance="3.5.0">Availability: 3.5.0</para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - <varname>projected</varname> parameter. Requires SFCGAL >= 2.3.0 for projected result; falls back to non-projected with a notice on older versions.</para>
             <note><para>This function ignores the Z dimension.
 It always gives a 2D result even when used on a 3D geometry.</para></note>
             <para>&sfcgal_required;</para>
@@ -1796,6 +1809,10 @@ It always gives a 2D result even when used on a 3D geometry.</para></note>
                     </tbody>
                 </tgroup>
             </informaltable>
+
+            <programlisting>-- Projected medial axis: free endpoints extended to polygon boundary
+SELECT CG_ApproximateMedialAxis('POLYGON((0 0,2 0,2 1,0 1,0 0))', true);
+-- Result: MULTILINESTRING((0 0.5,2 0.5))</programlisting>
         </refsection>
 
         <refsection>

commit ce1596f8f05cf524601e574f03ed8d4d73fe0a2d
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 13:51:40 2026 +0200

    docs(sfcgal): document CG_PolygonRepair

diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml
index a33f797cf..0ebf6a4d3 100644
--- a/doc/reference_sfcgal.xml
+++ b/doc/reference_sfcgal.xml
@@ -2169,6 +2169,64 @@ SELECT ST_AsText(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'FLAT', 2.0))
 
     </refentry>
 
+    <refentry xml:id="CG_PolygonRepair">
+        <refnamediv>
+            <refname>CG_PolygonRepair</refname>
+            <refpurpose>Repair an invalid polygon or multipolygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_PolygonRepair</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>text</type> <parameter>rule</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Repair an invalid polygon or multipolygon using CGAL's 2D Polygon
+                Repair algorithm. Returns a <varname>MultiPolygon</varname>.
+            </para>
+            <para>
+                The <varname>rule</varname> parameter controls the repair strategy:
+            </para>
+            <itemizedlist>
+                <listitem><para><varname>EVEN_ODD</varname> (default) — areas covered an odd
+                    number of times are kept. Available with CGAL 6.0+.</para></listitem>
+                <listitem><para><varname>NON_ZERO</varname> — areas with non-zero winding
+                    number are kept. Requires CGAL 6.1+.</para></listitem>
+                <listitem><para><varname>UNION</varname> — union of all input polygons.
+                    Requires CGAL 6.1+.</para></listitem>
+                <listitem><para><varname>INTERSECTION</varname> — intersection of all input
+                    polygons. Requires CGAL 6.1+.</para></listitem>
+            </itemizedlist>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0 and CGAL >= 6.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <programlisting>-- Repair a bowtie (self-intersecting) polygon
+SELECT ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))'));
+-- MULTIPOLYGON(((0 0,1 1,0 2,0 0)),((1 1,2 0,2 2,1 1)))
+
+-- Keep all winding area with UNION rule (requires CGAL 6.1 or later)
+SELECT ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))', 'UNION'));
+-- MULTIPOLYGON(((0 0,1 1,0 2,0 0)))</programlisting>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para><xref linkend="ST_IsValid"/>, <xref linkend="ST_MakeValid"/></para>
+        </refsection>
+
+    </refentry>
+
     <refentry xml:id="ST_ConstrainedDelaunayTriangles">
         <refnamediv>
             <refname>ST_ConstrainedDelaunayTriangles</refname>

commit 9b21502078522e9b5973120db2664731ac91b9cf
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 13:51:36 2026 +0200

    test(sfcgal): add tests for CG_PolygonRepair

diff --git a/sfcgal/regress/polygonrepair.sql b/sfcgal/regress/polygonrepair.sql
new file mode 100644
index 000000000..b1379625a
--- /dev/null
+++ b/sfcgal/regress/polygonrepair.sql
@@ -0,0 +1,8 @@
+-- Empty polygon
+SELECT 'empty', ST_AsText(CG_PolygonRepair('POLYGON EMPTY'));
+-- bowtie polygon (self-intersecting): split into two triangles by EVEN_ODD
+SELECT 'bowtie_even_odd', ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))'));
+-- explicit rule
+SELECT 'bowtie_even_odd_explicit', ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))', 'EVEN_ODD'));
+-- valid polygon is returned unchanged
+SELECT 'valid', ST_AsText(CG_PolygonRepair('POLYGON((0 0,1 0,1 1,0 1,0 0))'));
diff --git a/sfcgal/regress/polygonrepair_expected b/sfcgal/regress/polygonrepair_expected
new file mode 100644
index 000000000..820476f2a
--- /dev/null
+++ b/sfcgal/regress/polygonrepair_expected
@@ -0,0 +1,4 @@
+empty|MULTIPOLYGON EMPTY
+bowtie_even_odd|MULTIPOLYGON(((0 0,1 1,0 2,0 0)),((1 1,2 0,2 2,1 1)))
+bowtie_even_odd_explicit|MULTIPOLYGON(((0 0,1 1,0 2,0 0)),((1 1,2 0,2 2,1 1)))
+valid|MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)))
diff --git a/sfcgal/regress/polygonrepair_union.sql b/sfcgal/regress/polygonrepair_union.sql
new file mode 100644
index 000000000..74bb06eb2
--- /dev/null
+++ b/sfcgal/regress/polygonrepair_union.sql
@@ -0,0 +1,2 @@
+-- UNION rule: keep all winding area (requires CGAL 6.1+)
+SELECT 'bowtie_union', ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))', 'UNION'));
diff --git a/sfcgal/regress/polygonrepair_union_expected b/sfcgal/regress/polygonrepair_union_expected
new file mode 100644
index 000000000..c19353709
--- /dev/null
+++ b/sfcgal/regress/polygonrepair_union_expected
@@ -0,0 +1 @@
+bowtie_union|MULTIPOLYGON(((0 0,1 1,0 2,0 0)))
diff --git a/sfcgal/regress/polygonrepair_union_pre61.sql b/sfcgal/regress/polygonrepair_union_pre61.sql
new file mode 100644
index 000000000..74bb06eb2
--- /dev/null
+++ b/sfcgal/regress/polygonrepair_union_pre61.sql
@@ -0,0 +1,2 @@
+-- UNION rule: keep all winding area (requires CGAL 6.1+)
+SELECT 'bowtie_union', ST_AsText(CG_PolygonRepair('POLYGON((0 0,2 2,2 0,0 2,0 0))', 'UNION'));
diff --git a/sfcgal/regress/polygonrepair_union_pre61_expected b/sfcgal/regress/polygonrepair_union_pre61_expected
new file mode 100644
index 000000000..4345071d2
--- /dev/null
+++ b/sfcgal/regress/polygonrepair_union_pre61_expected
@@ -0,0 +1 @@
+ERROR:  CG_PolygonRepair: 'UNION' rule requires CGAL 6.1 or later
diff --git a/sfcgal/regress/tests.mk.in b/sfcgal/regress/tests.mk.in
index 54c3c6190..053a1953a 100644
--- a/sfcgal/regress/tests.mk.in
+++ b/sfcgal/regress/tests.mk.in
@@ -12,6 +12,7 @@
 POSTGIS_PGSQL_VERSION=@POSTGIS_PGSQL_VERSION@
 POSTGIS_GEOS_VERSION=@POSTGIS_GEOS_VERSION@
 POSTGIS_SFCGAL_VERSION=@POSTGIS_SFCGAL_VERSION@
+POSTGIS_CGAL_VERSION=@POSTGIS_CGAL_VERSION@
 
 TESTS += \
 		$(top_srcdir)/sfcgal/regress/regress_sfcgal \
@@ -47,5 +48,11 @@ endif
 ifeq ($(shell expr "$(POSTGIS_SFCGAL_VERSION)" ">=" 20300),1)
 	TESTS += \
 		$(top_srcdir)/sfcgal/regress/alphashape_components.sql \
-		$(top_srcdir)/sfcgal/regress/roofgeneration.sql
+		$(top_srcdir)/sfcgal/regress/roofgeneration.sql \
+		$(top_srcdir)/sfcgal/regress/polygonrepair.sql
+	ifeq ($(shell expr "$(POSTGIS_CGAL_VERSION)" ">=" 601),1)
+		TESTS += $(top_srcdir)/sfcgal/regress/polygonrepair_union.sql
+	else
+		TESTS += $(top_srcdir)/sfcgal/regress/polygonrepair_union_pre61.sql
+	endif
 endif

commit 55e186147d5add56e8203fbaff273e83f2003e35
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 13:51:32 2026 +0200

    feat(sfcgal): add CG_PolygonRepair

diff --git a/configure.ac b/configure.ac
index 4de164b61..6d84d5568 100644
--- a/configure.ac
+++ b/configure.ac
@@ -964,6 +964,17 @@ if test "x$with_sfcgal" != "xno"; then
 		POSTGIS_SFCGAL_VERSION="$SFCGAL_MAJOR_VERSION$SFCGAL_NUMERIC_MINOR_VERSION$SFCGAL_NUMERIC_PATCH_VERSION"
 		AC_DEFINE_UNQUOTED([POSTGIS_SFCGAL_VERSION], [$POSTGIS_SFCGAL_VERSION], [SFCGAL library version at build time])
 
+		POSTGIS_CGAL_VERSION="0"
+		_sfcgal_inc=`echo "$SFCGAL_CPPFLAGS" | tr ' ' '\n' | grep '^-I' | head -1 | sed 's/^-I//'`
+		if test -n "$_sfcgal_inc" && test -f "${_sfcgal_inc}/SFCGAL/version.h"; then
+			_cgal_major=`grep '#define SFCGAL_CGAL_VERSION_MAJOR' "${_sfcgal_inc}/SFCGAL/version.h" | awk '{print $3}'`
+			_cgal_minor=`grep '#define SFCGAL_CGAL_VERSION_MINOR' "${_sfcgal_inc}/SFCGAL/version.h" | awk '{print $3}'`
+			if test -n "$_cgal_major" && test -n "$_cgal_minor"; then
+				_cgal_minor_pad=`printf "%02d" $_cgal_minor`
+				POSTGIS_CGAL_VERSION="${_cgal_major}${_cgal_minor_pad}"
+			fi
+		fi
+
 		SFCGAL_STATIC=`$SFCGAL_CONFIG --static`
 		if test "x$SFCGAL_STATIC" = "xON"; then
 			AC_MSG_WARN([The SFCGAL version found is not installed as a dynamic library.])
@@ -984,6 +995,7 @@ if test "x$with_sfcgal" != "xno"; then
 fi
 
 AC_SUBST([POSTGIS_SFCGAL_VERSION])
+AC_SUBST([POSTGIS_CGAL_VERSION])
 AC_SUBST([SFCGAL_VERSION])
 AC_SUBST([SFCGAL_CPPFLAGS])
 AC_SUBST([SFCGAL_LDFLAGS])
@@ -1929,6 +1941,7 @@ fi
 if test "x$SFCGAL" = "xsfcgal"; then
     AC_MSG_RESULT([  SFCGAL config:        ${SFCGAL_CONFIG}])
     AC_MSG_RESULT([  SFCGAL version:       ${SFCGAL_VERSION}])
+    AC_MSG_RESULT([  CGAL version:         ${POSTGIS_CGAL_VERSION}])
 fi
 if test "x$SUPPORT_POSTGRESQL" = "xyes"; then
   AC_MSG_RESULT([  PostgreSQL config:    ${PG_CONFIG}])
diff --git a/sfcgal/lwgeom_sfcgal.c b/sfcgal/lwgeom_sfcgal.c
index bf58c04a7..4ecaf0c9b 100644
--- a/sfcgal/lwgeom_sfcgal.c
+++ b/sfcgal/lwgeom_sfcgal.c
@@ -91,6 +91,7 @@ Datum sfcgal_generate_hipped_roof(PG_FUNCTION_ARGS);
 Datum sfcgal_generate_gable_roof(PG_FUNCTION_ARGS);
 Datum sfcgal_generate_skillion_roof(PG_FUNCTION_ARGS);
 Datum sfcgal_generate_roof(PG_FUNCTION_ARGS);
+Datum sfcgal_polygon_repair(PG_FUNCTION_ARGS);
 
 GSERIALIZED *geometry_serialize(LWGEOM *lwgeom);
 char *text_to_cstring(const text *textptr);
@@ -2106,3 +2107,73 @@ sfcgal_generate_roof(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(output);
 #endif
 }
+
+PG_FUNCTION_INFO_V1(sfcgal_polygon_repair);
+Datum
+sfcgal_polygon_repair(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300 || \
+    !defined(SFCGAL_CGAL_VERSION_MAJOR) || \
+    SFCGAL_CGAL_VERSION_MAJOR < 6
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_polygon_repair' function (requires SFCGAL 2.3.0+ and CGAL 6.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	sfcgal_polygon_repair_rule_t rule = SFCGAL_POLYGON_REPAIR_EVEN_ODD;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	srid = gserialized_get_srid(input);
+
+	if (!PG_ARGISNULL(1))
+	{
+		char *rule_str = text_to_cstring(PG_GETARG_TEXT_P(1));
+
+		if (strcmp(rule_str, "EVEN_ODD") == 0)
+			rule = SFCGAL_POLYGON_REPAIR_EVEN_ODD;
+		else if (strcmp(rule_str, "NON_ZERO") == 0 ||
+		         strcmp(rule_str, "UNION") == 0 ||
+		         strcmp(rule_str, "INTERSECTION") == 0)
+		{
+			if (SFCGAL_CGAL_VERSION_MAJOR < 6 ||
+			    (SFCGAL_CGAL_VERSION_MAJOR == 6 && SFCGAL_CGAL_VERSION_MINOR < 1))
+			{
+				lwpgerror("CG_PolygonRepair: '%s' rule requires CGAL 6.1 or later", rule_str);
+				pfree(rule_str);
+				PG_RETURN_NULL();
+			}
+			if (strcmp(rule_str, "NON_ZERO") == 0)
+				rule = SFCGAL_POLYGON_REPAIR_NON_ZERO;
+			else if (strcmp(rule_str, "UNION") == 0)
+				rule = SFCGAL_POLYGON_REPAIR_UNION;
+			else
+				rule = SFCGAL_POLYGON_REPAIR_INTERSECTION;
+		}
+		else
+		{
+			lwpgerror("CG_PolygonRepair: unknown rule '%s', expected EVEN_ODD, NON_ZERO, UNION or INTERSECTION",
+			          rule_str);
+			pfree(rule_str);
+			PG_RETURN_NULL();
+		}
+		pfree(rule_str); /* alloc'ed in text_to_cstring */
+	}
+
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_polygon_repair(geom, rule);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 0, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
diff --git a/sfcgal/sfcgal.sql.in b/sfcgal/sfcgal.sql.in
index dcba5b678..a8e9809a4 100644
--- a/sfcgal/sfcgal.sql.in
+++ b/sfcgal/sfcgal.sql.in
@@ -644,4 +644,11 @@ AS 'MODULE_PATHNAME', 'sfcgal_generate_roof'
 LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
 _COST_DEFAULT;
 
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0 and CGAL >= 6.0
+CREATE OR REPLACE FUNCTION CG_PolygonRepair(geom geometry, rule text DEFAULT 'EVEN_ODD')
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_polygon_repair'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
 COMMIT;

commit afbe34be9ea5379debd594442ab53bda0f013c45
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Tue Jun 9 14:13:36 2026 +0200

    fix(garden): polygon must be valid

diff --git a/doc/xsl/sfcgal_gardentest.sql.xsl b/doc/xsl/sfcgal_gardentest.sql.xsl
index 0e48ecd1c..8c6f18e81 100644
--- a/doc/xsl/sfcgal_gardentest.sql.xsl
+++ b/doc/xsl/sfcgal_gardentest.sql.xsl
@@ -13,7 +13,7 @@
 	<xsl:output method="text" />
 	<xsl:variable name='testversion'>3.7.0</xsl:variable>
 	<xsl:variable name='fnexclude14'>AddGeometryColumn DropGeometryColumn DropGeometryTable</xsl:variable>
-	<xsl:variable name='fnexclude'>CG_Simplify CG_Visibility CG_YMonotonePartition ST_AlphaShape ST_OptimalAlphaShape</xsl:variable>
+	<xsl:variable name='fnexclude'>CG_GenerateFlatRoof CG_GenerateGableRoof CG_GenerateHippedRoof CG_GenerateRoof CG_GenerateSkillionRoof CG_Simplify CG_Visibility CG_YMonotonePartition ST_AlphaShape ST_OptimalAlphaShape</xsl:variable>
 	<!--This is just a place holder to state functions not supported or tested separately -->
 
 	<xsl:variable name='var_srid'>3395</xsl:variable>

commit 10ab504c6dd5036c1ff65db83e1e8e76ed713ada
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 14:29:48 2026 +0200

    docs(sfcgal): document roof generation functions

diff --git a/doc/html/images/static/cg_generateflatroof01.png b/doc/html/images/static/cg_generateflatroof01.png
new file mode 100644
index 000000000..7174e08da
Binary files /dev/null and b/doc/html/images/static/cg_generateflatroof01.png differ
diff --git a/doc/html/images/static/cg_generategableroof01.png b/doc/html/images/static/cg_generategableroof01.png
new file mode 100644
index 000000000..0ee27cfac
Binary files /dev/null and b/doc/html/images/static/cg_generategableroof01.png differ
diff --git a/doc/html/images/static/cg_generatehippedroof01.png b/doc/html/images/static/cg_generatehippedroof01.png
new file mode 100644
index 000000000..a3e5ccd28
Binary files /dev/null and b/doc/html/images/static/cg_generatehippedroof01.png differ
diff --git a/doc/html/images/static/cg_generateskillionroof01.png b/doc/html/images/static/cg_generateskillionroof01.png
new file mode 100644
index 000000000..b2073908f
Binary files /dev/null and b/doc/html/images/static/cg_generateskillionroof01.png differ
diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml
index b27b72804..a33f797cf 100644
--- a/doc/reference_sfcgal.xml
+++ b/doc/reference_sfcgal.xml
@@ -1806,6 +1806,369 @@ It always gives a 2D result even when used on a 3D geometry.</para></note>
 
     </refentry>
 
+    <refentry xml:id="CG_GenerateFlatRoof">
+        <refnamediv>
+            <refname>CG_GenerateFlatRoof</refname>
+            <refpurpose>Generate a flat (box) roof from a footprint polygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_GenerateFlatRoof</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>height</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Generate a flat (box-shaped) roof as a 3D
+                <varname>PolyhedralSurface Z</varname> from a 2D footprint polygon.
+                The roof height above the ground plane is given by
+                <varname>height</varname> (default 3.0).
+            </para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <informaltable>
+                <tgroup cols="2">
+                    <tbody>
+                        <row>
+                            <entry>
+                                <programlisting>SELECT ST_AsText(
+  CG_GenerateFlatRoof(
+    'POLYGON((0 0,5 0,5 4,0 4,0 0))',
+    2.0));
+-- POLYHEDRALSURFACE Z (...)</programlisting>
+                            </entry>
+                            <entry>
+                                <mediaobject>
+                                    <imageobject>
+                                        <imagedata fileref="images/cg_generateflatroof01.png"/>
+                                    </imageobject>
+                                </mediaobject>
+                            </entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </informaltable>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para>
+                <xref linkend="CG_GenerateHippedRoof"/>,
+                <xref linkend="CG_GenerateGableRoof"/>,
+                <xref linkend="CG_GenerateSkillionRoof"/>,
+                <xref linkend="CG_GenerateRoof"/>
+            </para>
+        </refsection>
+
+    </refentry>
+
+    <refentry xml:id="CG_GenerateHippedRoof">
+        <refnamediv>
+            <refname>CG_GenerateHippedRoof</refname>
+            <refpurpose>Generate a hipped roof from a footprint polygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_GenerateHippedRoof</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>height</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Generate a hipped roof as a 3D <varname>PolyhedralSurface Z</varname>
+                from a 2D footprint polygon. All sides slope upward to meet at a ridge
+                or apex. The roof height is given by <varname>height</varname>
+                (default 3.0).
+            </para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <informaltable>
+                <tgroup cols="2">
+                    <tbody>
+                        <row>
+                            <entry>
+                                <programlisting>SELECT ST_AsText(
+  CG_GenerateHippedRoof(
+    'POLYGON((0 0,5 0,5 4,0 4,0 0))',
+    2.0));
+-- POLYHEDRALSURFACE Z (...)</programlisting>
+                            </entry>
+                            <entry>
+                                <mediaobject>
+                                    <imageobject>
+                                        <imagedata fileref="images/cg_generatehippedroof01.png"/>
+                                    </imageobject>
+                                </mediaobject>
+                            </entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </informaltable>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para>
+                <xref linkend="CG_GenerateFlatRoof"/>,
+                <xref linkend="CG_GenerateGableRoof"/>,
+                <xref linkend="CG_GenerateSkillionRoof"/>,
+                <xref linkend="CG_GenerateRoof"/>
+            </para>
+        </refsection>
+
+    </refentry>
+
+    <refentry xml:id="CG_GenerateGableRoof">
+        <refnamediv>
+            <refname>CG_GenerateGableRoof</refname>
+            <refpurpose>Generate a gable roof from a footprint polygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_GenerateGableRoof</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>height</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>slope_angle</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Generate a gable roof as a 3D <varname>PolyhedralSurface Z</varname>
+                from a 2D footprint polygon. Two opposite sides are vertical gable
+                ends; the other two sides slope to a horizontal ridge.
+                <varname>height</varname> sets the ridge height above the ground
+                (default 3.0) and <varname>slope_angle</varname> controls the pitch
+                in degrees (default 30.0). The SRID of the input geometry is
+                preserved.
+            </para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <informaltable>
+                <tgroup cols="2">
+                    <tbody>
+                        <row>
+                            <entry>
+                                <programlisting>SELECT ST_CoordDim(
+  CG_GenerateGableRoof(
+    'POLYGON((0 0,5 0,5 4,0 4,0 0))',
+    2.0, 30.0));
+-- 3</programlisting>
+                            </entry>
+                            <entry>
+                                <mediaobject>
+                                    <imageobject>
+                                        <imagedata fileref="images/cg_generategableroof01.png"/>
+                                    </imageobject>
+                                </mediaobject>
+                            </entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </informaltable>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para>
+                <xref linkend="CG_GenerateFlatRoof"/>,
+                <xref linkend="CG_GenerateHippedRoof"/>,
+                <xref linkend="CG_GenerateSkillionRoof"/>,
+                <xref linkend="CG_GenerateRoof"/>
+            </para>
+        </refsection>
+
+    </refentry>
+
+    <refentry xml:id="CG_GenerateSkillionRoof">
+        <refnamediv>
+            <refname>CG_GenerateSkillionRoof</refname>
+            <refpurpose>Generate a skillion (single-slope) roof from a footprint polygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_GenerateSkillionRoof</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>height</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>slope_angle</parameter></paramdef>
+                    <paramdef choice="opt"><type>integer</type> <parameter>primary_edge_index</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Generate a skillion (single-slope, shed-style) roof as a 3D
+                <varname>PolyhedralSurface Z</varname> from a 2D footprint polygon.
+                The entire roof surface slopes in one direction.
+                <varname>height</varname> sets the maximum ridge height (default 3.0),
+                <varname>slope_angle</varname> sets the pitch in degrees (default
+                30.0), and <varname>primary_edge_index</varname> selects which
+                polygon edge defines the high side (0-based, default 0).
+            </para>
+            <para>
+                When the combination of <varname>height</varname> and
+                <varname>slope_angle</varname> would cause a side wall to vanish
+                (the slope reaches the far edge before the full height is achieved),
+                that face is omitted from the result. This means the number of
+                output patches can vary depending on the parameters — a lower
+                height relative to the footprint depth produces more faces than
+                a taller, steeper configuration.
+            </para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <para>
+                Height 2.0 produces 6 faces (all walls present); height 4.0
+                produces 5 faces (one side wall is filtered out because the
+                slope reaches the far edge before the full height).
+            </para>
+            <informaltable>
+                <tgroup cols="2">
+                    <tbody>
+                        <row>
+                            <entry>
+                                <programlisting>-- height 2.0 → 6 patches
+SELECT ST_NumPatches(
+  CG_GenerateSkillionRoof(
+    'POLYGON((0 0,5 0,5 4,0 4,0 0))',
+    2.0));
+-- 6
+
+-- height 4.0 → 5 patches (one face filtered)
+SELECT ST_NumPatches(
+  CG_GenerateSkillionRoof(
+    'POLYGON((0 0,5 0,5 4,0 4,0 0))',
+    4.0));
+-- 5</programlisting>
+                            </entry>
+                            <entry>
+                                <mediaobject>
+                                    <imageobject>
+                                        <imagedata fileref="images/cg_generateskillionroof01.png"/>
+                                    </imageobject>
+                                </mediaobject>
+                            </entry>
+                        </row>
+                    </tbody>
+                </tgroup>
+            </informaltable>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para>
+                <xref linkend="CG_GenerateFlatRoof"/>,
+                <xref linkend="CG_GenerateHippedRoof"/>,
+                <xref linkend="CG_GenerateGableRoof"/>,
+                <xref linkend="CG_GenerateRoof"/>
+            </para>
+        </refsection>
+
+    </refentry>
+
+    <refentry xml:id="CG_GenerateRoof">
+        <refnamediv>
+            <refname>CG_GenerateRoof</refname>
+            <refpurpose>Generate a roof of the requested type from a footprint polygon.</refpurpose>
+        </refnamediv>
+
+        <refsynopsisdiv>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>geometry <function>CG_GenerateRoof</function></funcdef>
+                    <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                    <paramdef choice="opt"><type>text</type> <parameter>roof_type</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>height</parameter></paramdef>
+                    <paramdef choice="opt"><type>float8</type> <parameter>slope_angle</parameter></paramdef>
+                    <paramdef choice="opt"><type>integer</type> <parameter>primary_edge_index</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+        </refsynopsisdiv>
+
+        <refsection>
+            <title>Description</title>
+            <para>
+                Generate a roof as a 3D <varname>PolyhedralSurface Z</varname> from
+                a 2D footprint polygon. The <varname>roof_type</varname> parameter
+                selects the roof style (default <literal>'HIPPED'</literal>):
+            </para>
+            <itemizedlist>
+                <listitem><para><varname>FLAT</varname> — flat box roof.</para></listitem>
+                <listitem><para><varname>HIPPED</varname> — hipped roof (all sides slope to apex).</para></listitem>
+                <listitem><para><varname>GABLE</varname> — gable roof (two sloping sides, two vertical ends).</para></listitem>
+                <listitem><para><varname>SKILLION</varname> — single-slope (shed) roof.</para></listitem>
+            </itemizedlist>
+            <para>
+                <varname>height</varname> sets the roof height (default 3.0),
+                <varname>slope_angle</varname> sets the pitch in degrees (default
+                30.0), and <varname>primary_edge_index</varname> selects the
+                reference edge for skillion roofs (default 0).
+            </para>
+            <para role="availability" conformance="3.7.0">Availability: 3.7.0 - requires SFCGAL >= 2.3.0.</para>
+            <para>&sfcgal_required;</para>
+            <para>&P_support;</para>
+        </refsection>
+
+        <refsection>
+            <title>Examples</title>
+            <programlisting>SELECT ST_AsText(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'HIPPED', 2.0, 30.0, 0));
+-- POLYHEDRALSURFACE Z (((0 4 0,4 4 0,4 0 0,0 0 0,0 4 0)), ...)
+
+SELECT ST_AsText(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'FLAT', 2.0));
+-- POLYHEDRALSURFACE Z (((0 0 0,0 4 0,4 4 0,4 0 0,0 0 0)), ...)</programlisting>
+        </refsection>
+
+        <refsection>
+            <title>See Also</title>
+            <para>
+                <xref linkend="CG_GenerateFlatRoof"/>,
+                <xref linkend="CG_GenerateHippedRoof"/>,
+                <xref linkend="CG_GenerateGableRoof"/>,
+                <xref linkend="CG_GenerateSkillionRoof"/>
+            </para>
+        </refsection>
+
+    </refentry>
+
     <refentry xml:id="ST_ConstrainedDelaunayTriangles">
         <refnamediv>
             <refname>ST_ConstrainedDelaunayTriangles</refname>

commit a63959a97b85bb029b8ab12712a14db8c4689653
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 14:29:44 2026 +0200

    test(sfcgal): add tests for roof generation functions

diff --git a/sfcgal/regress/roofgeneration.sql b/sfcgal/regress/roofgeneration.sql
new file mode 100644
index 000000000..106f5105e
--- /dev/null
+++ b/sfcgal/regress/roofgeneration.sql
@@ -0,0 +1,16 @@
+-- Tests for CG_GenerateFlatRoof, CG_GenerateHippedRoof, CG_GenerateGableRoof,
+-- CG_GenerateSkillionRoof, CG_GenerateRoof
+-- Uses square polygon with integer coordinates to ensure exact WKT for flat/hipped.
+-- Gable/skillion involve trigonometric values so only dimensionality is checked.
+
+SELECT 'flat', ST_AsText(CG_GenerateFlatRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0));
+SELECT 'hipped', ST_AsText(CG_GenerateHippedRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0));
+SELECT 'flat_3d', ST_CoordDim(CG_GenerateFlatRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0));
+SELECT 'hipped_3d', ST_CoordDim(CG_GenerateHippedRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0));
+SELECT 'gable_3d', ST_CoordDim(CG_GenerateGableRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0, 30.0));
+SELECT 'skillion_3d', ST_CoordDim(CG_GenerateSkillionRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 2.0, 30.0, 0));
+SELECT 'generic_hipped', ST_AsText(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'HIPPED', 2.0, 30.0, 0));
+SELECT 'generic_flat', ST_AsText(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'FLAT', 2.0, 30.0, 0));
+SELECT 'generic_gable_3d', ST_CoordDim(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'GABLE', 2.0, 30.0, 0));
+SELECT 'generic_skillion_3d', ST_CoordDim(CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'SKILLION', 2.0, 30.0, 0));
+SELECT 'invalid_type', CG_GenerateRoof('POLYGON((0 0,4 0,4 4,0 4,0 0))', 'DOME', 2.0, 30.0, 0);
diff --git a/sfcgal/regress/roofgeneration_expected b/sfcgal/regress/roofgeneration_expected
new file mode 100644
index 000000000..d67b1e24d
--- /dev/null
+++ b/sfcgal/regress/roofgeneration_expected
@@ -0,0 +1,11 @@
+flat|POLYHEDRALSURFACE Z (((0 0 0,0 4 0,4 4 0,4 0 0,0 0 0)),((0 0 2,4 0 2,4 4 2,0 4 2,0 0 2)),((0 0 0,0 0 2,0 4 2,0 4 0,0 0 0)),((0 4 0,0 4 2,4 4 2,4 4 0,0 4 0)),((4 4 0,4 4 2,4 0 2,4 0 0,4 4 0)),((4 0 0,4 0 2,0 0 2,0 0 0,4 0 0)))
+hipped|POLYHEDRALSURFACE Z (((0 4 0,4 4 0,4 0 0,0 0 0,0 4 0)),((0 4 0,0 0 0,2 2 2,0 4 0)),((0 0 0,4 0 0,2 2 2,0 0 0)),((4 0 0,4 4 0,2 2 2,4 0 0)),((4 4 0,0 4 0,2 2 2,4 4 0)))
+flat_3d|3
+hipped_3d|3
+gable_3d|3
+skillion_3d|3
+generic_hipped|POLYHEDRALSURFACE Z (((0 4 0,4 4 0,4 0 0,0 0 0,0 4 0)),((0 4 0,0 0 0,2 2 2,0 4 0)),((0 0 0,4 0 0,2 2 2,0 0 0)),((4 0 0,4 4 0,2 2 2,4 0 0)),((4 4 0,0 4 0,2 2 2,4 4 0)))
+generic_flat|POLYHEDRALSURFACE Z (((0 0 0,0 4 0,4 4 0,4 0 0,0 0 0)),((0 0 2,4 0 2,4 4 2,0 4 2,0 0 2)),((0 0 0,0 0 2,0 4 2,0 4 0,0 0 0)),((0 4 0,0 4 2,4 4 2,4 4 0,0 4 0)),((4 4 0,4 4 2,4 0 2,4 0 0,4 4 0)),((4 0 0,4 0 2,0 0 2,0 0 0,4 0 0)))
+generic_gable_3d|3
+generic_skillion_3d|3
+ERROR:  CG_GenerateRoof: unknown roof type 'DOME', expected FLAT, HIPPED, GABLE or SKILLION
diff --git a/sfcgal/regress/tests.mk.in b/sfcgal/regress/tests.mk.in
index 8dd78b2ec..54c3c6190 100644
--- a/sfcgal/regress/tests.mk.in
+++ b/sfcgal/regress/tests.mk.in
@@ -46,5 +46,6 @@ ifeq ($(shell expr "$(POSTGIS_SFCGAL_VERSION)" ">=" 20100),1)
 endif
 ifeq ($(shell expr "$(POSTGIS_SFCGAL_VERSION)" ">=" 20300),1)
 	TESTS += \
-		$(top_srcdir)/sfcgal/regress/alphashape_components.sql
+		$(top_srcdir)/sfcgal/regress/alphashape_components.sql \
+		$(top_srcdir)/sfcgal/regress/roofgeneration.sql
 endif

commit ea9aa716c07a95b1ebd586cdf7e8ca6c506b3acf
Author: Loïc Bartoletti <loic.bartoletti at oslandia.com>
Date:   Mon Jun 8 14:29:40 2026 +0200

    feat(sfcgal): add CG_GenerateFlatRoof, CG_GenerateHippedRoof, CG_GenerateGableRoof, CG_GenerateSkillionRoof, CG_GenerateRoof

diff --git a/sfcgal/lwgeom_sfcgal.c b/sfcgal/lwgeom_sfcgal.c
index 66b2b33cc..bf58c04a7 100644
--- a/sfcgal/lwgeom_sfcgal.c
+++ b/sfcgal/lwgeom_sfcgal.c
@@ -86,6 +86,11 @@ Datum sfcgal_convexhull3D(PG_FUNCTION_ARGS);
 Datum sfcgal_alphashape(PG_FUNCTION_ARGS);
 Datum sfcgal_optimalalphashape(PG_FUNCTION_ARGS);
 Datum sfcgal_simplify(PG_FUNCTION_ARGS);
+Datum sfcgal_generate_flat_roof(PG_FUNCTION_ARGS);
+Datum sfcgal_generate_hipped_roof(PG_FUNCTION_ARGS);
+Datum sfcgal_generate_gable_roof(PG_FUNCTION_ARGS);
+Datum sfcgal_generate_skillion_roof(PG_FUNCTION_ARGS);
+Datum sfcgal_generate_roof(PG_FUNCTION_ARGS);
 
 GSERIALIZED *geometry_serialize(LWGEOM *lwgeom);
 char *text_to_cstring(const text *textptr);
@@ -1856,3 +1861,248 @@ sfcgal_alphawrapping_3d(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(output);
 #endif
 }
+
+PG_FUNCTION_INFO_V1(sfcgal_generate_flat_roof);
+Datum
+sfcgal_generate_flat_roof(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_generate_flat_roof' function (requires SFCGAL 2.3.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	double height;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	if (gserialized_get_type(input) != POLYGONTYPE)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateFlatRoof: input geometry must be a Polygon");
+		PG_RETURN_NULL();
+	}
+	height = PG_GETARG_FLOAT8(1);
+	srid = gserialized_get_srid(input);
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_generate_flat_roof(geom, height);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 1, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
+
+PG_FUNCTION_INFO_V1(sfcgal_generate_hipped_roof);
+Datum
+sfcgal_generate_hipped_roof(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_generate_hipped_roof' function (requires SFCGAL 2.3.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	double height;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	if (gserialized_get_type(input) != POLYGONTYPE)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateHippedRoof: input geometry must be a Polygon");
+		PG_RETURN_NULL();
+	}
+	height = PG_GETARG_FLOAT8(1);
+	srid = gserialized_get_srid(input);
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_generate_hipped_roof(geom, height);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 1, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
+
+PG_FUNCTION_INFO_V1(sfcgal_generate_gable_roof);
+Datum
+sfcgal_generate_gable_roof(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_generate_gable_roof' function (requires SFCGAL 2.3.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	double height;
+	double slope_angle;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	if (gserialized_get_type(input) != POLYGONTYPE)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateGableRoof: input geometry must be a Polygon");
+		PG_RETURN_NULL();
+	}
+	height = PG_GETARG_FLOAT8(1);
+	slope_angle = PG_GETARG_FLOAT8(2);
+	srid = gserialized_get_srid(input);
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_generate_gable_roof(geom, height, slope_angle);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 1, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
+
+PG_FUNCTION_INFO_V1(sfcgal_generate_skillion_roof);
+Datum
+sfcgal_generate_skillion_roof(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_generate_skillion_roof' function (requires SFCGAL 2.3.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	double height;
+	double slope_angle;
+	int primary_edge_index;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	if (gserialized_get_type(input) != POLYGONTYPE)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateSkillionRoof: input geometry must be a Polygon");
+		PG_RETURN_NULL();
+	}
+	height = PG_GETARG_FLOAT8(1);
+	slope_angle = PG_GETARG_FLOAT8(2);
+	primary_edge_index = PG_GETARG_INT32(3);
+	if (primary_edge_index < 0)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateSkillionRoof: primary_edge_index must be >= 0");
+		PG_RETURN_NULL();
+	}
+	srid = gserialized_get_srid(input);
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_generate_skillion_roof(geom, height, slope_angle, (size_t)primary_edge_index);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 1, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
+
+PG_FUNCTION_INFO_V1(sfcgal_generate_roof);
+Datum
+sfcgal_generate_roof(PG_FUNCTION_ARGS)
+{
+#if POSTGIS_SFCGAL_VERSION < 20300
+	lwpgerror(
+	    "The SFCGAL version this PostGIS binary was compiled against (%d) doesn't support "
+	    "'sfcgal_geometry_generate_roof' function (requires SFCGAL 2.3.0+)",
+	    POSTGIS_SFCGAL_VERSION);
+	PG_RETURN_NULL();
+#else
+	GSERIALIZED *input, *output;
+	sfcgal_geometry_t *geom, *result;
+	sfcgal_roof_type_t roof_type;
+	double height;
+	double slope_angle;
+	int primary_edge_index;
+	srid_t srid;
+
+	sfcgal_postgis_init();
+
+	input = PG_GETARG_GSERIALIZED_P(0);
+	if (gserialized_get_type(input) != POLYGONTYPE)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateRoof: input geometry must be a Polygon");
+		PG_RETURN_NULL();
+	}
+	height = PG_GETARG_FLOAT8(2);
+	slope_angle = PG_GETARG_FLOAT8(3);
+	primary_edge_index = PG_GETARG_INT32(4);
+	if (primary_edge_index < 0)
+	{
+		PG_FREE_IF_COPY(input, 0);
+		lwpgerror("CG_GenerateRoof: primary_edge_index must be >= 0");
+		PG_RETURN_NULL();
+	}
+	srid = gserialized_get_srid(input);
+
+	{
+		char *type_str = text_to_cstring(PG_GETARG_TEXT_P(1));
+
+		if (strcmp(type_str, "FLAT") == 0)
+			roof_type = SFCGAL_ROOF_FLAT;
+		else if (strcmp(type_str, "HIPPED") == 0)
+			roof_type = SFCGAL_ROOF_HIPPED;
+		else if (strcmp(type_str, "GABLE") == 0)
+			roof_type = SFCGAL_ROOF_GABLE;
+		else if (strcmp(type_str, "SKILLION") == 0)
+			roof_type = SFCGAL_ROOF_SKILLION;
+		else
+		{
+			lwpgerror("CG_GenerateRoof: unknown roof type '%s', expected FLAT, HIPPED, GABLE or SKILLION",
+			          type_str);
+			pfree(type_str);
+			PG_RETURN_NULL();
+		}
+		pfree(type_str); /* alloc'ed in text_to_cstring */
+	}
+
+	geom = POSTGIS2SFCGALGeometry(input);
+	PG_FREE_IF_COPY(input, 0);
+
+	result = sfcgal_geometry_generate_roof(geom, roof_type, slope_angle, height, (size_t)primary_edge_index);
+	sfcgal_geometry_delete(geom);
+
+	output = SFCGALGeometry2POSTGIS(result, 1, srid);
+	sfcgal_geometry_delete(result);
+
+	PG_RETURN_POINTER(output);
+#endif
+}
diff --git a/sfcgal/sfcgal.sql.in b/sfcgal/sfcgal.sql.in
index e377d6691..dcba5b678 100644
--- a/sfcgal/sfcgal.sql.in
+++ b/sfcgal/sfcgal.sql.in
@@ -609,4 +609,39 @@ AS 'MODULE_PATHNAME', 'sfcgal_alphawrapping_3d'
 LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
 _COST_LOW;
 
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0
+CREATE OR REPLACE FUNCTION CG_GenerateFlatRoof(geom geometry, height float8 DEFAULT 3.0)
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_generate_flat_roof'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0
+CREATE OR REPLACE FUNCTION CG_GenerateHippedRoof(geom geometry, height float8 DEFAULT 3.0)
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_generate_hipped_roof'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0
+CREATE OR REPLACE FUNCTION CG_GenerateGableRoof(geom geometry, height float8 DEFAULT 3.0, slope_angle float8 DEFAULT 30.0)
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_generate_gable_roof'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0
+CREATE OR REPLACE FUNCTION CG_GenerateSkillionRoof(geom geometry, height float8 DEFAULT 3.0, slope_angle float8 DEFAULT 30.0, primary_edge_index integer DEFAULT 0)
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_generate_skillion_roof'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
+-- Availability: 3.7.0 - requires SFCGAL >= 2.3.0
+CREATE OR REPLACE FUNCTION CG_GenerateRoof(geom geometry, roof_type text DEFAULT 'HIPPED', height float8 DEFAULT 3.0, slope_angle float8 DEFAULT 30.0, primary_edge_index integer DEFAULT 0)
+RETURNS geometry
+AS 'MODULE_PATHNAME', 'sfcgal_generate_roof'
+LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+_COST_DEFAULT;
+
 COMMIT;

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

Summary of changes:
 configure.ac                                       |  13 +
 doc/html/images/static/cg_generateflatroof01.png   | Bin 0 -> 3249 bytes
 doc/html/images/static/cg_generategableroof01.png  | Bin 0 -> 2624 bytes
 doc/html/images/static/cg_generatehippedroof01.png | Bin 0 -> 3072 bytes
 .../images/static/cg_generateskillionroof01.png    | Bin 0 -> 3306 bytes
 doc/reference_sfcgal.xml                           | 438 +++++++++++++++++++++
 doc/xsl/sfcgal_gardentest.sql.xsl                  |   2 +-
 sfcgal/lwgeom_sfcgal.c                             | 338 ++++++++++++++++
 sfcgal/regress/approximatemedialaxis.sql           |   5 +-
 sfcgal/regress/approximatemedialaxis_expected      |   4 +-
 sfcgal/regress/approximatemedialaxis_projected.sql |   3 +
 .../approximatemedialaxis_projected_expected       |   2 +
 .../approximatemedialaxis_projected_pre230.sql     |   3 +
 ...approximatemedialaxis_projected_pre230_expected |   4 +
 sfcgal/regress/polygonrepair.sql                   |   8 +
 sfcgal/regress/polygonrepair_expected              |   4 +
 sfcgal/regress/polygonrepair_union.sql             |   2 +
 sfcgal/regress/polygonrepair_union_expected        |   1 +
 sfcgal/regress/polygonrepair_union_pre61.sql       |   2 +
 sfcgal/regress/polygonrepair_union_pre61_expected  |   1 +
 sfcgal/regress/roofgeneration.sql                  |  16 +
 sfcgal/regress/roofgeneration_expected             |  11 +
 sfcgal/regress/tests.mk.in                         |  14 +-
 sfcgal/sfcgal.sql.in                               |  50 +++
 24 files changed, 917 insertions(+), 4 deletions(-)
 create mode 100644 doc/html/images/static/cg_generateflatroof01.png
 create mode 100644 doc/html/images/static/cg_generategableroof01.png
 create mode 100644 doc/html/images/static/cg_generatehippedroof01.png
 create mode 100644 doc/html/images/static/cg_generateskillionroof01.png
 create mode 100644 sfcgal/regress/approximatemedialaxis_projected.sql
 create mode 100644 sfcgal/regress/approximatemedialaxis_projected_expected
 create mode 100644 sfcgal/regress/approximatemedialaxis_projected_pre230.sql
 create mode 100644 sfcgal/regress/approximatemedialaxis_projected_pre230_expected
 create mode 100644 sfcgal/regress/polygonrepair.sql
 create mode 100644 sfcgal/regress/polygonrepair_expected
 create mode 100644 sfcgal/regress/polygonrepair_union.sql
 create mode 100644 sfcgal/regress/polygonrepair_union_expected
 create mode 100644 sfcgal/regress/polygonrepair_union_pre61.sql
 create mode 100644 sfcgal/regress/polygonrepair_union_pre61_expected
 create mode 100644 sfcgal/regress/roofgeneration.sql
 create mode 100644 sfcgal/regress/roofgeneration_expected


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list