[SCM] PostGIS branch master updated. 3.6.0rc2-656-g226a7ef5f
git at osgeo.org
git at osgeo.org
Sun Jun 21 14:21:47 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, master has been updated
via 226a7ef5fef9669372a5d63de0d6ef23dabb4929 (commit)
from a57ccdcaccaceb74534024c795fabd51989ef69e (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 226a7ef5fef9669372a5d63de0d6ef23dabb4929
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jun 20 02:56:51 2026 +0400
Fix X3D 2D polygon serialization
Emit Coordinate child nodes for indexed 2D line and polygon X3D output instead of writing bare coordinate text into the parent element. Reuse the indexed face output path for polygons inside geometry collections so collection wrapping produces a valid Shape payload.
Closes #2838
Closes https://github.com/postgis/postgis/pull/987
diff --git a/NEWS b/NEWS
index 8d59c8d32..36b96fe56 100644
--- a/NEWS
+++ b/NEWS
@@ -94,6 +94,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
when padding is not requested (Darafei Praliaskouski)
- #2623, Generate a pgsql2shp GID field when exporting geometry-only
shapefiles (Darafei Praliaskouski)
+ - #2838, Emit valid X3D Coordinate nodes for 2D polygon output
+ (Darafei Praliaskouski)
- Fix WKB and TWKB parser resource exhaustion on malformed input
(Darafei Praliaskouski)
- #2850, Preserve pgsql2shp numeric precision and scale in DBF metadata
diff --git a/liblwgeom/cunit/cu_out_x3d.c b/liblwgeom/cunit/cu_out_x3d.c
index 8a0ffa1db..2418e31c2 100644
--- a/liblwgeom/cunit/cu_out_x3d.c
+++ b/liblwgeom/cunit/cu_out_x3d.c
@@ -3,6 +3,7 @@
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
* Copyright 2011-2016 Regina Obe
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
*
* This is free software; you can redistribute and/or modify it under
* the terms of the GNU General Public Licence. See the COPYING file.
@@ -75,12 +76,25 @@ static void out_x3d3_test_geoms(void)
"<LineSet vertexCount='3'><Coordinate point='0 1 5 2 3 6 4 5 7' /></LineSet>",
0, 0);
+ /* 2D Linestring */
+ do_x3d3_test("LINESTRING(0 1,2 3,4 5)",
+ "<LineSet vertexCount='3'><Coordinate point='0 1 0 2 3 0 4 5 0' /></LineSet>",
+ 0,
+ 0);
+
/* Polygon **/
do_x3d3_test(
"POLYGON((15 10 3,13.536 6.464 3,10 5 3,6.464 6.464 3,5 10 3,6.464 13.536 3,10 15 3,13.536 13.536 3,15 10 3))",
"<IndexedFaceSet convex='false' coordIndex='0 1 2 3 4 5 6 7'><Coordinate point='15 10 3 13.536 6.464 3 10 5 3 6.464 6.464 3 5 10 3 6.464 13.536 3 10 15 3 13.536 13.536 3 ' /></IndexedFaceSet>",
3, 0);
+ /* 2D Polygon */
+ do_x3d3_test(
+ "POLYGON((0 0,1 0,1 1,0 0))",
+ "<IndexedFaceSet convex='false' coordIndex='0 1 2'><Coordinate point='0 0 0 1 0 0 1 1 0 ' /></IndexedFaceSet>",
+ 0,
+ 0);
+
/* TODO: Polygon - with internal ring - the answer is clearly wrong */
/** do_x3d3_test(
"POLYGON((0 1 3,2 3 3,4 5 3,0 1 3),(6 7 3,8 9 3,10 11 3,6 7 3))",
@@ -104,23 +118,38 @@ static void out_x3d3_test_geoms(void)
"<IndexedLineSet coordIndex='0 1 2 -1 3 4 5'><Coordinate point='0 1 1 2 3 4 4 5 5 6 7 5 8 9 8 10 11 5 ' /></IndexedLineSet>",
0, 0);
+ /* 2D Multiline */
+ do_x3d3_test(
+ "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))",
+ "<IndexedLineSet coordIndex='0 1 2 -1 3 4 5'><Coordinate point='0 1 0 2 3 0 4 5 0 6 7 0 8 9 0 10 11 0 ' /></IndexedLineSet>",
+ 0,
+ 0);
+
/* MultiPolygon */
do_x3d3_test(
"MULTIPOLYGON(((0 1 1,2 3 1,4 5 1,0 1 1)),((6 7 1,8 9 1,10 11 1,6 7 1)))",
"<IndexedFaceSet convex='false' coordIndex='0 1 2 -1 3 4 5'><Coordinate point='0 1 1 2 3 1 4 5 1 6 7 1 8 9 1 10 11 1 ' /></IndexedFaceSet>",
0, 0);
+ /* 2D MultiPolygon */
+ do_x3d3_test(
+ "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))",
+ "<IndexedFaceSet convex='false' coordIndex='0 1 2 -1 3 4 5'><Coordinate point='0 1 0 2 3 0 4 5 0 6 7 0 8 9 0 10 11 0 ' /></IndexedFaceSet>",
+ 0,
+ 0);
+
/* PolyhedralSurface */
do_x3d3_test(
"POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )",
"<IndexedFaceSet convex='false' coordIndex='0 1 2 3 -1 4 5 6 7 -1 8 9 10 11 -1 12 13 14 15 -1 16 17 18 19 -1 20 21 22 23'><Coordinate point='0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1 0 0 1 0 1 0 0 1 1 1 0 1 1 1 1 0 1 1 0 0 0 1 0 0 1 1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 1 0 1 1' /></IndexedFaceSet>",
0, 0);
- /* TODO: returns garbage at moment correctly implement GeometryCollection -- */
- /** do_x3d3_test(
- "GEOMETRYCOLLECTION(POINT(0 1 3),LINESTRING(2 3 3,4 5 3))",
- "",
- NULL, 0); **/
+ /* GeometryCollection with 2D Polygon */
+ do_x3d3_test(
+ "GEOMETRYCOLLECTION(POLYGON((0 0,1 0,1 1,0 0)))",
+ "<Shape><IndexedFaceSet convex='false' coordIndex='0 1 2'><Coordinate point='0 0 0 1 0 0 1 1 0 ' /></IndexedFaceSet></Shape>",
+ 0,
+ 0);
/* TODO: Implement Empty GeometryCollection correctly or throw a not-implemented */
/** do_x3d3_test(
diff --git a/liblwgeom/lwout_x3d.c b/liblwgeom/lwout_x3d.c
index b1a8d7be4..4737fd09e 100644
--- a/liblwgeom/lwout_x3d.c
+++ b/liblwgeom/lwout_x3d.c
@@ -19,6 +19,7 @@
**********************************************************************
*
* Copyright 2011-2017 Arrival 3D, Regina Obe
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
*
**********************************************************************/
@@ -117,13 +118,13 @@ asx3d3_point_sb(const LWPOINT *point,
stringbuffer_t *sb)
{
/** for point we just output the coordinates **/
- return ptarray_to_x3d3_sb(point->point, precision, opts, 0, sb);
+ return ptarray_to_x3d3_sb(point->point, precision, opts, 0, LW_FALSE, sb);
}
static int
-asx3d3_line_coords_sb(const LWLINE *line, int precision, int opts, stringbuffer_t *sb)
+asx3d3_line_coords_sb(const LWLINE *line, int precision, int opts, int force_3d, stringbuffer_t *sb)
{
- return ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed(line), sb);
+ return ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed(line), force_3d, sb);
}
/* Calculate the coordIndex property of the IndexedLineSet for the multilinestring
@@ -238,8 +239,7 @@ asx3d3_line_sb(const LWLINE *line,
else
stringbuffer_aprintf(sb, "<Coordinate point='");
- ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed((LWLINE *) line), sb);
-
+ ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed((LWLINE *)line), LW_TRUE, sb);
stringbuffer_aprintf(sb, "' />");
@@ -259,7 +259,7 @@ asx3d3_poly_sb(const LWPOLY *poly,
for (i=0; i<poly->nrings; i++)
{
if (i) stringbuffer_aprintf(sb, " "); /* inner ring points start */
- ptarray_to_x3d3_sb(poly->rings[i], precision, opts, 1, sb);
+ ptarray_to_x3d3_sb(poly->rings[i], precision, opts, 1, LW_TRUE, sb);
}
return LW_SUCCESS;
}
@@ -271,7 +271,7 @@ asx3d3_triangle_sb(const LWTRIANGLE *triangle,
__attribute__((__unused__)) const char *defid,
stringbuffer_t *sb)
{
- return ptarray_to_x3d3_sb(triangle->points, precision, opts, 1, sb);
+ return ptarray_to_x3d3_sb(triangle->points, precision, opts, 1, LW_TRUE, sb);
}
@@ -284,6 +284,7 @@ asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *de
char *x3dtype;
uint32_t i;
int dimension=2;
+ int has_coordinate_node = LW_FALSE;
if (FLAGS_GET_Z(col->flags)) dimension = 3;
LWGEOM *subgeom;
@@ -292,38 +293,49 @@ asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *de
switch (col->type)
{
- case MULTIPOINTTYPE:
- x3dtype = "PointSet";
- if ( dimension == 2 ){ /** Use Polypoint2D instead **/
- x3dtype = "Polypoint2D";
- stringbuffer_aprintf(sb, "<%s %s point='", x3dtype, defid);
- }
- else {
- stringbuffer_aprintf(sb, "<%s %s>", x3dtype, defid);
- }
- break;
- case MULTILINETYPE:
- x3dtype = "IndexedLineSet";
- stringbuffer_aprintf(sb, "<%s %s coordIndex='", x3dtype, defid);
- asx3d3_mline_coordindex_sb((const LWMLINE *)col, sb);
- stringbuffer_aprintf(sb, "'>");
- break;
- case MULTIPOLYGONTYPE:
- x3dtype = "IndexedFaceSet";
- stringbuffer_aprintf(sb, "<%s %s convex='false' coordIndex='", x3dtype, defid);
- asx3d3_mpoly_coordindex_sb((const LWMPOLY *)col, sb);
- stringbuffer_aprintf(sb, "'>");
- break;
- default:
- lwerror("asx3d3_multi_buf: '%s' geometry type not supported", lwtype_name(col->type));
- return 0;
- }
- if (dimension == 3){
- if ( X3D_USE_GEOCOORDS(opts) )
- stringbuffer_aprintf(sb, "<GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='", ((opts & LW_X3D_FLIP_XY) ? "latitude_first" : "longitude_first") );
+ case MULTIPOINTTYPE:
+ x3dtype = "PointSet";
+ if (dimension == 2)
+ { /** Use Polypoint2D instead **/
+ x3dtype = "Polypoint2D";
+ stringbuffer_aprintf(sb, "<%s %s point='", x3dtype, defid);
+ }
else
- stringbuffer_aprintf(sb, "<Coordinate point='");
- }
+ {
+ stringbuffer_aprintf(sb, "<%s %s>", x3dtype, defid);
+ has_coordinate_node = LW_TRUE;
+ }
+ break;
+ case MULTILINETYPE:
+ x3dtype = "IndexedLineSet";
+ stringbuffer_aprintf(sb, "<%s %s coordIndex='", x3dtype, defid);
+ asx3d3_mline_coordindex_sb((const LWMLINE *)col, sb);
+ stringbuffer_aprintf(sb, "'>");
+ has_coordinate_node = LW_TRUE;
+ break;
+ case MULTIPOLYGONTYPE:
+ x3dtype = "IndexedFaceSet";
+ stringbuffer_aprintf(sb, "<%s %s convex='false' coordIndex='", x3dtype, defid);
+ asx3d3_mpoly_coordindex_sb((const LWMPOLY *)col, sb);
+ stringbuffer_aprintf(sb, "'>");
+ has_coordinate_node = LW_TRUE;
+ break;
+ default:
+ lwerror("asx3d3_multi_buf: '%s' geometry type not supported", lwtype_name(col->type));
+ return 0;
+ }
+
+ /* Indexed X3D nodes always need a Coordinate child; only 2D multipoints
+ * can store coordinates directly on the Polypoint2D node. */
+ if (has_coordinate_node)
+ {
+ if (X3D_USE_GEOCOORDS(opts))
+ stringbuffer_aprintf(sb,
+ "<GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='",
+ ((opts & LW_X3D_FLIP_XY) ? "latitude_first" : "longitude_first"));
+ else
+ stringbuffer_aprintf(sb, "<Coordinate point='");
+ }
for (i=0; i<col->ngeoms; i++)
{
@@ -335,7 +347,7 @@ asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *de
}
else if (subgeom->type == LINETYPE)
{
- asx3d3_line_coords_sb((LWLINE*)subgeom, precision, opts, sb);
+ asx3d3_line_coords_sb((LWLINE *)subgeom, precision, opts, has_coordinate_node, sb);
stringbuffer_aprintf(sb, " ");
}
else if (subgeom->type == POLYGONTYPE)
@@ -346,7 +358,8 @@ asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *de
}
/* Close outmost tag */
- if (dimension == 3){
+ if (has_coordinate_node)
+ {
stringbuffer_aprintf(sb, "' /></%s>", x3dtype);
}
else { stringbuffer_aprintf(sb, "' />"); }
@@ -474,7 +487,9 @@ asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const cha
}
else if ( subgeom->type == POLYGONTYPE )
{
- asx3d3_poly_sb((LWPOLY *)subgeom, precision, opts, 0, defid, sb);
+ LWCOLLECTION *tmp = (LWCOLLECTION *)lwgeom_as_multi(subgeom);
+ asx3d3_multi_sb(tmp, precision, opts, defid, sb);
+ lwcollection_free(tmp);
}
else if ( subgeom->type == TINTYPE )
{
@@ -508,14 +523,14 @@ asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const cha
/** In X3D3, coordinates are separated by a space separator
*/
static int
-ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, stringbuffer_t *sb )
+ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, int force_3d, stringbuffer_t *sb)
{
uint32_t i;
char x[OUT_DOUBLE_BUFFER_SIZE];
char y[OUT_DOUBLE_BUFFER_SIZE];
char z[OUT_DOUBLE_BUFFER_SIZE];
- if ( ! FLAGS_GET_Z(pa->flags) )
+ if (!FLAGS_GET_Z(pa->flags) && !force_3d)
{
for (i=0; i<pa->npoints; i++)
{
@@ -537,6 +552,30 @@ ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, strin
}
}
}
+ else if (!FLAGS_GET_Z(pa->flags))
+ {
+ for (i = 0; i < pa->npoints; i++)
+ {
+ /** Only output the point if it is not the last point of a closed object or it is a non-closed
+ * type **/
+ if (!is_closed || i < (pa->npoints - 1))
+ {
+ POINT2D pt;
+ getPoint2d_p(pa, i, &pt);
+
+ lwprint_double(pt.x, precision, x);
+ lwprint_double(pt.y, precision, y);
+
+ if (i)
+ stringbuffer_append_len(sb, " ", 1);
+
+ if ((opts & LW_X3D_FLIP_XY))
+ stringbuffer_aprintf(sb, "%s %s 0", y, x);
+ else
+ stringbuffer_aprintf(sb, "%s %s 0", x, y);
+ }
+ }
+ }
else
{
for (i=0; i<pa->npoints; i++)
diff --git a/liblwgeom/lwout_x3d.h b/liblwgeom/lwout_x3d.h
index ca8c6db83..0efb67d95 100644
--- a/liblwgeom/lwout_x3d.h
+++ b/liblwgeom/lwout_x3d.h
@@ -19,6 +19,7 @@
**********************************************************************
*
* Copyright 2011-2017 Arrival 3D, Regina Obe
+ * Copyright 2026 Darafei Praliaskouski <me at komzpa.net>
*
**********************************************************************/
@@ -45,4 +46,4 @@ static int asx3d3_tin_sb(const LWTIN *tin, int precision, int opts, const char *
static int
asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb);
-static int ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, stringbuffer_t *sb );
+static int ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, int force_3d, stringbuffer_t *sb);
diff --git a/regress/core/tickets_expected b/regress/core/tickets_expected
index 93529c7b5..639472965 100644
--- a/regress/core/tickets_expected
+++ b/regress/core/tickets_expected
@@ -423,8 +423,8 @@ ERROR: BOX2D_construct: args can not be empty points
#4399|ST_AsTWKB|\x030001040000020201000001
#4399|ST_AsTWKB|\x070001030001040000020201000001
#4399|ST_AsTWKB|\x0310
-#4399|ST_AsX3D|0 0 1 1 0 1
-#4399|ST_AsX3D|<IndexedTriangleSet index='0 1 2'><Coordinate point='0 0 1 1 0 1'/></IndexedTriangleSet>
+#4399|ST_AsX3D|0 0 0 1 1 0 0 1 0
+#4399|ST_AsX3D|<IndexedTriangleSet index='0 1 2'><Coordinate point='0 0 0 1 1 0 0 1 0'/></IndexedTriangleSet>
#4399|ST_AsX3D|
#4399|ST_GeoHash|s00
#4399|ST_GeoHash|s00
-----------------------------------------------------------------------
Summary of changes:
NEWS | 2 +
liblwgeom/cunit/cu_out_x3d.c | 39 +++++++++++--
liblwgeom/lwout_x3d.c | 125 +++++++++++++++++++++++++++---------------
liblwgeom/lwout_x3d.h | 3 +-
regress/core/tickets_expected | 4 +-
5 files changed, 122 insertions(+), 51 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list