From git at osgeo.org Fri May 1 10:42:26 2026 From: git at osgeo.org (git at osgeo.org) Date: Fri, 1 May 2026 10:42:26 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-474-g0e794aa47 Message-ID: <20260501174227.6F3D418146B@trac.osgeo.org> 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 0e794aa474544d0da986d4ba7a807103b97c129a (commit) from 26012786958aedb55589c0696086f7c48fc9fc46 (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 0e794aa474544d0da986d4ba7a807103b97c129a Author: Paul Ramsey Date: Fri May 1 10:41:07 2026 -0700 ST_CatmullSmoothing(geom, nSegments) "smoothes" a LineString or LinearRing by adding vertices, and leaves all the input vertices unchanged. The nSegments parameter controls how many vertices are added between each input pair. Closes #4398 diff --git a/NEWS b/NEWS index 1a59e317b..b66809e9b 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,7 @@ This version requires GEOS 3.10 or higher - #5889, [topology] Include representative locations in topology build errors (Darafei Praliaskouski) - #2614, Use GEOSPreparedDistance and caching to accelerate ST_DWithin (Paul Ramsey) - GH-839, ST_Multi support for TIN and surfaces (Lo?c Bartoletti) + - #4398, Add ST_CatmullSmoothing smoothes but retains original vertices (Paul Ramsey) * Enhancements * diff --git a/doc/html/images/Makefile.in b/doc/html/images/Makefile.in index 203142611..288065530 100644 --- a/doc/html/images/Makefile.in +++ b/doc/html/images/Makefile.in @@ -85,6 +85,10 @@ GENERATED_IMAGES= \ st_centroid02.png \ st_centroid03.png \ st_centroid04.png \ + st_catmullromsmoothing01.png \ + st_catmullromsmoothing02.png \ + st_catmullromsmoothing03.png \ + st_catmullromsmoothing04.png \ st_chaikinsmoothing01.png \ st_chaikinsmoothing02.png \ st_chaikinsmoothing03.png \ diff --git a/doc/html/images/wkt/st_catmullromsmoothing01.wkt b/doc/html/images/wkt/st_catmullromsmoothing01.wkt new file mode 100644 index 000000000..a2ecc12a5 --- /dev/null +++ b/doc/html/images/wkt/st_catmullromsmoothing01.wkt @@ -0,0 +1,2 @@ +ArgA-thin;POLYGON ((20 20, 60 90, 10 150, 100 190, 190 160, 130 120, 190 50, 140 70, 120 10, 90 60, 20 20)) +Result;POLYGON((20 20,22.4 27.12,32.4 40.56,45.2 57.44,56 74.88,60 90,53.52 102.96,39.76 115.68,24.24 127.92,12.48 139.44,10 150,19.04 160.4,35.92 170.8,57.28 180,79.76 186.8,100 190,120.4 188.64,143.2 183.52,164.8 176.08,181.6 167.76,190 160,185.68 153.12,171.04 146.16,152.56 138.64,136.72 130.08,130 120,136.08 106.48,150.64 89.84,168.16 72.96,183.12 58.72,190 50,186.56 49.52,176.48 55.36,163.12 63.44,149.84 69.68,140 70,134.24 61.36,130.32 46.48,127.28 29.92,124.16 16.24,120 10,115.28 14.4,110.64 26.4,105.36 41.2,98.72 54,90 60,76.8 56,59.6 45.2,42 32.4,27.6 22.4,20 20)) diff --git a/doc/html/images/wkt/st_catmullromsmoothing02.wkt b/doc/html/images/wkt/st_catmullromsmoothing02.wkt new file mode 100644 index 000000000..eeeed2a86 --- /dev/null +++ b/doc/html/images/wkt/st_catmullromsmoothing02.wkt @@ -0,0 +1,2 @@ +ArgA-thin;POLYGON ((20 20, 60 90, 10 150, 100 190, 190 160, 130 120, 190 50, 140 70, 120 10, 90 60, 20 20)) +Result;POLYGON((20 20,19.95 22.59,22.4 27.12,26.75 33.23,32.4 40.56,38.75 48.75,45.2 57.44,51.15 66.27,56 74.88,59.15 82.91,60 90,58.015 96.495,53.52 102.96,47.205 109.365,39.76 115.68,31.875 121.875,24.24 127.92,17.545 133.785,12.48 139.44,9.735 144.855,10 150,13.33 155.125,19.04 160.4,26.71 165.675,35.92 170.8,46.25 175.625,57.28 180,68.59 183.775,79.76 186.8,90.37 188.925,100 190,109.675 189.88,120.4 188.64,131.725 186.46,143.2 183.52,154.375 180,164.8 176.08,174.025 171.94,181.6 167.76,187.075 163.72,190 160,189.535 156.54,185.68 153.12,179.245 149.68,171.04 146.16,161.875 142.5,152.56 138.64,143.905 134.52,136.72 130.08,131.815 125.26,130 120,131.635 113.81,136.08 106.48,142.645 98.37,150.64 89.84,159.375 81.25,168.16 72.96,176.305 65.33,183.12 58.72,187.915 53.49,190 50,189.32 48.715,186.56 49.52,182.14 51.905,176.48 55.36,170 59.375,163.12 63.44,156.26 67.045,149.84 69.68,144.28 70.835,140 70,136.83 66.745,134.24 61.36,132.11 54.415,130.32 46.48,128.75 38.125,127.28 29.92,125 .79 22.435,124.16 16.24,122.27 11.905,120 10,117.585 10.95,115.28 14.4,112.995 19.75,110.64 26.4,108.125 33.75,105.36 41.2,102.255 48.15,98.72 54,94.665 58.15,90 60,84.125 59.15,76.8 56,68.475 51.15,59.6 45.2,50.625 38.75,42 32.4,34.175 26.75,27.6 22.4,22.725 19.95,20 20)) diff --git a/doc/html/images/wkt/st_catmullromsmoothing03.wkt b/doc/html/images/wkt/st_catmullromsmoothing03.wkt new file mode 100644 index 000000000..53817d248 --- /dev/null +++ b/doc/html/images/wkt/st_catmullromsmoothing03.wkt @@ -0,0 +1,2 @@ +ArgA-thin;LINESTRING (10 140, 80 130, 100 190, 190 150, 140 20, 120 120, 50 30, 30 100) +Result;LINESTRING(10 140,24.8 136.88,40.4 132.64,55.6 128.96,69.2 127.52,80 130,86.08 139.12,88.24 153.76,89.36 169.84,92.32 183.28,100 190,115.76 189.84,137.68 185.52,160.72 177.28,179.84 165.36,190 150,188.48 126.08,178.64 93.44,164.56 59.76,150.32 32.72,140 20,134.88 28.32,132.24 52.56,130.16 82.64,126.72 108.48,120 120,108.4 111.6,93.2 90,76.8 63.6,61.6 40.8,50 30,42.8 33.76,38.4 46.48,35.6 64.32,33.2 83.44,30 100) diff --git a/doc/html/images/wkt/st_catmullromsmoothing04.wkt b/doc/html/images/wkt/st_catmullromsmoothing04.wkt new file mode 100644 index 000000000..1bfb3792f --- /dev/null +++ b/doc/html/images/wkt/st_catmullromsmoothing04.wkt @@ -0,0 +1,2 @@ +ArgA-thin;LINESTRING (10 140, 80 130, 100 190, 190 150, 140 20, 120 120, 50 30, 30 100) +Result;LINESTRING(10 140,17.225 138.685,24.8 136.88,32.575 134.795,40.4 132.64,48.125 130.625,55.6 128.96,62.675 127.855,69.2 127.52,75.025 128.165,80 130,83.71 133.615,86.08 139.12,87.47 146.005,88.24 153.76,88.75 161.875,89.36 169.84,90.43 177.145,92.32 183.28,95.39 187.735,100 190,106.795 190.455,115.76 189.84,126.265 188.185,137.68 185.52,149.375 181.875,160.72 177.28,171.085 171.765,179.84 165.36,186.355 158.095,190 150,190.535 139.61,188.48 126.08,184.345 110.37,178.64 93.44,171.875 76.25,164.56 59.76,157.205 44.93,150.32 32.72,144.415 24.09,140 20,137.01 21.54,134.88 28.32,133.37 39.08,132.24 52.56,131.25 67.5,130.16 82.64,128.73 96.72,126.72 108.48,123.89 116.66,120 120,114.8 117.975,108.4 111.6,101.1 101.925,93.2 90,85 76.875,76.8 63.6,68.9 51.225,61.6 40.8,55.2 33.375,50 30,45.975 30.52,42.8 33.76,40.325 39.24,38.4 46.48,36.875 55,35.6 64.32,34.425 73.96,33.2 83.44,31.775 92.28,30 100) diff --git a/doc/reference_processing.xml b/doc/reference_processing.xml index f4d7100ba..2f369b2ed 100644 --- a/doc/reference_processing.xml +++ b/doc/reference_processing.xml @@ -794,6 +794,140 @@ SELECT ST_ChaikinSmoothing( + + + ST_CatmullRomSmoothing + Returns a smoothed version of a geometry, using the Catmull-Rom spline algorithm + + + + + + geometry ST_CatmullRomSmoothing + geometry geom + integer nSegments = 5 + + + + + + Description + Smoothes a linear or polygonal geometry using the + Catmull-Rom spline + algorithm. Unlike , this is an interpolating + spline: the output curve passes through every original vertex. Between each pair of + consecutive original vertices, nSegments - 1 new intermediate + points are inserted. + + + The nSegments parameter controls the density of the output. + With nSegments = 5 (the default), each original span is + divided into 5 sub-segments (inserting 4 new points per span). The minimum value is 2. + + + At least 4 input vertices are required to apply smoothing; + geometries with fewer vertices are returned unchanged. + Points and multipoints are always returned unchanged. + + + The output vertex count grows as + 1 + (N-1) * nSegments for open lines, + and 1 + N * nSegments for closed rings, + where N is the number of input vertices. + For large geometries, use a simplification function on the result + to reduce the number of points + (see , + and ). + + + The result has interpolated values for the Z and M dimensions when present. + + &Z_support; + Availability: 3.6.0 + + + + Examples + Smoothing a 4-point collinear line with default nSegments=5: + +SELECT ST_AsText(ST_CatmullRomSmoothing('LINESTRING(0 0, 5 0, 10 0, 15 0)')); + + LINESTRING(0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 0) + + + Smoothing a Polygon using nSegments = 5 and 10: + + + + + + + + + + nSegments = 5 + + + + + + + + + nSegments = 10 + + + + + + + +SELECT ST_CatmullRomSmoothing( + 'POLYGON ((20 20, 60 90, 10 150, 100 190, 190 160, 130 120, 190 50, 140 70, 120 10, 90 60, 20 20))', + generate_series(5, 10, 5) ); + + + Smoothing a LineString using nSegments = 5 and 10: + + + + + + + + + + nSegments = 5 + + + + + + + + + nSegments = 10 + + + + + + + +SELECT ST_CatmullRomSmoothing( + 'LINESTRING (10 140, 80 130, 100 190, 190 150, 140 20, 120 120, 50 30, 30 100)', + generate_series(5, 10, 5) ); + + + + + See Also + , , + , + + + + ST_ConcaveHull diff --git a/liblwgeom/Makefile.in b/liblwgeom/Makefile.in index caed0abdb..f74c2d9a9 100644 --- a/liblwgeom/Makefile.in +++ b/liblwgeom/Makefile.in @@ -131,7 +131,7 @@ SA_OBJS = \ lwgeom_wrapx.o \ lwunionfind.o \ effectivearea.o \ - lwchaikins.o \ + lwsmoothing.o \ lwmval.o \ lwkmeans.o \ varint.o \ diff --git a/liblwgeom/cunit/Makefile.in b/liblwgeom/cunit/Makefile.in index f397414c9..791be060e 100644 --- a/liblwgeom/cunit/Makefile.in +++ b/liblwgeom/cunit/Makefile.in @@ -48,6 +48,7 @@ OBJS= \ cu_measures.o \ cu_effectivearea.o \ cu_chaikin.o \ + cu_catmullrom.o \ cu_filterm.o \ cu_node.o \ cu_clip_by_rect.o \ diff --git a/liblwgeom/cunit/cu_catmullrom.c b/liblwgeom/cunit/cu_catmullrom.c new file mode 100644 index 000000000..1d9ff4e0c --- /dev/null +++ b/liblwgeom/cunit/cu_catmullrom.c @@ -0,0 +1,201 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2026 PostGIS contributors + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void +do_test_catmull_rom(char *geom_txt, char *expected, int n_segments) +{ + LWGEOM *geom_in, *geom_out; + char *out_txt; + geom_in = lwgeom_from_wkt(geom_txt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_catmull_rom(geom_in, n_segments); + out_txt = lwgeom_to_wkt(geom_out, WKT_EXTENDED, 6, NULL); + if (strcmp(expected, out_txt)) + printf("\nExpected: %s\n Got: %s\n", expected, out_txt); + ASSERT_STRING_EQUAL(expected, out_txt); + lwfree(out_txt); + lwgeom_free(geom_in); + lwgeom_free(geom_out); +} + +/* + * A collinear 4-point line: Catmull-Rom on a straight line must produce + * a straight line with original vertices preserved at their positions. + * With n_segments=2 we get 1 + 3*2 = 7 output points. + */ +static void +do_test_catmull_rom_lines(void) +{ + /* Collinear: output must be straight, original vertices preserved */ + do_test_catmull_rom( + "LINESTRING(0 0, 1 0, 2 0, 3 0)", + "LINESTRING(0 0,0.5 0,1 0,1.5 0,2 0,2.5 0,3 0)", + 2); + + /* Fewer than 4 points: return input unchanged */ + do_test_catmull_rom( + "LINESTRING(0 0, 1 1, 2 0)", + "LINESTRING(0 0,1 1,2 0)", + 5); + + /* Exactly 2 points: return input unchanged */ + do_test_catmull_rom( + "LINESTRING(0 0, 10 10)", + "LINESTRING(0 0,10 10)", + 5); + + /* Empty line: return empty */ + do_test_catmull_rom( + "LINESTRING EMPTY", + "LINESTRING EMPTY", + 5); +} + +/* + * Verify that original vertices are preserved by checking a symmetric + * curve where we can identify exactly where original points fall. + * LINESTRING(0 0, 0 10, 10 10, 10 0) with n_segments=1 is trivial + * (n_segments=1 is invalid per SQL, but at the C level we test =2). + */ +static void +do_test_catmull_rom_vertex_preservation(void) +{ + LWGEOM *geom_in, *geom_out; + POINT4D pt; + + /* With n_segments=3 on a 4-point line, output has 1+3*3=10 points. + * Original vertices must appear at indices 0, 3, 6, 9. */ + geom_in = lwgeom_from_wkt("LINESTRING(0 0, 1 0, 2 0, 3 0)", LW_PARSER_CHECK_NONE); + geom_out = lwgeom_catmull_rom(geom_in, 3); + + LWLINE *oline = (LWLINE *)geom_out; + CU_ASSERT_EQUAL(oline->points->npoints, 10); + + /* Check original vertices at expected positions */ + pt = getPoint4d(oline->points, 0); + CU_ASSERT_DOUBLE_EQUAL(pt.x, 0.0, 1e-10); + CU_ASSERT_DOUBLE_EQUAL(pt.y, 0.0, 1e-10); + + pt = getPoint4d(oline->points, 3); + CU_ASSERT_DOUBLE_EQUAL(pt.x, 1.0, 1e-10); + CU_ASSERT_DOUBLE_EQUAL(pt.y, 0.0, 1e-10); + + pt = getPoint4d(oline->points, 6); + CU_ASSERT_DOUBLE_EQUAL(pt.x, 2.0, 1e-10); + CU_ASSERT_DOUBLE_EQUAL(pt.y, 0.0, 1e-10); + + pt = getPoint4d(oline->points, 9); + CU_ASSERT_DOUBLE_EQUAL(pt.x, 3.0, 1e-10); + CU_ASSERT_DOUBLE_EQUAL(pt.y, 0.0, 1e-10); + + lwgeom_free(geom_in); + lwgeom_free(geom_out); +} + +/* + * Polygon ring smoothing: a 5-point square ring (4 unique vertices + closing) + * is the minimum for Catmull-Rom on a ring (needs npoints-1 >= 4). + */ +static void +do_test_catmull_rom_polygons(void) +{ + LWGEOM *geom_in, *geom_out; + LWPOLY *opoly; + + /* Polygon with 5 ring points (4 unique + close): smooth with n_segments=2. + * Each ring span produces 2 sub-segments, 4 spans total ? 4*2+1 = 9 ring pts. */ + geom_in = lwgeom_from_wkt( + "POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))", + LW_PARSER_CHECK_NONE); + geom_out = lwgeom_catmull_rom(geom_in, 2); + + opoly = (LWPOLY *)geom_out; + CU_ASSERT_EQUAL(opoly->nrings, 1); + CU_ASSERT_EQUAL(opoly->rings[0]->npoints, 9); + + /* First and last ring points must be identical (closed) */ + POINT4D first = getPoint4d(opoly->rings[0], 0); + POINT4D last = getPoint4d(opoly->rings[0], opoly->rings[0]->npoints - 1); + CU_ASSERT_DOUBLE_EQUAL(first.x, last.x, 1e-10); + CU_ASSERT_DOUBLE_EQUAL(first.y, last.y, 1e-10); + + lwgeom_free(geom_in); + lwgeom_free(geom_out); + + /* Triangle ring (npoints-1 = 3 < 4): return original ring unchanged */ + geom_in = lwgeom_from_wkt( + "POLYGON((0 0, 4 0, 2 4, 0 0))", + LW_PARSER_CHECK_NONE); + geom_out = lwgeom_catmull_rom(geom_in, 5); + opoly = (LWPOLY *)geom_out; + CU_ASSERT_EQUAL(opoly->rings[0]->npoints, 4); /* unchanged */ + lwgeom_free(geom_in); + lwgeom_free(geom_out); +} + +/* + * Z and M dimensions must be interpolated by the algorithm. + */ +static void +do_test_catmull_rom_zm(void) +{ + LWGEOM *geom_in, *geom_out; + LWLINE *oline; + POINT4D pt; + + /* Collinear in XYZ: Z should be interpolated linearly on a straight line */ + geom_in = lwgeom_from_wkt( + "LINESTRING Z (0 0 0, 1 0 10, 2 0 20, 3 0 30)", + LW_PARSER_CHECK_NONE); + geom_out = lwgeom_catmull_rom(geom_in, 2); + oline = (LWLINE *)geom_out; + + /* Z at midpoint of first span (index 1) should be ~5 */ + pt = getPoint4d(oline->points, 1); + CU_ASSERT_DOUBLE_EQUAL(pt.z, 5.0, 1e-10); + + /* Original Z values preserved at span boundaries */ + pt = getPoint4d(oline->points, 2); + CU_ASSERT_DOUBLE_EQUAL(pt.z, 10.0, 1e-10); + + lwgeom_free(geom_in); + lwgeom_free(geom_out); +} + +/* + * Points pass through unchanged. + */ +static void +do_test_catmull_rom_points(void) +{ + do_test_catmull_rom("POINT(1 2)", "POINT(1 2)", 5); + do_test_catmull_rom("MULTIPOINT((1 2),(3 4))", "MULTIPOINT(1 2,3 4)", 5); +} + + +void catmullrom_suite_setup(void); +void catmullrom_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("catmullrom", NULL, NULL); + PG_ADD_TEST(suite, do_test_catmull_rom_lines); + PG_ADD_TEST(suite, do_test_catmull_rom_vertex_preservation); + PG_ADD_TEST(suite, do_test_catmull_rom_polygons); + PG_ADD_TEST(suite, do_test_catmull_rom_zm); + PG_ADD_TEST(suite, do_test_catmull_rom_points); +} diff --git a/liblwgeom/cunit/cu_tester.c b/liblwgeom/cunit/cu_tester.c index 708b8070c..a32c3f41a 100644 --- a/liblwgeom/cunit/cu_tester.c +++ b/liblwgeom/cunit/cu_tester.c @@ -53,6 +53,7 @@ extern void lwstroke_suite_setup(void); extern void measures_suite_setup(void); extern void effectivearea_suite_setup(void); extern void chaikin_suite_setup(void); +extern void catmullrom_suite_setup(void); extern void filterm_suite_setup(void); extern void minimum_bounding_circle_suite_setup(void); extern void misc_suite_setup(void); @@ -107,6 +108,7 @@ PG_SuiteSetup setupfuncs[] = {algorithms_suite_setup, measures_suite_setup, effectivearea_suite_setup, chaikin_suite_setup, + catmullrom_suite_setup, filterm_suite_setup, minimum_bounding_circle_suite_setup, misc_suite_setup, diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index 9ff00e1a4..401ac78cf 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -1132,6 +1132,7 @@ extern LWGEOM* lwgeom_force_4d(const LWGEOM *geom, double zval, double mval); extern LWGEOM* lwgeom_set_effective_area(const LWGEOM *igeom, int set_area, double area); extern LWGEOM* lwgeom_chaikin(const LWGEOM *igeom, int n_iterations, int preserve_endpoint); +extern LWGEOM* lwgeom_catmull_rom(const LWGEOM *igeom, int n_segments); extern LWGEOM* lwgeom_filter_m(LWGEOM *geom, double min, double max, int returnm); /* diff --git a/liblwgeom/lwchaikins.c b/liblwgeom/lwchaikins.c deleted file mode 100644 index 4312a8631..000000000 --- a/liblwgeom/lwchaikins.c +++ /dev/null @@ -1,202 +0,0 @@ - -/********************************************************************** - * - * PostGIS - Spatial Types for PostgreSQL - * http://postgis.net - * - * PostGIS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * PostGIS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with PostGIS. If not, see . - * - ********************************************************************** - * - * Copyright 2018 Nicklas Av?n - * - **********************************************************************/ - - - #include "liblwgeom_internal.h" - - - -static POINTARRAY * ptarray_chaikin(POINTARRAY *inpts, int preserve_endpoint, int isclosed) -{ - uint32_t p, i, n_out_points=0, p1_set=0, p2_set=0; - POINT4D p1, p2; - POINTARRAY *opts; - double *dlist; - double deltaval, quarter_delta, val1, val2; - uint32_t ndims = 2 + ptarray_has_z(inpts) + ptarray_has_m(inpts); - int new_npoints = inpts->npoints * 2; - opts = ptarray_construct_empty(FLAGS_GET_Z(inpts->flags), FLAGS_GET_M(inpts->flags), new_npoints); - - dlist = (double*)(opts->serialized_pointlist); - - p1 = getPoint4d(inpts, 0); - /*if first point*/ - if(preserve_endpoint) - { - ptarray_append_point(opts, &p1, LW_TRUE); - n_out_points++; - } - - for (p=1;pnpoints;p++) - { - memcpy(&p2, &p1, sizeof(POINT4D)); - p1 = getPoint4d(inpts, p); - if(p>0) - { - p1_set = p2_set = 0; - for (i=0;i 1) - { - dlist[n_out_points * ndims + i] = val2 + quarter_delta; - p1_set = 1; - } - if(!preserve_endpoint || p < inpts->npoints - 1) - { - dlist[(n_out_points + p1_set) * ndims + i] = val1 - quarter_delta; - p2_set = 1; - } - } - n_out_points+=(p1_set + p2_set); - } - } - - /*if last point*/ - if(preserve_endpoint) - { - opts->npoints = n_out_points; - ptarray_append_point(opts, &p1, LW_TRUE); - n_out_points++; - } - - if(isclosed && !preserve_endpoint) - { - opts->npoints = n_out_points; - POINT4D first_point = getPoint4d(opts, 0); - ptarray_append_point(opts, &first_point, LW_TRUE); - n_out_points++; - } - opts->npoints = n_out_points; - - return opts; - -} - -static LWLINE* lwline_chaikin(const LWLINE *iline, int n_iterations) -{ - POINTARRAY *pa, *pa_new; - int j; - LWLINE *oline; - - if( lwline_is_empty(iline)) - return lwline_clone(iline); - - pa = iline->points; - for (j=0;j0) - ptarray_free(pa); - pa=pa_new; - } - oline = lwline_construct(iline->srid, NULL, pa); - - oline->type = iline->type; - return oline; - -} - - -static LWPOLY* lwpoly_chaikin(const LWPOLY *ipoly, int n_iterations, int preserve_endpoint) -{ - uint32_t i; - int j; - POINTARRAY *pa, *pa_new; - LWPOLY *opoly = lwpoly_construct_empty(ipoly->srid, FLAGS_GET_Z(ipoly->flags), FLAGS_GET_M(ipoly->flags)); - - if( lwpoly_is_empty(ipoly) ) - return opoly; - for (i = 0; i < ipoly->nrings; i++) - { - pa = ipoly->rings[i]; - for(j=0;j0) - ptarray_free(pa); - pa=pa_new; - } - if(pa->npoints>=4) - { - if( lwpoly_add_ring(opoly,pa ) == LW_FAILURE ) - return NULL; - } - } - - opoly->type = ipoly->type; - - if( lwpoly_is_empty(opoly) ) - return NULL; - - return opoly; - -} - - -static LWCOLLECTION* lwcollection_chaikin(const LWCOLLECTION *igeom, int n_iterations, int preserve_endpoint) -{ - LWDEBUG(2, "Entered lwcollection_set_effective_area"); - uint32_t i; - - LWCOLLECTION *out = lwcollection_construct_empty(igeom->type, igeom->srid, FLAGS_GET_Z(igeom->flags), FLAGS_GET_M(igeom->flags)); - - if( lwcollection_is_empty(igeom) ) - return out; /* should we return NULL instead ? */ - - for( i = 0; i < igeom->ngeoms; i++ ) - { - LWGEOM *ngeom = lwgeom_chaikin(igeom->geoms[i],n_iterations,preserve_endpoint); - if ( ngeom ) out = lwcollection_add_lwgeom(out, ngeom); - } - - return out; -} - - -LWGEOM* lwgeom_chaikin(const LWGEOM *igeom, int n_iterations, int preserve_endpoint) -{ - LWDEBUG(2, "Entered lwgeom_set_effective_area"); - switch (igeom->type) - { - case POINTTYPE: - case MULTIPOINTTYPE: - return lwgeom_clone(igeom); - case LINETYPE: - return (LWGEOM*)lwline_chaikin((LWLINE*)igeom, n_iterations); - case POLYGONTYPE: - return (LWGEOM*)lwpoly_chaikin((LWPOLY*)igeom,n_iterations,preserve_endpoint); - case MULTILINETYPE: - case MULTIPOLYGONTYPE: - case COLLECTIONTYPE: - return (LWGEOM*)lwcollection_chaikin((LWCOLLECTION *)igeom,n_iterations,preserve_endpoint); - default: - lwerror("lwgeom_chaikin: unsupported geometry type: %s",lwtype_name(igeom->type)); - } - return NULL; -} diff --git a/liblwgeom/lwsmoothing.c b/liblwgeom/lwsmoothing.c new file mode 100644 index 000000000..8d7e256f5 --- /dev/null +++ b/liblwgeom/lwsmoothing.c @@ -0,0 +1,432 @@ + +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Chaikin smoothing: Copyright 2018 Nicklas Av?n + * Catmull-Rom smoothing: Copyright 2026 PostGIS contributors + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" + + +/* ======================================================================== + * Chaikin smoothing + * ======================================================================== */ + +static POINTARRAY * ptarray_chaikin(POINTARRAY *inpts, int preserve_endpoint, int isclosed) +{ + uint32_t p, i, n_out_points=0, p1_set=0, p2_set=0; + POINT4D p1, p2; + POINTARRAY *opts; + double *dlist; + double deltaval, quarter_delta, val1, val2; + uint32_t ndims = 2 + ptarray_has_z(inpts) + ptarray_has_m(inpts); + int new_npoints = inpts->npoints * 2; + opts = ptarray_construct_empty(FLAGS_GET_Z(inpts->flags), FLAGS_GET_M(inpts->flags), new_npoints); + + dlist = (double*)(opts->serialized_pointlist); + + p1 = getPoint4d(inpts, 0); + /*if first point*/ + if(preserve_endpoint) + { + ptarray_append_point(opts, &p1, LW_TRUE); + n_out_points++; + } + + for (p=1;pnpoints;p++) + { + memcpy(&p2, &p1, sizeof(POINT4D)); + p1 = getPoint4d(inpts, p); + if(p>0) + { + p1_set = p2_set = 0; + for (i=0;i 1) + { + dlist[n_out_points * ndims + i] = val2 + quarter_delta; + p1_set = 1; + } + if(!preserve_endpoint || p < inpts->npoints - 1) + { + dlist[(n_out_points + p1_set) * ndims + i] = val1 - quarter_delta; + p2_set = 1; + } + } + n_out_points+=(p1_set + p2_set); + } + } + + /*if last point*/ + if(preserve_endpoint) + { + opts->npoints = n_out_points; + ptarray_append_point(opts, &p1, LW_TRUE); + n_out_points++; + } + + if(isclosed && !preserve_endpoint) + { + opts->npoints = n_out_points; + POINT4D first_point = getPoint4d(opts, 0); + ptarray_append_point(opts, &first_point, LW_TRUE); + n_out_points++; + } + opts->npoints = n_out_points; + + return opts; + +} + +static LWLINE* lwline_chaikin(const LWLINE *iline, int n_iterations) +{ + POINTARRAY *pa, *pa_new; + int j; + LWLINE *oline; + + if( lwline_is_empty(iline)) + return lwline_clone(iline); + + pa = iline->points; + for (j=0;j0) + ptarray_free(pa); + pa=pa_new; + } + oline = lwline_construct(iline->srid, NULL, pa); + + oline->type = iline->type; + return oline; + +} + + +static LWPOLY* lwpoly_chaikin(const LWPOLY *ipoly, int n_iterations, int preserve_endpoint) +{ + uint32_t i; + int j; + POINTARRAY *pa, *pa_new; + LWPOLY *opoly = lwpoly_construct_empty(ipoly->srid, FLAGS_GET_Z(ipoly->flags), FLAGS_GET_M(ipoly->flags)); + + if( lwpoly_is_empty(ipoly) ) + return opoly; + for (i = 0; i < ipoly->nrings; i++) + { + pa = ipoly->rings[i]; + for(j=0;j0) + ptarray_free(pa); + pa=pa_new; + } + if(pa->npoints>=4) + { + if( lwpoly_add_ring(opoly,pa ) == LW_FAILURE ) + return NULL; + } + } + + opoly->type = ipoly->type; + + if( lwpoly_is_empty(opoly) ) + return NULL; + + return opoly; + +} + + +static LWCOLLECTION* lwcollection_chaikin(const LWCOLLECTION *igeom, int n_iterations, int preserve_endpoint) +{ + LWDEBUG(2, "Entered lwcollection_set_effective_area"); + uint32_t i; + + LWCOLLECTION *out = lwcollection_construct_empty(igeom->type, igeom->srid, FLAGS_GET_Z(igeom->flags), FLAGS_GET_M(igeom->flags)); + + if( lwcollection_is_empty(igeom) ) + return out; /* should we return NULL instead ? */ + + for( i = 0; i < igeom->ngeoms; i++ ) + { + LWGEOM *ngeom = lwgeom_chaikin(igeom->geoms[i],n_iterations,preserve_endpoint); + if ( ngeom ) out = lwcollection_add_lwgeom(out, ngeom); + } + + return out; +} + + +LWGEOM* lwgeom_chaikin(const LWGEOM *igeom, int n_iterations, int preserve_endpoint) +{ + LWDEBUG(2, "Entered lwgeom_set_effective_area"); + switch (igeom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return lwgeom_clone(igeom); + case LINETYPE: + return (LWGEOM*)lwline_chaikin((LWLINE*)igeom, n_iterations); + case POLYGONTYPE: + return (LWGEOM*)lwpoly_chaikin((LWPOLY*)igeom,n_iterations,preserve_endpoint); + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return (LWGEOM*)lwcollection_chaikin((LWCOLLECTION *)igeom,n_iterations,preserve_endpoint); + default: + lwerror("lwgeom_chaikin: unsupported geometry type: %s",lwtype_name(igeom->type)); + } + return NULL; +} + + +/* Forward declaration needed by lwcollection_catmull_rom */ +LWGEOM *lwgeom_catmull_rom(const LWGEOM *igeom, int n_segments); + +/* ======================================================================== + * Catmull-Rom spline smoothing + * + * An interpolating spline: the output curve passes through every original + * vertex. Between each pair of original vertices, (n_segments - 1) new + * intermediate points are inserted. + * + * Standard uniform Catmull-Rom formula for t in [0,1] between P1 and P2: + * q(t) = 0.5 * [ 2*P1 + * + (-P0 + P2)*t + * + (2*P0 - 5*P1 + 4*P2 - P3)*t^2 + * + (-P0 + 3*P1 - 3*P2 + P3)*t^3 ] + * + * Applied per dimension (x, y, and optionally z and m). + * + * Boundary handling for open lines: phantom endpoints are constructed by + * reflecting the adjacent point through the endpoint. + * P_{-1} = 2*P0 - P1 + * P_N = 2*P_{N-1} - P_{N-2} + * + * Boundary handling for closed rings: wrap-around indexing. + * ======================================================================== */ + +/* + * Core Catmull-Rom algorithm on a POINTARRAY. + * + * isclosed: LW_TRUE for polygon rings (first == last point). + * n_segments: number of sub-segments per original span (>= 2). + * + * Returns a newly allocated POINTARRAY; caller must free. + */ +static POINTARRAY * +ptarray_catmull_rom(const POINTARRAY *inpts, int n_segments, int isclosed) +{ + uint32_t ndims = 2 + ptarray_has_z(inpts) + ptarray_has_m(inpts); + uint32_t npoints = inpts->npoints; + + /* For closed rings the last point duplicates the first; work with + * N = npoints-1 unique points so we don't double-count. */ + uint32_t N = isclosed ? npoints - 1 : npoints; + + /* Too few unique points: return a clone unchanged */ + if (N < 4) + return ptarray_clone(inpts); + + /* Number of spans between consecutive original vertices */ + uint32_t nspans = N - (isclosed ? 0 : 1); + + /* Output size: nspans * n_segments + 1 (we always close with the last orig point) */ + uint32_t out_cap = nspans * (uint32_t)n_segments + 1; + POINTARRAY *opts = ptarray_construct_empty( + FLAGS_GET_Z(inpts->flags), FLAGS_GET_M(inpts->flags), out_cap); + + /* Helper to get a point by index with phantom-endpoint / wrap-around logic. + * idx may be -1 or >= N; we handle accordingly. */ +#define GET_COORD(idx, dim) \ + (((int32_t)(idx) < 0) \ + ? (isclosed \ + ? ((double *)getPoint_internal(inpts, \ + (uint32_t)((int32_t)N + (int32_t)(idx))))[dim] \ + : 2.0 * ((double *)getPoint_internal(inpts, 0))[dim] \ + - ((double *)getPoint_internal(inpts, 1))[dim]) \ + : ((uint32_t)(idx) >= N) \ + ? (isclosed \ + ? ((double *)getPoint_internal(inpts, \ + (uint32_t)(idx) % N))[dim] \ + : 2.0 * ((double *)getPoint_internal(inpts, N-1))[dim]\ + - ((double *)getPoint_internal(inpts, N-2))[dim]) \ + : ((double *)getPoint_internal(inpts, (uint32_t)(idx)))[dim]) + + int first_point = 1; /* track whether we should include t=0 */ + + for (uint32_t span = 0; span < nspans; span++) + { + int32_t i0 = (int32_t)span - 1; /* P0 (leading control) */ + int32_t i1 = (int32_t)span; /* P1 (start of span) */ + int32_t i2 = (int32_t)span + 1; /* P2 (end of span) */ + int32_t i3 = (int32_t)span + 2; /* P3 (trailing control)*/ + + int k_start = first_point ? 0 : 1; + first_point = 0; + + /* Run k from k_start to n_segments inclusive. + * At k=0: t=0.0 (original vertex, only for first span) + * At k=n_segments: t=1.0 (next original vertex, closes the span) */ + for (int k = k_start; k <= n_segments; k++) + { + double t = k / (double)n_segments; + double t2 = t * t; + double t3 = t2 * t; + POINT4D pt; + + for (uint32_t d = 0; d < ndims; d++) + { + double c0 = GET_COORD(i0, d); + double c1 = GET_COORD(i1, d); + double c2 = GET_COORD(i2, d); + double c3 = GET_COORD(i3, d); + + ((double *)&pt)[d] = 0.5 * ( + 2.0 * c1 + + (-c0 + c2) * t + + (2.0*c0 - 5.0*c1 + 4.0*c2 - c3) * t2 + + (-c0 + 3.0*c1 - 3.0*c2 + c3) * t3 + ); + } + /* Zero out unused dimensions so POINT4D is clean */ + if (ndims < 3) pt.z = 0.0; + if (ndims < 4) pt.m = 0.0; + + ptarray_append_point(opts, &pt, LW_TRUE); + } + } + /* At k=n_segments (t=1.0), the Catmull-Rom formula evaluates to c2 (the span + * endpoint), so all original vertices are naturally included in the output, + * and closed rings are automatically closed by the last span's t=1 giving P0. */ + +#undef GET_COORD + + return opts; +} + + +static LWLINE * +lwline_catmull_rom(const LWLINE *iline, int n_segments) +{ + if (lwline_is_empty(iline)) + return lwline_clone(iline); + + if (iline->points->npoints < 4) + return lwline_clone(iline); + + POINTARRAY *pa = ptarray_catmull_rom(iline->points, n_segments, LW_FALSE); + LWLINE *oline = lwline_construct(iline->srid, NULL, pa); + oline->type = iline->type; + return oline; +} + + +static LWPOLY * +lwpoly_catmull_rom(const LWPOLY *ipoly, int n_segments) +{ + LWPOLY *opoly = lwpoly_construct_empty( + ipoly->srid, FLAGS_GET_Z(ipoly->flags), FLAGS_GET_M(ipoly->flags)); + + if (lwpoly_is_empty(ipoly)) + return opoly; + + for (uint32_t i = 0; i < ipoly->nrings; i++) + { + POINTARRAY *ring = ipoly->rings[i]; + POINTARRAY *pa_out; + + /* npoints-1 unique points; need at least 4 to smooth */ + if (ring->npoints - 1 < 4) + pa_out = ptarray_clone(ring); + else + pa_out = ptarray_catmull_rom(ring, n_segments, LW_TRUE); + + if (pa_out->npoints >= 4) + { + if (lwpoly_add_ring(opoly, pa_out) == LW_FAILURE) + return NULL; + } + else + { + ptarray_free(pa_out); + } + } + + opoly->type = ipoly->type; + + if (lwpoly_is_empty(opoly)) + return NULL; + + return opoly; +} + + +static LWCOLLECTION * +lwcollection_catmull_rom(const LWCOLLECTION *igeom, int n_segments) +{ + LWCOLLECTION *out = lwcollection_construct_empty( + igeom->type, igeom->srid, + FLAGS_GET_Z(igeom->flags), FLAGS_GET_M(igeom->flags)); + + if (lwcollection_is_empty(igeom)) + return out; + + for (uint32_t i = 0; i < igeom->ngeoms; i++) + { + LWGEOM *ngeom = lwgeom_catmull_rom(igeom->geoms[i], n_segments); + if (ngeom) + out = lwcollection_add_lwgeom(out, ngeom); + } + + return out; +} + + +LWGEOM * +lwgeom_catmull_rom(const LWGEOM *igeom, int n_segments) +{ + switch (igeom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return lwgeom_clone(igeom); + case LINETYPE: + return (LWGEOM *)lwline_catmull_rom((LWLINE *)igeom, n_segments); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_catmull_rom((LWPOLY *)igeom, n_segments); + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_catmull_rom((LWCOLLECTION *)igeom, n_segments); + default: + lwerror("lwgeom_catmull_rom: unsupported geometry type: %s", lwtype_name(igeom->type)); + } + return NULL; +} diff --git a/postgis/lwgeom_functions_analytic.c b/postgis/lwgeom_functions_analytic.c index 13e31cf01..6d6ca9e55 100644 --- a/postgis/lwgeom_functions_analytic.c +++ b/postgis/lwgeom_functions_analytic.c @@ -163,6 +163,38 @@ Datum LWGEOM_ChaikinSmoothing(PG_FUNCTION_ARGS) } +PG_FUNCTION_INFO_V1(LWGEOM_CatmullRomSmoothing); +Datum LWGEOM_CatmullRomSmoothing(PG_FUNCTION_ARGS) +{ + GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0); + GSERIALIZED *result; + int type = gserialized_get_type(geom); + LWGEOM *in, *out; + int n_segments = 5; + + if (type == POINTTYPE || type == MULTIPOINTTYPE) + PG_RETURN_POINTER(geom); + + if ((PG_NARGS() > 1) && (!PG_ARGISNULL(1))) + n_segments = PG_GETARG_INT32(1); + + if (n_segments < 2) + elog(ERROR, "nSegments must be >= 2: %s", __func__); + + in = lwgeom_from_gserialized(geom); + out = lwgeom_catmull_rom(in, n_segments); + if (!out) PG_RETURN_NULL(); + + /* COMPUTE_BBOX TAINTING */ + if (in->bbox) lwgeom_add_bbox(out); + + result = geometry_serialize(out); + lwgeom_free(out); + PG_FREE_IF_COPY(geom, 0); + PG_RETURN_POINTER(result); +} + + /*********************************************************************** * --strk at kbt.io; ***********************************************************************/ diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in index 649ed3fc5..73d41a465 100644 --- a/postgis/postgis.sql.in +++ b/postgis/postgis.sql.in @@ -3600,6 +3600,13 @@ CREATE OR REPLACE FUNCTION ST_ChaikinSmoothing(geometry, integer default 1, bool LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE _COST_MEDIUM; +-- Availability: 3.6.0 +CREATE OR REPLACE FUNCTION ST_CatmullRomSmoothing(geometry, integer default 5) + RETURNS geometry + AS 'MODULE_PATHNAME', 'LWGEOM_CatmullRomSmoothing' + LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE + _COST_MEDIUM; + -- ST_SnapToGrid(input, xoff, yoff, xsize, ysize) -- Availability: 1.2.2 CREATE OR REPLACE FUNCTION ST_SnapToGrid(geometry, float8, float8, float8, float8) diff --git a/regress/core/catmullrom.sql b/regress/core/catmullrom.sql new file mode 100644 index 000000000..fe4d9d533 --- /dev/null +++ b/regress/core/catmullrom.sql @@ -0,0 +1,39 @@ +-- ST_CatmullRomSmoothing regression tests + +-- Basic 4-point collinear line, default nSegments=5 +-- Scale by 5 so interpolated values are integers +SELECT '1', ST_AsText(ST_CatmullRomSmoothing('LINESTRING(0 0, 5 0, 10 0, 15 0)')); + +-- Explicit nSegments=2, outputs at half-integer positions +SELECT '2', ST_AsText(ST_CatmullRomSmoothing('LINESTRING(0 0, 1 0, 2 0, 3 0)', 2)); + +-- Fewer than 4 points: return original unchanged +SELECT '3', ST_AsText(ST_CatmullRomSmoothing('LINESTRING(0 0, 1 1, 2 0)')); + +-- 2-point line: return original unchanged +SELECT '4', ST_AsText(ST_CatmullRomSmoothing('LINESTRING(0 0, 10 10)')); + +-- Point: pass through unchanged +SELECT '5', ST_AsText(ST_CatmullRomSmoothing('POINT(0 0)')); + +-- Empty line: return empty +SELECT '6', ST_AsText(ST_CatmullRomSmoothing('LINESTRING EMPTY')); + +-- Polygon with square ring, nSegments=2 +SELECT '7', ST_AsText(ST_CatmullRomSmoothing('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))', 2)); + +-- GeometryCollection: point passthrough + line smoothing +SELECT '8', ST_AsText(ST_CatmullRomSmoothing( + 'GEOMETRYCOLLECTION(POINT(1 1), LINESTRING(0 0, 1 0, 2 0, 3 0))', 2)); + +-- Z dimension is preserved and interpolated +SELECT '9', ST_AsText(ST_CatmullRomSmoothing('LINESTRING Z (0 0 0, 1 0 10, 2 0 20, 3 0 30)', 2)); + +-- The geometry bbox is updated +WITH geom AS ( + SELECT ST_CatmullRomSmoothing('LINESTRING(0 0, 1 0, 2 0, 3 0)', 2) AS g +) +SELECT '10', postgis_getbbox(g) AS box FROM geom; + +-- Triangle polygon ring has npoints-1=3 unique pts (<4), return original +SELECT '11', ST_AsText(ST_CatmullRomSmoothing('POLYGON((0 0, 4 0, 2 4, 0 0))', 5)); diff --git a/regress/core/catmullrom_expected b/regress/core/catmullrom_expected new file mode 100644 index 000000000..ef9dcc837 --- /dev/null +++ b/regress/core/catmullrom_expected @@ -0,0 +1,11 @@ +1|LINESTRING(0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 0) +2|LINESTRING(0 0,0.5 0,1 0,1.5 0,2 0,2.5 0,3 0) +3|LINESTRING(0 0,1 1,2 0) +4|LINESTRING(0 0,10 10) +5|POINT(0 0) +6|LINESTRING EMPTY +7|POLYGON((0 0,2 -0.5,4 0,4.5 2,4 4,2 4.5,0 4,-0.5 2,0 0)) +8|GEOMETRYCOLLECTION(POINT(1 1),LINESTRING(0 0,0.5 0,1 0,1.5 0,2 0,2.5 0,3 0)) +9|LINESTRING Z (0 0 0,0.5 0 5,1 0 10,1.5 0 15,2 0 20,2.5 0 25,3 0 30) +10|BOX(0 0,3 0) +11|POLYGON((0 0,4 0,2 4,0 0)) diff --git a/regress/core/tests.mk.in b/regress/core/tests.mk.in index 5e334ee5e..80d136cbc 100644 --- a/regress/core/tests.mk.in +++ b/regress/core/tests.mk.in @@ -33,6 +33,7 @@ TESTS += \ $(top_srcdir)/regress/core/bestsrid \ $(top_srcdir)/regress/core/binary \ $(top_srcdir)/regress/core/boundary \ + $(top_srcdir)/regress/core/catmullrom \ $(top_srcdir)/regress/core/chaikin \ $(top_srcdir)/regress/core/clean \ $(top_srcdir)/regress/core/clipbybox2d \ ----------------------------------------------------------------------- Summary of changes: NEWS | 1 + doc/html/images/Makefile.in | 4 + doc/html/images/wkt/st_catmullromsmoothing01.wkt | 2 + doc/html/images/wkt/st_catmullromsmoothing02.wkt | 2 + doc/html/images/wkt/st_catmullromsmoothing03.wkt | 2 + doc/html/images/wkt/st_catmullromsmoothing04.wkt | 2 + doc/reference_processing.xml | 134 +++++++ liblwgeom/Makefile.in | 2 +- liblwgeom/cunit/Makefile.in | 1 + liblwgeom/cunit/cu_catmullrom.c | 201 +++++++++++ liblwgeom/cunit/cu_tester.c | 2 + liblwgeom/liblwgeom.h.in | 1 + liblwgeom/lwchaikins.c | 202 ----------- liblwgeom/lwsmoothing.c | 432 +++++++++++++++++++++++ postgis/lwgeom_functions_analytic.c | 32 ++ postgis/postgis.sql.in | 7 + regress/core/catmullrom.sql | 39 ++ regress/core/catmullrom_expected | 11 + regress/core/tests.mk.in | 1 + 19 files changed, 875 insertions(+), 203 deletions(-) create mode 100644 doc/html/images/wkt/st_catmullromsmoothing01.wkt create mode 100644 doc/html/images/wkt/st_catmullromsmoothing02.wkt create mode 100644 doc/html/images/wkt/st_catmullromsmoothing03.wkt create mode 100644 doc/html/images/wkt/st_catmullromsmoothing04.wkt create mode 100644 liblwgeom/cunit/cu_catmullrom.c delete mode 100644 liblwgeom/lwchaikins.c create mode 100644 liblwgeom/lwsmoothing.c create mode 100644 regress/core/catmullrom.sql create mode 100644 regress/core/catmullrom_expected hooks/post-receive -- PostGIS From trac at osgeo.org Fri May 1 10:42:29 2026 From: trac at osgeo.org (PostGIS) Date: Fri, 01 May 2026 17:42:29 -0000 Subject: [PostGIS] #4398: A function to smooth the linestring without losing vertices In-Reply-To: <048.3f1bee0fa669d57133ed12d0927f9903@osgeo.org> References: <048.3f1bee0fa669d57133ed12d0927f9903@osgeo.org> Message-ID: <063.c105855127cf9ea756758d451613392a@osgeo.org> #4398: A function to smooth the linestring without losing vertices --------------------------+----------------------------- Reporter: komzpa | Owner: komzpa Type: enhancement | Status: closed Priority: medium | Milestone: PostGIS Fund Me Component: postgis | Version: master Resolution: fixed | Keywords: --------------------------+----------------------------- Changes (by Paul Ramsey ): * resolution: => fixed * status: assigned => closed Comment: In [changeset:"0e794aa474544d0da986d4ba7a807103b97c129a/git" 0e794aa4/git]: {{{#!CommitTicketReference repository="git" revision="0e794aa474544d0da986d4ba7a807103b97c129a" ST_CatmullSmoothing(geom, nSegments) "smoothes" a LineString or LinearRing by adding vertices, and leaves all the input vertices unchanged. The nSegments parameter controls how many vertices are added between each input pair. Closes #4398 }}} -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Fri May 1 10:43:25 2026 From: trac at osgeo.org (PostGIS) Date: Fri, 01 May 2026 17:43:25 -0000 Subject: [PostGIS] #4398: A function to smooth the linestring without losing vertices In-Reply-To: <048.3f1bee0fa669d57133ed12d0927f9903@osgeo.org> References: <048.3f1bee0fa669d57133ed12d0927f9903@osgeo.org> Message-ID: <063.83b3ade33fd27f72561942b263701250@osgeo.org> #4398: A function to smooth the linestring without losing vertices --------------------------+--------------------------- Reporter: komzpa | Owner: komzpa Type: enhancement | Status: closed Priority: medium | Milestone: PostGIS 3.7.0 Component: postgis | Version: master Resolution: fixed | Keywords: --------------------------+--------------------------- Changes (by pramsey): * milestone: PostGIS Fund Me => PostGIS 3.7.0 -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Tue May 5 08:08:38 2026 From: trac at osgeo.org (PostGIS) Date: Tue, 05 May 2026 15:08:38 -0000 Subject: [PostGIS] #5895: Distance Calculation Issue with `ST_DistanceSphere` In-Reply-To: <057.cc508822a8e6a9eaa70a2081ad54626f@osgeo.org> References: <057.cc508822a8e6a9eaa70a2081ad54626f@osgeo.org> Message-ID: <072.968367f2f6896961812704cc490aa7d6@osgeo.org> #5895: Distance Calculation Issue with `ST_DistanceSphere` ------------------------------+--------------------- Reporter: giovannicimolin | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: Component: postgis | Version: 3.4.x Resolution: | Keywords: ------------------------------+--------------------- Comment (by giovannicimolin): Replying to [comment:3 pramsey]: > Operating system and hardware architecture? Unfortunately all the above tests are working fine for me, MacOS/ARM Hi @pramsey, sorry for the delay - I realized I'm not getting any emails from this just now. We're working with X86 server running Alpine 3.22 images on production. --- Even on Mac I'm seeing this issue, but I'm not running natively. {{{ Linux 0cc9a2d9a366 6.8.0-64-generic #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:40 UTC 2025 x86_64 GNU/Linux }}} {{{ root at 0cc9a2d9a366:/# psql --version psql (PostgreSQL) 17.5 (Debian 17.5-1.pgdg110+1) }}} The test case in the original issue still yields 0 as distance. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 07:46:05 2026 From: trac at osgeo.org (PostGIS) Date: Wed, 06 May 2026 14:46:05 -0000 Subject: [PostGIS] #6076: ST_Length() on GEOMETRYCOLLECTION for geography type Message-ID: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> #6076: ST_Length() on GEOMETRYCOLLECTION for geography type -------------------------+--------------------------- Reporter: paleolimbot | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.6.3 Component: postgis | Version: 3.6.x Keywords: | -------------------------+--------------------------- For all eight people that have ever calculated the length of a geometry collection for the geography type (and all zero of them that *should* have): {{{ -- For geometry, ST_Length returns just the linestring length (1.0) SELECT ST_Length(ST_GeomFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))')); -- Result: 1.0 -- For geography, ST_Length is different SELECT ST_Length(ST_GeogFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))')); -- Result: 489367.83620021143 -- Expected: just the linestring length as geography (~110574 meters) SELECT ST_Distance(ST_GeogFromText('POINT (0 0)'), ST_GeogFromText('POINT (0 1)')); -- Result: 110574.3885578 }}} I tested using the postgis/postgis:18-3.6 docker image (with apologies if this has been fixed since). -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 08:00:20 2026 From: trac at osgeo.org (PostGIS) Date: Wed, 06 May 2026 15:00:20 -0000 Subject: [PostGIS] #6076: ST_Length() on GEOMETRYCOLLECTION for geography type In-Reply-To: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> References: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> Message-ID: <068.3666a9ca76223ef4f7f09231da3269f6@osgeo.org> #6076: ST_Length() on GEOMETRYCOLLECTION for geography type --------------------------+--------------------------- Reporter: paleolimbot | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.6.3 Component: postgis | Version: 3.6.x Resolution: | Keywords: --------------------------+--------------------------- Comment (by paleolimbot): Update: I believe ST_Perimeter has a similar issue (returns the same as ST_Length for a geometrycollection). -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 08:17:02 2026 From: trac at osgeo.org (PostGIS) Date: Wed, 06 May 2026 15:17:02 -0000 Subject: [PostGIS] #6010: ST_SetBandNoDataValue can't handle infinite no data In-Reply-To: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> References: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> Message-ID: <068.7eb479755e6698e06f8503ecb88e7d13@osgeo.org> #6010: ST_SetBandNoDataValue can't handle infinite no data -------------------------+------------------------------------------------- Reporter: | Owner: robe GISuser5432 | Type: defect | Status: new Priority: low | Milestone: PostGIS 3.2.10 Component: raster | Version: 3.6.x Resolution: | Keywords: ST_Polygon (raster) ; Raster; | PostGIS_Raster; -------------------------+------------------------------------------------- Changes (by GISuser5432): * Attachment "test_st_polygonit_dump.sql" added. The 32BF NoData that doe snot work neither -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 08:53:15 2026 From: trac at osgeo.org (PostGIS) Date: Wed, 06 May 2026 15:53:15 -0000 Subject: [PostGIS] #6010: ST_SetBandNoDataValue can't handle infinite no data In-Reply-To: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> References: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> Message-ID: <068.51c7785ec8807d67ac1d7d5da82a4490@osgeo.org> #6010: ST_SetBandNoDataValue can't handle infinite no data -------------------------+------------------------------------------------- Reporter: | Owner: robe GISuser5432 | Type: defect | Status: new Priority: low | Milestone: PostGIS 3.2.10 Component: raster | Version: 3.6.x Resolution: | Keywords: ST_Polygon (raster) ; Raster; | PostGIS_Raster; -------------------------+------------------------------------------------- Changes (by GISuser5432): * Attachment "result_of_query.png" added. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 10:20:04 2026 From: trac at osgeo.org (PostGIS) Date: Wed, 06 May 2026 17:20:04 -0000 Subject: [PostGIS] #6010: ST_SetBandNoDataValue can't handle infinite no data In-Reply-To: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> References: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> Message-ID: <068.cda0526912f398e22d11fc4024f1b0e4@osgeo.org> #6010: ST_SetBandNoDataValue can't handle infinite no data -------------------------+------------------------------------------------- Reporter: | Owner: robe GISuser5432 | Type: defect | Status: new Priority: low | Milestone: PostGIS 3.2.10 Component: raster | Version: 3.6.x Resolution: | Keywords: ST_Polygon (raster) ; Raster; | PostGIS_Raster; -------------------------+------------------------------------------------- Comment (by GISuser5432): The problem is not only **Infinity** in that example (which I think originally was 64BF maximum and not Infinity). The ST_Polygon(rast) returns the NoData areas (by mistake) when the NoData is 3.4028234663852886e which is the maximum of **32BF** pixel type. However, on an old server with PostGIS 2.4.3, ST_Polygon(rast) works well even with 32BF max. I attached test_st_polygonit_dump.sql that is the export of a raster in which the NoData is the maximum of 32BF pixel type. Previously, in older version PostGIS, I have in my old server, ST_Polygon(raster) was working well to find the part of the raster that has values. However, the new PostGIS that I have in docker is not able to extract the currect polygon when NoData is the max of 32BF. When I convert the NoData to smaller values, then, it works well!! the new version is: {{{ POSTGIS="3.6.0 4c1967d" [EXTENSION] PGSQL="180" GEOS="3.13.1-CAPI-1.19.2" PROJ="9.6.0 NETWORK_ENABLED=OFF URL_ENDPOINT=https://cdn.proj.org USER_WRITABLE_DIRECTORY=/var/lib/postgresql/.local/share/proj DATABASE_PATH=/usr/share/proj/proj.db" (compiled against PROJ 9.6.0) GDAL="GDAL 3.10.3, released 2025/04/01" LIBXML="2.9.14" LIBJSON="0.18" LIBPROTOBUF="1.5.1" WAGYU="0.5.0 (Internal)" RASTER }}} and also using the new version of PostGIS, the query you gave me returns only 3.4028234663852886e+38 not Infinity and pixel type is 32BF: {{{ SELECT (gv).val val1, ST_Area((gv).geom) AS nodata, ST_Area(rast::Geometry) FROM test_st_polygon, ST_DumpAsPolygons(rast) AS gv WHERE gv.val > 0; }}} [[Image(result_of_query.png?)]] Finally, I want to say that even in 32BF raster with a valid NoData value equal to the maximum of 32BF, the ST_Polygon is not working in newer versions while in very old versions it was working without any problem for years. The old version on which ST_Polygon is working well is: {{{ POSTGIS="2.4.3 r16312" PGSQL="100" GEOS="3.6.2-CAPI-1.10.2 4d2925d6" PROJ="Rel. 4.9.3, 15 August 2016" GDAL="GDAL 2.2.3, released 2017/11/20" LIBXML="2.9.4" LIBJSON="0.12.1" LIBPROTOBUF="1.2.1" RASTER }}} -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Wed May 6 21:37:59 2026 From: trac at osgeo.org (PostGIS) Date: Thu, 07 May 2026 04:37:59 -0000 Subject: [PostGIS] #6077: Some notes about enhancements to the postgis manual Message-ID: <049.1916b90b6296f16b98377341be01c6f6@osgeo.org> #6077: Some notes about enhancements to the postgis manual ---------------------------+--------------------------- Reporter: jidanni | Owner: robe Type: enhancement | Status: new Priority: low | Milestone: PostGIS 3.6.3 Component: documentation | Version: 3.6.x Keywords: | ---------------------------+--------------------------- Hi. Here are some notes I made about enhancements for the postgis manual. I had better give them to you before even I no longer know what I was thinking. geometry CG_Visibility( geometry polygon, geometry point); geometry CG_Visibility( geometry polygon, geometry pointA, geometry pointB); point and segment missing in example images. The geometry dimension is a property of geometry types. Point types have dimension 0, linear types have dimension 1, and polygonal types have dimension 2. Collections have the dimension of the maximum element dimension. Mention what has dimensions 3 and 4 etc.! Geometries are topologically closed, so they always contain their boundary. The boundary is a geometry of dimension one less than that of the geometry itself. Give examples. The OGC geometry model defines validity rules for each geometry type. These rules ensure that geometry values represents realistic situations (e.g. it is possible to specify a polygon with a hole lying outside ITS xxxthe shell, but this makes no sense geometrically and is thus invalid). PostGIS also allows storing and manipulating invalid geometry values. This allows detecting and fixing them if needed. See Section 4.4, ?Geometry Validation? A MultiPolygon is a collection of non-overlapping, non-adjacent Polygons. Polygons in the collection may touch only at a finite number of points. Explain adjacent vs. touching. Finite number? 17? Wait, way down below this is explained. Add a link to that please. (That works offline.) A PolyhedralSurface is a contiguous collection of patches or facets which share some edges. Each patch is a planar Polygon. If the Polygon coordinates have Z ordinates then the surface is 3-dimensional. ADD PIC. Else too abstract. VALUES ( ST_GeomFromText('POINT(-126.4 45.32)', 312), 'A Place'); Mention again that 312 is the SRID. The geometry data type is opaque, which means that all access is done via invoking functions on geometry values. Functions allow creating geometry objects, accessing or updating all internal fields, and compute new geometry values. COMPUTING. By default, all distance and area calculations are done on the spheroid. You should find that the results of calculations in local areas match up will with local planar results in good local projections. Over larger areas, the spheroidal calculations will be more accurate than any calculation done on a projected plane. OK, BUT REMIND what is the difference between the two. coord_dimension The coordinate dimension (2, 3 or 4) of the column. Mention why no 0 or 1. It may be required to identify all road segments that cross each other, not at a point, but in a line (perhaps to validate some business rule). BUSINESS RULE: like insurance policy no valid on two-way one lane roads? name | roads_km ----------------------------+------------------ SURREY | 1539.47553551242 Nanometers necessary here? SELEcT ST_AsText(geom) SELECT ST_AsText(ST_Letters('Yo'), 1); #The 1 overrides the font? Index is 1-based as for OGC specs since version 0.8.0. Backward indexing (negative index) is not in OGC[MISSING PERIOD] Previous versions implemented this as 0-based instead. SELECT ST_AsText(geom) as line, ST_AsText(ST_Reverse(geom)) As reverseline FROM (SELECT ST_MakeLine(ST_Point(1,2), ST_Point(1,10)) As geom) as foo; FOO NEEDED? Find broadcasting towers that receiver with limited range can receive. ^a EPSG:26986 is state plane Massachusetts meters[Period] float ST_Angle( geometry point1, geometry point2, geometry point3, geometry point4); float ST_Angle( geometry point1, geometry point2, geometry point3[, geometry point4]); ..... ST_LengthSpheroid( geometry a_geometry, spheroid a_spheroid); Description Calculates the length or perimeter of a geometry on an ellipsoid. This is useful if the coordinates of the geometry are in longitude/latitude and a length is desired without reprojection. The spheroid is specified by a text value as follows: ..... s/ellipsoid/sphorid / or say why... float ST_MinimumClearance( geometry g); need pictures to explain better. Examples: 2.5Dish Note this is not a true intersection, compare to the same example using ST_3DIntersection. [No image provided: so Say if we are to imagine a 2.5 meter satellite dish or what.] ST_Node( geo ADD IMAGE. Thanks. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Thu May 7 03:06:11 2026 From: git at osgeo.org (git at osgeo.org) Date: Thu, 7 May 2026 03:06:11 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-475-g05682d312 Message-ID: <20260507100612.0D8241810C2@trac.osgeo.org> 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 05682d312a8b67ba7b90dfd5f89993ff082c054b (commit) from 0e794aa474544d0da986d4ba7a807103b97c129a (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 05682d312a8b67ba7b90dfd5f89993ff082c054b Author: Jean Felder Date: Sun May 3 19:02:44 2026 +0200 sfcgal: Adapt CG_ExtrudeStraightSkeleton test to triangulation improvements The geometry returned by CG_ExtrudeStraightSkeleton, when extrude and roof parameters are set was triangulated. Since SFCGAL 2.3, the triangles can be merged when the underlying surface is coplanar. This gives a smoother result and a polyhedralsurface which has less patches. Adapt the unit test to work with all SFCGAL versions by comparing the number of generated patches. See: https://gitlab.com/sfcgal/SFCGAL/-/merge_requests/730 diff --git a/doc/reference_sfcgal.xml b/doc/reference_sfcgal.xml index e3abfbac0..b27b72804 100644 --- a/doc/reference_sfcgal.xml +++ b/doc/reference_sfcgal.xml @@ -2103,7 +2103,7 @@ It always gives a 2D result even when used on a 3D geometry. Examples SELECT ST_AsText(CG_ExtrudeStraightSkeleton('POLYGON (( 0 0, 5 0, 5 5, 4 5, 4 4, 0 4, 0 0 ), (1 1, 1 2,2 2, 2 1, 1 1))', 3.0, 2.0)); - POLYHEDRALSURFACE Z (((0 0 0,0 4 0,4 4 0,4 5 0,5 5 0,5 0 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),((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 5 2,4 5 0,4 4 0)),((4 5 0,4 5 2,5 5 2,5 5 0,4 5 0)),((5 5 0,5 5 2,5 0 2,5 0 0,5 5 0)),((5 0 0,5 0 2,0 0 2,0 0 0,5 0 0)),((1 1 0,1 1 2,2 1 2,2 1 0,1 1 0)),((2 1 0,2 1 2,2 2 2,2 2 0,2 1 0)),((2 2 0,2 2 2,1 2 2,1 2 0,2 2 0)),((1 2 0,1 2 2,1 1 2,1 1 0,1 2 0)),((0.5 2.5 2.5,0 0 2,0.5 0.5 2.5,0.5 2.5 2.5)),((1 3 3,0 4 2,0.5 2.5 2.5,1 3 3)),((0.5 2.5 2.5,0 4 2,0 0 2,0.5 2.5 2.5)),((2.5 0.5 2.5,5 0 2,3.5 1.5 3.5,2.5 0.5 2.5)),((0 0 2,5 0 2,2.5 0.5 2.5,0 0 2)),((0.5 0.5 2.5,0 0 2,2.5 0.5 2.5,0.5 0.5 2.5)),((4.5 3.5 2.5,5 5 2,4.5 4.5 2.5,4.5 3.5 2.5)),((3.5 2.5 3.5,3.5 1.5 3.5,4.5 3.5 2.5,3.5 2.5 3.5)),((4.5 3.5 2.5,5 0 2,5 5 2,4.5 3.5 2.5)),((3.5 1.5 3.5,5 0 2,4.5 3.5 2.5,3.5 1.5 3.5)),((5 5 2,4 5 2,4.5 4.5 2.5,5 5 2)),((4.5 4.5 2.5,4 4 2,4.5 3.5 2.5,4.5 4.5 2.5)),((4.5 4.5 2.5,4 5 2,4 4 2,4.5 4.5 2.5)),((3 3 3,0 4 2,1 3 3,3 3 3)),((3.5 2.5 3.5,4.5 3.5 2.5,3 3 3,3.5 2.5 3.5)),((3 3 3,4 4 2,0 4 2,3 3 3)),((4.5 3.5 2.5,4 4 2,3 3 3,4.5 3.5 2.5)),((2 1 2,1 1 2,0.5 0.5 2.5,2 1 2)),((2.5 0.5 2.5,2 1 2,0.5 0.5 2.5,2.5 0.5 2.5)),((1 1 2,1 2 2,0.5 2.5 2.5,1 1 2)),((0.5 0.5 2.5,1 1 2,0.5 2.5 2.5,0.5 0.5 2.5)),((1 3 3,2 2 2,3 3 3,1 3 3)),((0.5 2.5 2.5,1 2 2,1 3 3,0.5 2.5 2.5)),((1 3 3,1 2 2,2 2 2,1 3 3)),((2 2 2,2 1 2,2.5 0.5 2.5,2 2 2)),((3.5 2.5 3.5,3 3 3,3.5 1.5 3.5,3.5 2.5 3.5)),((3.5 1.5 3.5,2 2 2,2.5 0.5 2.5,3.5 1.5 3.5)),((3 3 3,2 2 2,3.5 1.5 3.5,3 3 3))) + POLYHEDRALSURFACE Z (((0 0 0,0 4 0,4 4 0,4 5 0,5 5 0,5 0 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),((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 5 2,4 5 0,4 4 0)),((4 5 0,4 5 2,5 5 2,5 5 0,4 5 0)),((5 5 0,5 5 2,5 0 2,5 0 0,5 5 0)),((5 0 0,5 0 2,0 0 2,0 0 0,5 0 0)),((1 1 0,1 1 2,2 1 2,2 1 0,1 1 0)),((2 1 0,2 1 2,2 2 2,2 2 0,2 1 0)),((2 2 0,2 2 2,1 2 2,1 2 0,2 2 0)),((1 2 0,1 2 2,1 1 2,1 1 0,1 2 0)),((0 4 2,0 0 2,0.5 0.5 2.5,0.5 2.5 2.5,1 3 3,0 4 2)),((0 0 2,5 0 2,3.5 1.5 3.5,2.5 0.5 2.5,0.5 0.5 2.5,0 0 2)),((5 0 2,5 5 2,4.5 4.5 2.5,4.5 3.5 2.5,3.5 2.5 3.5,3.5 1.5 3.5,5 0 2)),((5 5 2,4 5 2,4.5 4.5 2.5,5 5 2)),((4 5 2,4 4 2,4.5 3.5 2.5,4.5 4.5 2.5,4 5 2)),((4 4 2,0 4 2,1 3 3,3 3 3,3.5 2.5 3.5,4.5 3.5 2.5,4 4 2)),((2 1 2,1 1 2,0.5 0.5 2.5,2.5 0.5 2.5,2 1 2)),((1 1 2,1 2 2,0.5 2.5 2.5,0.5 0.5 2.5,1 1 2)),((1 2 2,2 2 2,3 3 3,1 3 3,0.5 2.5 2.5,1 2 2)),((2 2 2,2 1 2,2.5 0.5 2.5,3.5 1.5 3.5,3.5 2.5 3.5,3 3 3,2 2 2))) diff --git a/sfcgal/regress/extrudestraigthskeleton.sql b/sfcgal/regress/extrudestraigthskeleton.sql index 11d12dbf4..fc273cd58 100644 --- a/sfcgal/regress/extrudestraigthskeleton.sql +++ b/sfcgal/regress/extrudestraigthskeleton.sql @@ -1,43 +1,72 @@ -SELECT 'Extrude roof', ST_AsText(CG_ExtrudeStraightSkeleton('POLYGON (( 0 0, 5 0, 5 5, 4 5, 4 4, 0 4, 0 0 ), (1 1, 1 2,2 2, 2 1, 1 1))', 2.0)); -WITH - version_info AS ( - -- The result depends on the SFCGAL Version. - -- Prior to version 2.2, it contained extra patches. - SELECT - string_to_array(postgis_sfcgal_version (), '.')::INT[] AS sfcgal_version_array - ), - skeleton AS ( - SELECT - ST_NumPatches ( - CG_ExtrudeStraightSkeleton ( - 'POLYGON (( 0 0, 5 0, 5 5, 4 5, 4 4, 0 4, 0 0 ), (1 1, 1 2,2 2, 2 1, 1 1))', - 3.0, - 2.0 - ) - ) AS num_patches - ), - check_patches AS ( - SELECT - sfcgal_version_array, - num_patches, - ( - ( - sfcgal_version_array <= ARRAY[2, 1, 0] - AND num_patches = 49 - ) - OR ( - sfcgal_version_array > ARRAY[2, 1, 0] - AND num_patches = 39 - ) - ) AS test_result - FROM - skeleton, - version_info - ) -SELECT - 'Extrude building and roof', - test_result -FROM - check_patches; +WITH version_info AS ( + SELECT string_to_array(postgis_sfcgal_version(), '.')::int[] AS sfcgal_version_array +), + +roof AS ( + SELECT + ST_NumPatches( + CG_ExtrudeStraightSkeleton( + 'POLYGON ((0 0, 5 0, 5 5, 4 5, 4 4, 0 4, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))', + 2.0 + ) + ) AS num_patches, + sfcgal_version_array + FROM version_info +), + +building_and_roof AS ( + SELECT + ST_NumPatches( + CG_ExtrudeStraightSkeleton( + 'POLYGON ((0 0, 5 0, 5 5, 4 5, 4 4, 0 4, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))', + 3.0, + 2.0 + ) + ) AS num_patches, + sfcgal_version_array + FROM version_info +) + +SELECT 'Extrude roof', + -- The result depends on the SFCGAL version. + -- Since version 2.3, the result is simplified by merging + -- triangles before storing the result, resulting in fewer + -- patches. + ( + ( + sfcgal_version_array < ARRAY[2,3,0] + AND num_patches = 38 + ) + OR ( + sfcgal_version_array >= ARRAY[2,3,0] + AND num_patches = 11 + ) + ) +FROM roof + +UNION ALL + +SELECT 'Extrude building and roof', + -- The result depends on the SFCGAL version. + -- Prior to version 2.2, it contained extra patches. + -- Since version 2.3, the result is simplified by merging + -- triangles before storing the result, resulting in fewer + -- patches. + ( + ( + sfcgal_version_array < ARRAY[2,2,0] + AND num_patches = 49 + ) + OR ( + sfcgal_version_array >= ARRAY[2,2,0] + AND sfcgal_version_array < ARRAY[2,3,0] + AND num_patches = 39 + ) + OR ( + sfcgal_version_array >= ARRAY[2,3,0] + AND num_patches = 21 + ) + ) +FROM building_and_roof; SELECT 'Empty building and roof', ST_AsText(CG_ExtrudeStraightSkeleton(ST_GeomFromText('POLYGON EMPTY',4326), 20.1, 20.1)); SELECT 'Empty roof', ST_AsText(CG_ExtrudeStraightSkeleton(ST_GeomFromText('POLYGON EMPTY',4326), 20.1)); diff --git a/sfcgal/regress/extrudestraigthskeleton_expected b/sfcgal/regress/extrudestraigthskeleton_expected index 8a8ac4ddc..b64e6d458 100644 --- a/sfcgal/regress/extrudestraigthskeleton_expected +++ b/sfcgal/regress/extrudestraigthskeleton_expected @@ -1,4 +1,4 @@ -Extrude roof|POLYHEDRALSURFACE Z (((4 5 0,5 5 0,4 4 0,4 5 0)),((2 1 0,5 0 0,0 0 0,2 1 0)),((5 5 0,5 0 0,4 4 0,5 5 0)),((2 1 0,0 0 0,1 1 0,2 1 0)),((1 2 0,1 1 0,0 0 0,1 2 0)),((0 4 0,2 2 0,1 2 0,0 4 0)),((0 4 0,1 2 0,0 0 0,0 4 0)),((4 4 0,5 0 0,2 2 0,4 4 0)),((4 4 0,2 2 0,0 4 0,4 4 0)),((2 2 0,5 0 0,2 1 0,2 2 0)),((0.5 2.5 0.5,0 0 0,0.5 0.5 0.5,0.5 2.5 0.5)),((1 3 1,0 4 0,0.5 2.5 0.5,1 3 1)),((0.5 2.5 0.5,0 4 0,0 0 0,0.5 2.5 0.5)),((2.5 0.5 0.5,5 0 0,3.5 1.5 1.5,2.5 0.5 0.5)),((0 0 0,5 0 0,2.5 0.5 0.5,0 0 0)),((0.5 0.5 0.5,0 0 0,2.5 0.5 0.5,0.5 0.5 0.5)),((4.5 3.5 0.5,5 5 0,4.5 4.5 0.5,4.5 3.5 0.5)),((3.5 2.5 1.5,3.5 1.5 1.5,4.5 3.5 0.5,3.5 2.5 1.5)),((4.5 3.5 0.5,5 0 0,5 5 0,4.5 3.5 0.5)),((3.5 1.5 1.5,5 0 0,4.5 3.5 0.5,3.5 1.5 1.5)),((5 5 0,4 5 0,4.5 4.5 0.5,5 5 0)),((4.5 4.5 0.5,4 4 0,4.5 3.5 0.5,4.5 4.5 0.5)),((4.5 4.5 0.5,4 5 0,4 4 0,4.5 4.5 0.5)),((3 3 1,0 4 0,1 3 1,3 3 1)),((3.5 2.5 1.5,4.5 3.5 0.5,3 3 1,3.5 2.5 1.5)),((3 3 1,4 4 0,0 4 0,3 3 1)),((4.5 3.5 0.5,4 4 0,3 3 1,4.5 3 .5 0.5)),((2 1 0,1 1 0,0.5 0.5 0.5,2 1 0)),((2.5 0.5 0.5,2 1 0,0.5 0.5 0.5,2.5 0.5 0.5)),((1 1 0,1 2 0,0.5 2.5 0.5,1 1 0)),((0.5 0.5 0.5,1 1 0,0.5 2.5 0.5,0.5 0.5 0.5)),((1 3 1,2 2 0,3 3 1,1 3 1)),((0.5 2.5 0.5,1 2 0,1 3 1,0.5 2.5 0.5)),((1 3 1,1 2 0,2 2 0,1 3 1)),((2 2 0,2 1 0,2.5 0.5 0.5,2 2 0)),((3.5 2.5 1.5,3 3 1,3.5 1.5 1.5,3.5 2.5 1.5)),((3.5 1.5 1.5,2 2 0,2.5 0.5 0.5,3.5 1.5 1.5)),((3 3 1,2 2 0,3.5 1.5 1.5,3 3 1))) +Extrude roof|t Extrude building and roof|t Empty building and roof|POLYHEDRALSURFACE EMPTY Empty roof|POLYHEDRALSURFACE EMPTY ----------------------------------------------------------------------- Summary of changes: doc/reference_sfcgal.xml | 2 +- sfcgal/regress/extrudestraigthskeleton.sql | 111 +++++++++++++++--------- sfcgal/regress/extrudestraigthskeleton_expected | 2 +- 3 files changed, 72 insertions(+), 43 deletions(-) hooks/post-receive -- PostGIS From git at osgeo.org Thu May 7 10:22:39 2026 From: git at osgeo.org (git at osgeo.org) Date: Thu, 7 May 2026 10:22:39 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-478-gd36418658 Message-ID: <20260507172239.447861B9E38@trac.osgeo.org> 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 d36418658cee0538c9a162500f747be614b1e2d0 (commit) via 96afdc7dd4bb292b02b468c650ddf6db203adb71 (commit) via 947f31aa704c7f34ae64ef1190c8c5fba7f343c8 (commit) from 05682d312a8b67ba7b90dfd5f89993ff082c054b (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 d36418658cee0538c9a162500f747be614b1e2d0 Merge: 05682d312 96afdc7dd Author: Paul Ramsey Date: Thu May 7 10:22:35 2026 -0700 Merge branch 'ptitjano-collection-doc-typo' commit 96afdc7dd4bb292b02b468c650ddf6db203adb71 Author: Jean Felder Date: Wed May 6 15:54:37 2026 +0200 doc: Fix typo in ST_GeometryN documentation diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml index d6e9f8d5c..014d71b8f 100644 --- a/doc/reference_accessor.xml +++ b/doc/reference_accessor.xml @@ -1222,7 +1222,7 @@ LINESTRING(0 0 1,1 1 1,1 2 1,1 1 1,0 0 1) Description Return the 1-based Nth element geometry of an input geometry which is a - GEOMETRYCOLLECTION, MULTIPOINT, MULTILINESTRING, MULTICURVE, MULTI)POLYGON, or POLYHEDRALSURFACE. + GEOMETRYCOLLECTION, MULTIPOINT, MULTILINESTRING, MULTICURVE, MULTIPOLYGON, or POLYHEDRALSURFACE. Otherwise, returns NULL. commit 947f31aa704c7f34ae64ef1190c8c5fba7f343c8 Author: Jean Felder Date: Wed May 6 15:55:42 2026 +0200 chrore(doc): Remove trailing whitespaces in reference_accessor diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml index 52b096594..d6e9f8d5c 100644 --- a/doc/reference_accessor.xml +++ b/doc/reference_accessor.xml @@ -935,7 +935,7 @@ FROM polyTable FROM ST_DumpRings( 'POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9), (2 2, 2 3, 3 3, 3 2, 2 2), (4 2, 4 4, 6 4, 6 2, 4 2))'); - path | geom + path | geom ------+-------------------------------- {0} | POLYGON((1 9,9 9,9 1,1 1,1 9)) {1} | POLYGON((2 2,2 3,3 3,3 2,2 2)) ----------------------------------------------------------------------- Summary of changes: doc/reference_accessor.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) hooks/post-receive -- PostGIS From git at osgeo.org Thu May 7 10:24:19 2026 From: git at osgeo.org (git at osgeo.org) Date: Thu, 7 May 2026 10:24:19 -0700 (PDT) Subject: [SCM] PostGIS branch stable-3.6 updated. 3.6.3-2-g8591f8189 Message-ID: <20260507172420.1232F1B97F0@trac.osgeo.org> 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.6 has been updated via 8591f818942c094254d0f9bbaface72de2795180 (commit) from 146713b10690d84328e7bc01d176728c7db23798 (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 8591f818942c094254d0f9bbaface72de2795180 Author: Jean Felder Date: Wed May 6 15:54:37 2026 +0200 doc: Fix typo in ST_GeometryN documentation diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml index 25668392f..c7f87e9c0 100644 --- a/doc/reference_accessor.xml +++ b/doc/reference_accessor.xml @@ -1218,7 +1218,7 @@ LINESTRING(0 0 1,1 1 1,1 2 1,1 1 1,0 0 1) Description Return the 1-based Nth element geometry of an input geometry which is a - GEOMETRYCOLLECTION, MULTIPOINT, MULTILINESTRING, MULTICURVE, MULTI)POLYGON, or POLYHEDRALSURFACE. + GEOMETRYCOLLECTION, MULTIPOINT, MULTILINESTRING, MULTICURVE, MULTIPOLYGON, or POLYHEDRALSURFACE. Otherwise, returns NULL. ----------------------------------------------------------------------- Summary of changes: doc/reference_accessor.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) hooks/post-receive -- PostGIS From git at osgeo.org Thu May 7 19:39:13 2026 From: git at osgeo.org (git at osgeo.org) Date: Thu, 7 May 2026 19:39:13 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-479-gf3f83293a Message-ID: <20260508023913.C778D1BB97B@trac.osgeo.org> 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 f3f83293aa7ba73988d579937a0251ee313f042f (commit) from d36418658cee0538c9a162500f747be614b1e2d0 (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 f3f83293aa7ba73988d579937a0251ee313f042f Author: Teramoto Ikuhiro Date: Fri May 8 00:43:01 2026 +0000 Translated PostGIS Manual using Weblate (Japanese) Currently translated at 99.9% (5538 of 5542 strings) Translation: postgis/PostGIS Manual Translate-URL: https://weblate.osgeo.org/projects/postgis/postgis-manual/ja/ diff --git a/doc/po/ja/postgis-manual.po b/doc/po/ja/postgis-manual.po index 8bba9f185..29b8984e6 100644 --- a/doc/po/ja/postgis-manual.po +++ b/doc/po/ja/postgis-manual.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: postgis 3.5\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" "POT-Creation-Date: 2025-11-13 16:23+0000\n" -"PO-Revision-Date: 2026-03-31 10:39+0000\n" +"PO-Revision-Date: 2026-05-08 02:39+0000\n" "Last-Translator: Teramoto Ikuhiro \n" "Language-Team: Japanese \n" @@ -9793,9 +9793,9 @@ msgid "" "ST_Endpoint(g)) and (does not self " "intersect)." msgstr "" -"??LINESTRING?(ST_StartPoint(g) ~= ST_Endpoint(g))??(?????????)????TRUE" +"??LINESTRING? (" +"ST_StartPoint(g) ~= ST_Endpoint(g))?? (?????????)????TRUE" "??????" #. Tag: para ----------------------------------------------------------------------- Summary of changes: doc/po/ja/postgis-manual.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) hooks/post-receive -- PostGIS From trac at osgeo.org Thu May 7 23:37:55 2026 From: trac at osgeo.org (PostGIS) Date: Fri, 08 May 2026 06:37:55 -0000 Subject: [PostGIS] #6059: AddGeometryColumn calls postgis_type_name without @extschema@ prefix In-Reply-To: <049.53e6f5bd167d0c7df5c745e96eec6194@osgeo.org> References: <049.53e6f5bd167d0c7df5c745e96eec6194@osgeo.org> Message-ID: <064.bbebf4884af4c9a15a91034623a489a1@osgeo.org> #6059: AddGeometryColumn calls postgis_type_name without @extschema@ prefix ----------------------+--------------------- Reporter: fthomas | Owner: pramsey Type: defect | Status: closed Priority: medium | Milestone: Component: postgis | Version: 3.6.x Resolution: fixed | Keywords: ----------------------+--------------------- Comment (by fthomas): I've been using QGIS to export a vector layer as "PostgreSQL SQL dump" and that is using !AddGeometryColumn for creating the schema. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Fri May 8 11:36:19 2026 From: trac at osgeo.org (PostGIS) Date: Fri, 08 May 2026 18:36:19 -0000 Subject: [PostGIS] #6077: Some notes about enhancements to the postgis manual In-Reply-To: <049.1916b90b6296f16b98377341be01c6f6@osgeo.org> References: <049.1916b90b6296f16b98377341be01c6f6@osgeo.org> Message-ID: <064.dfc89b1171c7dfba97a82db39dd7c34e@osgeo.org> #6077: Some notes about enhancements to the postgis manual ----------------------------+--------------------------- Reporter: jidanni | Owner: robe Type: enhancement | Status: new Priority: low | Milestone: PostGIS 3.7.0 Component: documentation | Version: 3.6.x Resolution: | Keywords: ----------------------------+--------------------------- Changes (by robe): * milestone: PostGIS 3.6.3 => PostGIS 3.7.0 -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Sat May 9 20:43:15 2026 From: git at osgeo.org (git at osgeo.org) Date: Sat, 9 May 2026 20:43:15 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-480-geeffb4311 Message-ID: <20260510034316.4FF32185716@trac.osgeo.org> 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 eeffb4311706d8b93c84b4817bbbbf3142cd3626 (commit) from f3f83293aa7ba73988d579937a0251ee313f042f (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 eeffb4311706d8b93c84b4817bbbbf3142cd3626 Author: Paul Ramsey Date: Sat May 9 20:43:01 2026 -0700 Edits and tweaks to docs, closes #6077 diff --git a/doc/reference_accessor.xml b/doc/reference_accessor.xml index 014d71b8f..fd1efaaf1 100644 --- a/doc/reference_accessor.xml +++ b/doc/reference_accessor.xml @@ -2692,7 +2692,7 @@ VALUES (ST_GeomFromEWKT('POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0) Index is 1-based as for OGC specs since version 0.8.0. - Backward indexing (negative index) is not in OGC + Backward indexing (negative index) is not in OGC. Previous versions implemented this as 0-based instead. diff --git a/doc/reference_constructor.xml b/doc/reference_constructor.xml index 93d2a06cc..f3ddbc048 100644 --- a/doc/reference_constructor.xml +++ b/doc/reference_constructor.xml @@ -1287,7 +1287,7 @@ has an unknown SRID (as is the case by default). Example: Generating the word 'Yo' - SELECT ST_AsText(ST_Letters('Yo'), 1); + SELECT ST_AsText(ST_Letters('Yo')); diff --git a/doc/reference_editor.xml b/doc/reference_editor.xml index a74e529da..cff821413 100644 --- a/doc/reference_editor.xml +++ b/doc/reference_editor.xml @@ -2013,9 +2013,7 @@ SELECT ST_AsText( ST_RemoveRepeatedPoints( 'LINESTRING (0 0, 0 0, 1 1, 5 5, 1 1, Examples SELECT ST_AsText(geom) as line, ST_AsText(ST_Reverse(geom)) As reverseline -FROM -(SELECT ST_MakeLine(ST_Point(1,2), - ST_Point(1,10)) As geom) as foo; +FROM ST_MakeLine(ST_Point(1,2), ST_Point(1,10)) As geom; --result line | reverseline ---------------------+---------------------- diff --git a/doc/reference_measure.xml b/doc/reference_measure.xml index d50c5758c..5b2de34fa 100644 --- a/doc/reference_measure.xml +++ b/doc/reference_measure.xml @@ -68,7 +68,7 @@ from ( Return area square feet and transform to Massachusetts state plane meters (EPSG:26986) to get square meters. Note this is in square feet because 2249 is - Massachusetts State Plane Feet and transformed area is in square meters since EPSG:26986 is state plane Massachusetts meters + Massachusetts State Plane Feet and transformed area is in square meters since EPSG:26986 is state plane Massachusetts meters. select ST_Area(geom) sqft, ST_Area(ST_Transform(geom, 26986)) As sqm from ( @@ -1175,7 +1175,7 @@ ST_3DLength Description - Calculates the length or perimeter of a geometry on an ellipsoid. This + Calculates the length or perimeter of a geometry on a spheroid. This is useful if the coordinates of the geometry are in longitude/latitude and a length is desired without reprojection. The spheroid is specified by a text value as follows: diff --git a/doc/reference_overlay.xml b/doc/reference_overlay.xml index 16ffc5f90..2e6eaa80d 100644 --- a/doc/reference_overlay.xml +++ b/doc/reference_overlay.xml @@ -284,8 +284,8 @@ where not ST_IsEmpty(ST_Buffer(ST_Intersection(country.geom, poly.geom), 0.0));< - Examples: 2.5Dish - Note this is not a true intersection, compare to the same example using . + Examples: 2.5D geometries + Note this is not a true 3D intersection. It uses 2.5D geometries (geometries with Z ordinates, but where calculations are performed in 2D). Compare to the same example using . select ST_AsText(ST_Intersection(linestring, polygon)) As wkt from ST_GeomFromText('LINESTRING Z (2 2 6,1.5 1.5 7,1 1 8,0.5 0.5 8,0 0 10)') AS linestring diff --git a/doc/reference_relationship.xml b/doc/reference_relationship.xml index f0e9c7385..1cd519be8 100644 --- a/doc/reference_relationship.xml +++ b/doc/reference_relationship.xml @@ -2261,7 +2261,7 @@ SELECT s.gid, s.school_name LEFT JOIN hospitals h ON ST_DWithin(s.geom, h.geom, 3000) WHERE h.gid IS NULL; --- Find broadcasting towers that receiver with limited range can receive. +-- Find broadcasting towers that a receiver with limited range can receive. -- Data is geometry in Spherical Mercator (SRID=3857), ranges are approximate. -- Create geometry index that will check proximity limit of user to tower diff --git a/doc/using_postgis_dataman.xml b/doc/using_postgis_dataman.xml index 896184c0f..0fc321fe7 100644 --- a/doc/using_postgis_dataman.xml +++ b/doc/using_postgis_dataman.xml @@ -53,6 +53,7 @@ If Z or M values are present in a geometry value, they must be defined for each point in the geometry. If a geometry has Z or M ordinates the coordinate dimension is 3D; if it has both Z and M the coordinate dimension is 4D. + The coordinate dimension is at least 2, because every geometry has at least X and Y coordinates. Geometry values are associated with a @@ -70,7 +71,7 @@ The geometry dimension is a property of geometry types. Point types have dimension 0, linear types have dimension 1, - and polygonal types have dimension 2. + polygonal types have dimension 2, and solid types have dimension 3. Collections have the dimension of the maximum element dimension. @@ -96,12 +97,13 @@ are defined for each geometry type. Geometries are topologically closed, so they always contain their boundary. The boundary is a geometry of dimension one less than that of the geometry itself. + For example, the boundary of a Polygon is its rings (LineStrings), the boundary of a LineString is its endpoints (Points), and the boundary of a Point is empty. The OGC geometry model defines validity rules for each geometry type. These rules ensure that geometry values represents realistic situations (e.g. it is possible to specify a polygon - with a hole lying outside the shell, but this makes no sense geometrically + with a hole lying outside its shell, but this makes no sense geometrically and is thus invalid). PostGIS also allows storing and manipulating invalid geometry values. This allows detecting and fixing them if needed. @@ -166,6 +168,9 @@ POINT ZM (1 2 3 4) MultiPolygon A MultiPolygon is a collection of non-overlapping, non-adjacent Polygons. Polygons in the collection may touch only at a finite number of points. + (Two polygons are adjacent if they share an edge. + They touch if they share only points or edges on their boundaries). + For more details on MultiPolygon validity, see . MULTIPOLYGON (((1 5, 5 5, 5 1, 1 1, 1 5)), ((6 5, 9 1, 6 1, 6 5))) @@ -376,7 +381,7 @@ geometry = ST_GeomFromText(text WKT, SRID); For example, a statement to create and insert a spatial object from WKT and a SRID is: INSERT INTO geotable ( geom, name ) - VALUES ( ST_GeomFromText('POINT(-126.4 45.32)', 312), 'A Place'); + VALUES ( ST_GeomFromText('POINT(-126.4 45.32)', 312), 'A Place'); -- 312 is the SRID Well-Known Binary (WKB) provides a portable, full-precision representation of spatial data as binary data (arrays of bytes). @@ -426,7 +431,7 @@ geometry = ST_GeomFromWKB(bytea WKB, SRID); which means that all access is done via invoking functions on geometry values. Functions allow creating geometry objects, accessing or updating all internal fields, - and compute new geometry values. + and computing new geometry values. PostGIS supports all the functions specified in the OGC Simple feature access - Part 2: SQL option (SFS) specification, as well many others. @@ -825,8 +830,9 @@ SELECT ST_Distance('LINESTRING(-122.33 47.606, 0.0 51.5)'::geometry, 'POINT(-21. - By default, all distance and area calculations are done on the spheroid. You should find that the results of calculations in local areas match up will with local planar results in good local projections. + By default, all distance and area calculations are done on the spheroid. You should find that the results of calculations in local areas match up well with local planar results in good local projections. Over larger areas, the spheroidal calculations will be more accurate than any calculation done on a projected plane. + The difference is that the spheroid takes into account the curvature and non-spherical shape of the Earth, whereas planar calculations assume a flat surface. All the geography functions have the option of using a sphere calculation, by setting a final boolean parameter to 'FALSE'. This will somewhat speed up calculations, particularly for cases where the geometries are very simple. @@ -1672,7 +1678,9 @@ VALUES ( 990000, coord_dimension - The coordinate dimension (2, 3 or 4) of the column. + The coordinate dimension (2, 3 or 4) of the column. + The coordinate dimension is at least 2, representing X and Y coordinates. + diff --git a/doc/using_postgis_query.xml b/doc/using_postgis_query.xml index c788ce92f..fb9776f51 100644 --- a/doc/using_postgis_query.xml +++ b/doc/using_postgis_query.xml @@ -316,7 +316,8 @@ FROM city JOIN state ON ST_Intersects(city.geom, state.geom); For example, consider a linear dataset representing a road network. It may be required to identify all road segments that cross - each other, not at a point, but in a line (perhaps to validate some business rule). + each other, not at a point, but in a line (perhaps to validate a business rule + such as ensuring that no two different roads share a common segment). In this case does not provide the necessary spatial filter, since for linear features it returns true only where they cross at a point. @@ -642,12 +643,12 @@ GROUP BY m.name ORDER BY roads_km; name | roads_km -----------------------------+------------------ -SURREY | 1539.47553551242 -VANCOUVER | 1450.33093486576 -LANGLEY DISTRICT | 833.793392535662 -BURNABY | 773.769091404338 -PRINCE GEORGE | 694.37554369147 +----------------------------+---------- +SURREY | 1539.476 +VANCOUVER | 1450.331 +LANGLEY DISTRICT | 833.793 +BURNABY | 773.769 +PRINCE GEORGE | 694.376 ... This query takes a while, because every road in the table is ----------------------------------------------------------------------- Summary of changes: doc/reference_accessor.xml | 2 +- doc/reference_constructor.xml | 2 +- doc/reference_editor.xml | 4 +--- doc/reference_measure.xml | 4 ++-- doc/reference_overlay.xml | 4 ++-- doc/reference_relationship.xml | 2 +- doc/using_postgis_dataman.xml | 20 ++++++++++++++------ doc/using_postgis_query.xml | 15 ++++++++------- 8 files changed, 30 insertions(+), 23 deletions(-) hooks/post-receive -- PostGIS From trac at osgeo.org Sat May 9 20:43:20 2026 From: trac at osgeo.org (PostGIS) Date: Sun, 10 May 2026 03:43:20 -0000 Subject: [PostGIS] #6077: Some notes about enhancements to the postgis manual In-Reply-To: <049.1916b90b6296f16b98377341be01c6f6@osgeo.org> References: <049.1916b90b6296f16b98377341be01c6f6@osgeo.org> Message-ID: <064.d993f72106c6ca2ff37e9d0ab235cbce@osgeo.org> #6077: Some notes about enhancements to the postgis manual ----------------------------+--------------------------- Reporter: jidanni | Owner: robe Type: enhancement | Status: closed Priority: low | Milestone: PostGIS 3.7.0 Component: documentation | Version: 3.6.x Resolution: fixed | Keywords: ----------------------------+--------------------------- Changes (by Paul Ramsey ): * resolution: => fixed * status: new => closed Comment: In [changeset:"eeffb4311706d8b93c84b4817bbbbf3142cd3626/git" eeffb43/git]: {{{#!CommitTicketReference repository="git" revision="eeffb4311706d8b93c84b4817bbbbf3142cd3626" Edits and tweaks to docs, closes #6077 }}} -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Mon May 11 08:56:57 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 11 May 2026 08:56:57 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-481-gae7249900 Message-ID: <20260511155657.E95AB1A84EC@trac.osgeo.org> 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 ae7249900558365d48a7c5fe5ed34463becd8382 (commit) from eeffb4311706d8b93c84b4817bbbbf3142cd3626 (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 ae7249900558365d48a7c5fe5ed34463becd8382 Author: Paul Ramsey Date: Mon May 11 08:56:23 2026 -0700 Fix ST_Length and ST_Perimeter for geography collections, closes #6076 ST_Length(geography) on collections now only sums linear components, and ST_Perimeter(geography) now only sums areal components. Added lwgeom_perimeter_spheroid to support the separation of logic. Preserve legacy behaviour in ST_LengthSpheroid. diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index 401ac78cf..3ac202217 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -1814,6 +1814,10 @@ extern double lwgeom_area_spheroid(const LWGEOM *lwgeom, const SPHEROID *spheroi */ extern double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s); +/** +* Calculate the geodetic perimeter of a lwgeom on the unit sphere. +*/ +extern double lwgeom_perimeter_spheroid(const LWGEOM *geom, const SPHEROID *s); /** * Calculate covers predicate for two lwgeoms on the sphere. Currently * only handles point-in-polygon. diff --git a/liblwgeom/lwgeodetic.c b/liblwgeom/lwgeodetic.c index 128ce4591..252f54c29 100644 --- a/liblwgeom/lwgeodetic.c +++ b/liblwgeom/lwgeodetic.c @@ -3150,31 +3150,22 @@ double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s) assert(geom); - /* No area in nothing */ + /* No length in nothing */ if ( lwgeom_is_empty(geom) ) return 0.0; type = geom->type; - if ( type == POINTTYPE || type == MULTIPOINTTYPE ) + if ( type == POINTTYPE || type == MULTIPOINTTYPE || + type == POLYGONTYPE || type == MULTIPOLYGONTYPE || + type == TRIANGLETYPE || type == TINTYPE || + type == POLYHEDRALSURFACETYPE || type == CURVEPOLYTYPE || + type == MULTISURFACETYPE ) return 0.0; if ( type == LINETYPE ) return ptarray_length_spheroid(((LWLINE*)geom)->points, s); - if ( type == POLYGONTYPE ) - { - LWPOLY *poly = (LWPOLY*)geom; - for ( i = 0; i < poly->nrings; i++ ) - { - length += ptarray_length_spheroid(poly->rings[i], s); - } - return length; - } - - if ( type == TRIANGLETYPE ) - return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s); - if ( lwtype_is_collection( type ) ) { LWCOLLECTION *col = (LWCOLLECTION*)geom; @@ -3186,7 +3177,48 @@ double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s) return length; } - lwerror("unsupported type passed to lwgeom_length_sphere"); + lwerror("unsupported type passed to lwgeom_length_spheroid"); + return 0.0; +} + +double lwgeom_perimeter_spheroid(const LWGEOM *geom, const SPHEROID *s) +{ + int type; + uint32_t i = 0; + double perimeter = 0.0; + + assert(geom); + + /* No perimeter in nothing */ + if ( lwgeom_is_empty(geom) ) + return 0.0; + + type = geom->type; + + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + { + perimeter += ptarray_length_spheroid(poly->rings[i], s); + } + return perimeter; + } + + if ( type == TRIANGLETYPE ) + return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s); + + if ( lwtype_is_collection( type ) ) + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + + for ( i = 0; i < col->ngeoms; i++ ) + { + perimeter += lwgeom_perimeter_spheroid(col->geoms[i], s); + } + return perimeter; + } + return 0.0; } diff --git a/postgis/geography_measurement.c b/postgis/geography_measurement.c index ece265fc0..15090d670 100644 --- a/postgis/geography_measurement.c +++ b/postgis/geography_measurement.c @@ -599,12 +599,12 @@ Datum geography_perimeter(PG_FUNCTION_ARGS) s.a = s.b = s.radius; /* Calculate the length */ - length = lwgeom_length_spheroid(lwgeom, &s); + length = lwgeom_perimeter_spheroid(lwgeom, &s); /* Something went wrong... */ if ( length < 0.0 ) { - elog(ERROR, "lwgeom_length_spheroid returned length < 0.0"); + elog(ERROR, "lwgeom_perimeter_spheroid returned length < 0.0"); PG_RETURN_NULL(); } diff --git a/postgis/lwgeom_spheroid.c b/postgis/lwgeom_spheroid.c index 572470fe2..bbfac2173 100644 --- a/postgis/lwgeom_spheroid.c +++ b/postgis/lwgeom_spheroid.c @@ -165,7 +165,7 @@ Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(0.0); } - length = lwgeom_length_spheroid(lwgeom, sphere); + length = lwgeom_length_spheroid(lwgeom, sphere) + lwgeom_perimeter_spheroid(lwgeom, sphere); lwgeom_free(lwgeom); PG_FREE_IF_COPY(geom, 0); diff --git a/regress/core/geography.sql b/regress/core/geography.sql index 9b80cd757..5f98c7066 100644 --- a/regress/core/geography.sql +++ b/regress/core/geography.sql @@ -149,6 +149,10 @@ SELECT 'lrs_llp_4', round(ST_LineLocatePoint(geography 'linestring(0 1, 50 1)', SELECT 'lrs_substr_1', ST_AsText(ST_LineSubstring(geography 'linestring(0 20, 100 20)', 0.1, 0.2),2); +-- Ticket #6076 +SELECT 'ticket_6076_length', round(ST_Length(ST_GeogFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))'))::numeric, 3); +SELECT 'ticket_6076_perimeter', round(ST_Perimeter(ST_GeogFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))'))::numeric, 3); + --SELECT 'lrs_cp_1', ST_AsText(ST_ClosestPoint(geography 'Linestring(0 20, 50 20)', 'Point(25 20)'), 3); --SELECT 'lrs_cp_2', ST_AsText(ST_ClosestPoint(geography 'Point(25 20)', geography 'Linestring(0 20, 50 20)'), 3); diff --git a/regress/core/geography_expected b/regress/core/geography_expected index 0ca174883..8584b7df0 100644 --- a/regress/core/geography_expected +++ b/regress/core/geography_expected @@ -66,4 +66,6 @@ lrs_llp_2|0.00 lrs_llp_3|1.00 ERROR: geography_line_locate_point: 2nd arg is not a point lrs_substr_1|LINESTRING(9.28 23.25,18.97 25.93) +ticket_6076_length|110574.389 +ticket_6076_perimeter|378793.448 lrs_sl_1|LINESTRING(25 42.79,25 40) ----------------------------------------------------------------------- Summary of changes: liblwgeom/liblwgeom.h.in | 4 ++++ liblwgeom/lwgeodetic.c | 46 ++++++++++++++++++++++++++++++++++------- postgis/geography_measurement.c | 4 ++-- postgis/lwgeom_spheroid.c | 2 +- regress/core/geography.sql | 4 ++++ regress/core/geography_expected | 2 ++ 6 files changed, 52 insertions(+), 10 deletions(-) hooks/post-receive -- PostGIS From trac at osgeo.org Mon May 11 08:57:00 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 11 May 2026 15:57:00 -0000 Subject: [PostGIS] #6076: ST_Length() on GEOMETRYCOLLECTION for geography type In-Reply-To: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> References: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> Message-ID: <068.d597890867456b32a89ef55a7abcdb8d@osgeo.org> #6076: ST_Length() on GEOMETRYCOLLECTION for geography type --------------------------+--------------------------- Reporter: paleolimbot | Owner: pramsey Type: defect | Status: closed Priority: medium | Milestone: PostGIS 3.6.3 Component: postgis | Version: 3.6.x Resolution: fixed | Keywords: --------------------------+--------------------------- Changes (by Paul Ramsey ): * resolution: => fixed * status: new => closed Comment: In [changeset:"ae7249900558365d48a7c5fe5ed34463becd8382/git" ae72499/git]: {{{#!CommitTicketReference repository="git" revision="ae7249900558365d48a7c5fe5ed34463becd8382" Fix ST_Length and ST_Perimeter for geography collections, closes #6076 ST_Length(geography) on collections now only sums linear components, and ST_Perimeter(geography) now only sums areal components. Added lwgeom_perimeter_spheroid to support the separation of logic. Preserve legacy behaviour in ST_LengthSpheroid. }}} -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Mon May 11 08:58:25 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 11 May 2026 08:58:25 -0700 (PDT) Subject: [SCM] PostGIS branch stable-3.6 updated. 3.6.3-4-g79f2b6bc4 Message-ID: <20260511155825.E0DE51A8C3A@trac.osgeo.org> 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.6 has been updated via 79f2b6bc45c66b01d3455c14591fbff4e2b90cf0 (commit) via 2d60db735cd96d889d7e43474deb436563ec3b77 (commit) from 8591f818942c094254d0f9bbaface72de2795180 (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 79f2b6bc45c66b01d3455c14591fbff4e2b90cf0 Author: Paul Ramsey Date: Mon May 11 08:58:16 2026 -0700 News item for #6076 diff --git a/NEWS b/NEWS index 769000fe3..249ca7adb 100644 --- a/NEWS +++ b/NEWS @@ -3,7 +3,8 @@ PostGIS 3.6.4 * Fixes * -- +- #6076, ST_Length(geography) for GeometryCollection not consistent + with geometry implementation PostGIS 3.6.3 commit 2d60db735cd96d889d7e43474deb436563ec3b77 Author: Paul Ramsey Date: Mon May 11 08:56:23 2026 -0700 Fix ST_Length and ST_Perimeter for geography collections, closes #6076 ST_Length(geography) on collections now only sums linear components, and ST_Perimeter(geography) now only sums areal components. Added lwgeom_perimeter_spheroid to support the separation of logic. Preserve legacy behaviour in ST_LengthSpheroid. diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index 391c383aa..16bace594 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -1811,6 +1811,10 @@ extern double lwgeom_area_spheroid(const LWGEOM *lwgeom, const SPHEROID *spheroi */ extern double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s); +/** +* Calculate the geodetic perimeter of a lwgeom on the unit sphere. +*/ +extern double lwgeom_perimeter_spheroid(const LWGEOM *geom, const SPHEROID *s); /** * Calculate covers predicate for two lwgeoms on the sphere. Currently * only handles point-in-polygon. diff --git a/liblwgeom/lwgeodetic.c b/liblwgeom/lwgeodetic.c index 128ce4591..252f54c29 100644 --- a/liblwgeom/lwgeodetic.c +++ b/liblwgeom/lwgeodetic.c @@ -3150,31 +3150,22 @@ double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s) assert(geom); - /* No area in nothing */ + /* No length in nothing */ if ( lwgeom_is_empty(geom) ) return 0.0; type = geom->type; - if ( type == POINTTYPE || type == MULTIPOINTTYPE ) + if ( type == POINTTYPE || type == MULTIPOINTTYPE || + type == POLYGONTYPE || type == MULTIPOLYGONTYPE || + type == TRIANGLETYPE || type == TINTYPE || + type == POLYHEDRALSURFACETYPE || type == CURVEPOLYTYPE || + type == MULTISURFACETYPE ) return 0.0; if ( type == LINETYPE ) return ptarray_length_spheroid(((LWLINE*)geom)->points, s); - if ( type == POLYGONTYPE ) - { - LWPOLY *poly = (LWPOLY*)geom; - for ( i = 0; i < poly->nrings; i++ ) - { - length += ptarray_length_spheroid(poly->rings[i], s); - } - return length; - } - - if ( type == TRIANGLETYPE ) - return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s); - if ( lwtype_is_collection( type ) ) { LWCOLLECTION *col = (LWCOLLECTION*)geom; @@ -3186,7 +3177,48 @@ double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s) return length; } - lwerror("unsupported type passed to lwgeom_length_sphere"); + lwerror("unsupported type passed to lwgeom_length_spheroid"); + return 0.0; +} + +double lwgeom_perimeter_spheroid(const LWGEOM *geom, const SPHEROID *s) +{ + int type; + uint32_t i = 0; + double perimeter = 0.0; + + assert(geom); + + /* No perimeter in nothing */ + if ( lwgeom_is_empty(geom) ) + return 0.0; + + type = geom->type; + + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + { + perimeter += ptarray_length_spheroid(poly->rings[i], s); + } + return perimeter; + } + + if ( type == TRIANGLETYPE ) + return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s); + + if ( lwtype_is_collection( type ) ) + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + + for ( i = 0; i < col->ngeoms; i++ ) + { + perimeter += lwgeom_perimeter_spheroid(col->geoms[i], s); + } + return perimeter; + } + return 0.0; } diff --git a/postgis/geography_measurement.c b/postgis/geography_measurement.c index ece265fc0..15090d670 100644 --- a/postgis/geography_measurement.c +++ b/postgis/geography_measurement.c @@ -599,12 +599,12 @@ Datum geography_perimeter(PG_FUNCTION_ARGS) s.a = s.b = s.radius; /* Calculate the length */ - length = lwgeom_length_spheroid(lwgeom, &s); + length = lwgeom_perimeter_spheroid(lwgeom, &s); /* Something went wrong... */ if ( length < 0.0 ) { - elog(ERROR, "lwgeom_length_spheroid returned length < 0.0"); + elog(ERROR, "lwgeom_perimeter_spheroid returned length < 0.0"); PG_RETURN_NULL(); } diff --git a/postgis/lwgeom_spheroid.c b/postgis/lwgeom_spheroid.c index 8c11edff5..a8af0f2d9 100644 --- a/postgis/lwgeom_spheroid.c +++ b/postgis/lwgeom_spheroid.c @@ -370,7 +370,7 @@ Datum LWGEOM_length_ellipsoid_linestring(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(0.0); } - length = lwgeom_length_spheroid(lwgeom, sphere); + length = lwgeom_length_spheroid(lwgeom, sphere) + lwgeom_perimeter_spheroid(lwgeom, sphere); lwgeom_free(lwgeom); PG_FREE_IF_COPY(geom, 0); diff --git a/regress/core/geography.sql b/regress/core/geography.sql index 9b80cd757..5f98c7066 100644 --- a/regress/core/geography.sql +++ b/regress/core/geography.sql @@ -149,6 +149,10 @@ SELECT 'lrs_llp_4', round(ST_LineLocatePoint(geography 'linestring(0 1, 50 1)', SELECT 'lrs_substr_1', ST_AsText(ST_LineSubstring(geography 'linestring(0 20, 100 20)', 0.1, 0.2),2); +-- Ticket #6076 +SELECT 'ticket_6076_length', round(ST_Length(ST_GeogFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))'))::numeric, 3); +SELECT 'ticket_6076_perimeter', round(ST_Perimeter(ST_GeogFromText('GEOMETRYCOLLECTION (POINT (5 5), LINESTRING (0 0, 0 1), POLYGON ((0 0, 0 1, 1 0, 0 0)))'))::numeric, 3); + --SELECT 'lrs_cp_1', ST_AsText(ST_ClosestPoint(geography 'Linestring(0 20, 50 20)', 'Point(25 20)'), 3); --SELECT 'lrs_cp_2', ST_AsText(ST_ClosestPoint(geography 'Point(25 20)', geography 'Linestring(0 20, 50 20)'), 3); diff --git a/regress/core/geography_expected b/regress/core/geography_expected index 0ca174883..8584b7df0 100644 --- a/regress/core/geography_expected +++ b/regress/core/geography_expected @@ -66,4 +66,6 @@ lrs_llp_2|0.00 lrs_llp_3|1.00 ERROR: geography_line_locate_point: 2nd arg is not a point lrs_substr_1|LINESTRING(9.28 23.25,18.97 25.93) +ticket_6076_length|110574.389 +ticket_6076_perimeter|378793.448 lrs_sl_1|LINESTRING(25 42.79,25 40) ----------------------------------------------------------------------- Summary of changes: NEWS | 3 ++- liblwgeom/liblwgeom.h.in | 4 ++++ liblwgeom/lwgeodetic.c | 46 ++++++++++++++++++++++++++++++++++------- postgis/geography_measurement.c | 4 ++-- postgis/lwgeom_spheroid.c | 2 +- regress/core/geography.sql | 4 ++++ regress/core/geography_expected | 2 ++ 7 files changed, 54 insertions(+), 11 deletions(-) hooks/post-receive -- PostGIS From trac at osgeo.org Mon May 11 08:58:27 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 11 May 2026 15:58:27 -0000 Subject: [PostGIS] #6076: ST_Length() on GEOMETRYCOLLECTION for geography type In-Reply-To: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> References: <053.481048f5d79ff6a6c902aeb268f16ed8@osgeo.org> Message-ID: <068.1180465a7947a75b07c070209736e756@osgeo.org> #6076: ST_Length() on GEOMETRYCOLLECTION for geography type --------------------------+--------------------------- Reporter: paleolimbot | Owner: pramsey Type: defect | Status: closed Priority: medium | Milestone: PostGIS 3.6.3 Component: postgis | Version: 3.6.x Resolution: fixed | Keywords: --------------------------+--------------------------- Comment (by Paul Ramsey ): In [changeset:"2d60db735cd96d889d7e43474deb436563ec3b77/git" 2d60db7/git]: {{{#!CommitTicketReference repository="git" revision="2d60db735cd96d889d7e43474deb436563ec3b77" Fix ST_Length and ST_Perimeter for geography collections, closes #6076 ST_Length(geography) on collections now only sums linear components, and ST_Perimeter(geography) now only sums areal components. Added lwgeom_perimeter_spheroid to support the separation of logic. Preserve legacy behaviour in ST_LengthSpheroid. }}} -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Tue May 12 10:20:30 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 12 May 2026 10:20:30 -0700 (PDT) Subject: [SCM] postgis.net branch website updated. clarity-final-183-gad6f133 Message-ID: <20260512172030.E3FE91AF565@trac.osgeo.org> 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.net". The branch, website has been updated via ad6f1339ad37cb5f067e07549f57ac7edd71c9b6 (commit) from d24e1596fd09b8fb7a3bd5d04919907d67d2f474 (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 ad6f1339ad37cb5f067e07549f57ac7edd71c9b6 Author: Paul Ramsey Date: Tue May 12 10:20:22 2026 -0700 Fix link to source path diff --git a/content/news/2026/02-09_postgis-patches.md b/content/news/2026/02-09_postgis-patches.md index 714d24a..695a8fe 100644 --- a/content/news/2026/02-09_postgis-patches.md +++ b/content/news/2026/02-09_postgis-patches.md @@ -9,7 +9,7 @@ thumbnail: --- The PostGIS development team is pleased to provide -[bug fix releases](/source) for +[bug fix releases](/development/source_code) for PostGIS 3.0 - 3.6. These are the End-Of-Life (EOL) releases for PostGIS 3.0.12 and 3.1.13. If you haven't already upgraded from 3.0 or 3.1 series, you should do so soon. diff --git a/content/news/2026/04-14_postgis-patches.md b/content/news/2026/04-14_postgis-patches.md index 8385934..91203c6 100644 --- a/content/news/2026/04-14_postgis-patches.md +++ b/content/news/2026/04-14_postgis-patches.md @@ -9,7 +9,7 @@ thumbnail: --- The PostGIS development team is pleased to provide -[bug fix and security releases](/source) for +[bug fix and security releases](/development/source_code) for PostGIS 3.2 - 3.6. ----------------------------------------------------------------------- Summary of changes: content/news/2026/02-09_postgis-patches.md | 2 +- content/news/2026/04-14_postgis-patches.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) hooks/post-receive -- postgis.net From trac at osgeo.org Tue May 12 11:36:04 2026 From: trac at osgeo.org (PostGIS) Date: Tue, 12 May 2026 18:36:04 -0000 Subject: [PostGIS] #2362: Support building with CMake In-Reply-To: <046.26616307945f5a6f3ce87c5787c210c4@osgeo.org> References: <046.26616307945f5a6f3ce87c5787c210c4@osgeo.org> Message-ID: <061.2d899fc1c6922b00ab9f558501b2f3f1@osgeo.org> #2362: Support building with CMake --------------------------+----------------------------- Reporter: robe | Owner: strk Type: enhancement | Status: new Priority: medium | Milestone: PostGIS Fund Me Component: build | Version: master Resolution: | Keywords: --------------------------+----------------------------- Changes (by jmckenna): * cc: jmckenna (added) -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Thu May 21 07:40:25 2026 From: trac at osgeo.org (PostGIS) Date: Thu, 21 May 2026 14:40:25 -0000 Subject: [PostGIS] #6010: ST_SetBandNoDataValue can't handle infinite no data In-Reply-To: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> References: <053.e75a882ed0a76b9cbe60661a303659f4@osgeo.org> Message-ID: <068.420cc6d88038807297ca871d7f37ecba@osgeo.org> #6010: ST_SetBandNoDataValue can't handle infinite no data -------------------------+------------------------------------------------- Reporter: | Owner: robe GISuser5432 | Type: defect | Status: new Priority: low | Milestone: PostGIS 3.2.10 Component: raster | Version: 3.6.x Resolution: | Keywords: ST_Polygon (raster) ; Raster; | PostGIS_Raster; -------------------------+------------------------------------------------- Comment (by robe): I'll try to trace when this behavior changed. This might be a change in behavior in PostgreSQL 12 as I think that was the version when they switched how they handle doubles. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Mon May 25 09:38:37 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 25 May 2026 09:38:37 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-483-g9db5b1e16 Message-ID: <20260525163838.09F8713EE60@trac.osgeo.org> 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 9db5b1e16de4b83a4eedc52ce5d60760f9801845 (commit) via e79f97080ec5293ab8af150563f9847257305f72 (commit) from ae7249900558365d48a7c5fe5ed34463becd8382 (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 9db5b1e16de4b83a4eedc52ce5d60760f9801845 Merge: ae7249900 e79f97080 Author: Paul Ramsey Date: Mon May 25 09:38:32 2026 -0700 Merge branch 'ptitjano-tesselate-deprecated' commit e79f97080ec5293ab8af150563f9847257305f72 Author: Jean Felder Date: Mon May 25 15:46:05 2026 +0200 sfcgal: Adapt to deprecated sfcgal_geometry_tesselate `sfcgal_geometry_tessellate` is now used instead. diff --git a/sfcgal/lwgeom_sfcgal.c b/sfcgal/lwgeom_sfcgal.c index 083ba081a..66b2b33cc 100644 --- a/sfcgal/lwgeom_sfcgal.c +++ b/sfcgal/lwgeom_sfcgal.c @@ -327,7 +327,11 @@ sfcgal_tesselate(PG_FUNCTION_ARGS) geom = POSTGIS2SFCGALGeometry(input); PG_FREE_IF_COPY(input, 0); +#if POSTGIS_SFCGAL_VERSION < 20300 result = sfcgal_geometry_tesselate(geom); +#else + result = sfcgal_geometry_tessellate(geom); +#endif sfcgal_geometry_delete(geom); output = SFCGALGeometry2POSTGIS(result, 0, srid); ----------------------------------------------------------------------- Summary of changes: sfcgal/lwgeom_sfcgal.c | 4 ++++ 1 file changed, 4 insertions(+) hooks/post-receive -- PostGIS From git at osgeo.org Mon May 25 09:41:01 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 25 May 2026 09:41:01 -0700 (PDT) Subject: [SCM] PostGIS branch stable-3.6 updated. 3.6.3-6-gf313504c6 Message-ID: <20260525164102.0E643188021@trac.osgeo.org> 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.6 has been updated via f313504c66bb76adffa23d41e0266317710968dd (commit) via 0a8939cc21781b1a0fb8e22a6fb24a300468e9e6 (commit) from 79f2b6bc45c66b01d3455c14591fbff4e2b90cf0 (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 f313504c66bb76adffa23d41e0266317710968dd Author: Paul Ramsey Date: Mon May 25 09:40:50 2026 -0700 News entry for GH-862 diff --git a/NEWS b/NEWS index 249ca7adb..c5a7f5fb6 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ PostGIS 3.6.4 - #6076, ST_Length(geography) for GeometryCollection not consistent with geometry implementation +- GH-862, use SFCGAL undeprecated function sfcgal_geometry_tessellate() (Jean Felder) PostGIS 3.6.3 commit 0a8939cc21781b1a0fb8e22a6fb24a300468e9e6 Author: Jean Felder Date: Mon May 25 15:46:05 2026 +0200 sfcgal: Adapt to deprecated sfcgal_geometry_tesselate `sfcgal_geometry_tessellate` is now used instead. diff --git a/sfcgal/lwgeom_sfcgal.c b/sfcgal/lwgeom_sfcgal.c index cec862018..6f0a4368d 100644 --- a/sfcgal/lwgeom_sfcgal.c +++ b/sfcgal/lwgeom_sfcgal.c @@ -319,7 +319,11 @@ sfcgal_tesselate(PG_FUNCTION_ARGS) geom = POSTGIS2SFCGALGeometry(input); PG_FREE_IF_COPY(input, 0); +#if POSTGIS_SFCGAL_VERSION < 20300 result = sfcgal_geometry_tesselate(geom); +#else + result = sfcgal_geometry_tessellate(geom); +#endif sfcgal_geometry_delete(geom); output = SFCGALGeometry2POSTGIS(result, 0, srid); ----------------------------------------------------------------------- Summary of changes: NEWS | 1 + sfcgal/lwgeom_sfcgal.c | 4 ++++ 2 files changed, 5 insertions(+) hooks/post-receive -- PostGIS From trac at osgeo.org Mon May 25 13:23:19 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 25 May 2026 20:23:19 -0000 Subject: [PostGIS] #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist In-Reply-To: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> References: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> Message-ID: <061.7509e8a28716d0a91db211012bc51a7f@osgeo.org> #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist ----------------------+--------------------------- Reporter: robe | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.7.0 Component: postgis | Version: 3.4.x Resolution: | Keywords: ----------------------+--------------------------- Comment (by robe): I think we concluded the fix for this was to remove the geography srid longlat check from the code. Perhaps we never executed on that idea. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Mon May 25 13:53:33 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 25 May 2026 20:53:33 -0000 Subject: [PostGIS] #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist In-Reply-To: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> References: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> Message-ID: <061.2bf1c1178fb1cea79d9f424b8ace34c5@osgeo.org> #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist ----------------------+--------------------------- Reporter: robe | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.7.0 Component: postgis | Version: 3.4.x Resolution: | Keywords: ----------------------+--------------------------- Comment (by pramsey): PgSQL core has changes that address this, so far unapplied. https://commitfest.postgresql.org/patch/6340/ https://www.postgresql.org/message-id/flat/CAM2+6=UstF2jQc8tZMbb3A- ag84-UhKs2OnYQn7pwwarY9i2nA at mail.gmail.com -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From trac at osgeo.org Mon May 25 14:08:48 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 25 May 2026 21:08:48 -0000 Subject: [PostGIS] #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist In-Reply-To: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> References: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> Message-ID: <061.2c52d9026e4fa35d88e257de6dad1468@osgeo.org> #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist ----------------------+--------------------------- Reporter: robe | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.7.0 Component: postgis | Version: 3.4.x Resolution: | Keywords: ----------------------+--------------------------- Comment (by pramsey): Actually there's two patches live right now, here's the other one https://www.postgresql.org/message- id/CAA0-ca0sPhggS1EK25FmpFh_efbT=ZSZWgEOyKzHiUEC27LPnA at mail.gmail.com -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project. From git at osgeo.org Mon May 25 14:57:53 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 25 May 2026 14:57:53 -0700 (PDT) Subject: [SCM] PostGIS branch master updated. 3.6.0rc2-484-g77c71d1e1 Message-ID: <20260525215753.D917D189981@trac.osgeo.org> 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 77c71d1e18ab526a2bfd7acf3831d233ea56fbbe (commit) from 9db5b1e16de4b83a4eedc52ce5d60760f9801845 (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 77c71d1e18ab526a2bfd7acf3831d233ea56fbbe Author: Paul Ramsey Date: Mon May 25 14:57:46 2026 -0700 Update comments on srid_check_latlong diff --git a/postgis/geography_inout.c b/postgis/geography_inout.c index ad809d9bd..edd41b14f 100644 --- a/postgis/geography_inout.c +++ b/postgis/geography_inout.c @@ -171,7 +171,7 @@ Datum geography_in(PG_FUNCTION_ARGS) lwgeom = lwg_parser_result.geom; } - /* Error on any SRID != default */ + /* Error on any non-geographic SRID */ srid_check_latlong(lwgeom->srid); /* Convert to gserialized */ @@ -448,7 +448,7 @@ Datum geography_from_text(PG_FUNCTION_ARGS) if ( lwgeom_parse_wkt(&lwg_parser_result, wkt, LW_PARSER_CHECK_ALL) == LW_FAILURE ) PG_PARSER_ERROR(lwg_parser_result); - /* Error on any SRID != default */ + /* Error on any non-geographic SRID */ srid_check_latlong(lwg_parser_result.geom->srid); /* Clean up string */ @@ -476,7 +476,7 @@ Datum geography_from_binary(PG_FUNCTION_ARGS) if ( ! lwgeom ) lwpgerror("Unable to parse WKB"); - /* Error on any SRID != default */ + /* Error on any non-geographic SRID */ srid_check_latlong(lwgeom->srid); gser = gserialized_geography_from_lwgeom(lwgeom, -1); @@ -500,7 +500,7 @@ Datum geography_from_geometry(PG_FUNCTION_ARGS) lwgeom->srid = SRID_DEFAULT; } - /* Error on any SRID != default */ + /* Error on any non-geographic SRID */ srid_check_latlong(lwgeom->srid); /* Force the geometry to have valid geodetic coordinate range. */ @@ -569,7 +569,7 @@ Datum geography_recv(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - /* Error on any SRID != default */ + /* Error on any non-geographic SRID */ srid_check_latlong(lwgeom->srid); g_ser = gserialized_geography_from_lwgeom(lwgeom, geog_typmod); ----------------------------------------------------------------------- Summary of changes: postgis/geography_inout.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) hooks/post-receive -- PostGIS From trac at osgeo.org Mon May 25 15:20:56 2026 From: trac at osgeo.org (PostGIS) Date: Mon, 25 May 2026 22:20:56 -0000 Subject: [PostGIS] #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist In-Reply-To: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> References: <046.7db8fae173ff95d3422792441e5a0a68@osgeo.org> Message-ID: <061.2233d7a4274ad9105d33f014961d047e@osgeo.org> #5899: pg_upgrade fails restore geography because spatial_ref_sys does not exist ----------------------+--------------------------- Reporter: robe | Owner: pramsey Type: defect | Status: new Priority: medium | Milestone: PostGIS 3.7.0 Component: postgis | Version: 3.4.x Resolution: | Keywords: ----------------------+--------------------------- Comment (by pramsey): I think I figured out a hack... In the code that looks up the SRID number and figures out how to represent it (as an "EPSG:XX" token, or a WKT string or a PROJ string) we first check for the existence of spatial_ref_sys, and then check for a non-zero size... if it cannot pass both these tests then we bypass it, and hand an "EPSG:XX" token directly into the proj lookup functions. This should, for cases where we have EPSG numbers that match, which is 99% of them, just Work. For people with custom codes, it will fail (still). For people with non-custom but non-epsg codes (French government?) it will fail (still). It does not FIX the problem, that requires pg_upgrade, but I think it dramatically lowers the affected footprint. Who is still screwed... people with custom srids or odd auth_name values who use an SRID in functions called in the schema or have non-4326 geography srids in their schema. For anyone with a non-missing and non-zero-sized spatial_ref_sys table (mostly everyone) things should still work exactly the same. -- Ticket URL: PostGIS The PostGIS Trac is used for bug, enhancement & task tracking, a user and developer wiki, and a view into the subversion code repository of PostGIS project.