[PostGIS] #6098: ST_Collect can turn valid POLYGONs into invalid MULTIPOLYGONs
PostGIS
trac at osgeo.org
Sat Jul 11 16:21:42 PDT 2026
#6098: ST_Collect can turn valid POLYGONs into invalid MULTIPOLYGONs
------------------------------------------------+--------------------------
Reporter: pnorman | Owner: pramsey
Type: defect | Status: new
Priority: medium | Milestone: PostGIS
| 3.6.5
Component: postgis | Version: 3.6.x
Keywords: collect valid multipolygon invalid |
------------------------------------------------+--------------------------
If you have two valid POLYGONs that touch each other running ST_Collect on
them can return an invalid MULTIPOLYGON.
ST_Collect appears to make the assumption that any set of geometries of a
homogeneous type can be turned into a Multi* of that type. This is true
for points and linestrings, but not true for multipolygons. In the case of
the former a Multi* adds no additional validity constraints but for the
latter constraints are added that none of the member POLYGONs of the
MULTIPOLYGON may touch.
This can be seen with the following queries
{{{
SELECT ST_IsValid(geom), ST_IsValidReason(geom), ST_Area(geom),
substring(st_astext(geom) FOR 40) FROM (SELECT geom geom FROM (VALUES
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267785.83997943
5212881.969248425,-9267744.930066563 5212353.6841178,-9267302.724521339
5212418.611175549,-9267290.724280229 5212414.921270518,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9266988.859217046 5213697.935206385,-9267005.278841939
5214068.187909914,-9267249.101922622 5214061.1834017765,-9267259.031621201
5214379.554829701,-9267016.98965237 5214383.652227536,-9267027.698587382
5214642.3040552875,-9267929.620233739 5214629.454150022,-9267991.458210874
5214629.258313381,-9268052.171861153 5214636.63985105,-9268099.404721096
5214645.6935408255,-9268145.46872639 5214657.534153168,-9268189.796147622
5214665.427902955,-9268312.715129355 5214658.52840373,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry),
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267744.930066563
5212353.6841178,-9267302.724521339 5212418.611175549,-9267290.724280229
5212414.921270518,-9266816.224950723 5212491.023970135,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9267005.278841939 5214068.187909914,-9267249.101922622
5214061.1834017765,-9267259.031621201 5214379.554829701,-9267016.98965237
5214383.652227536,-9267027.698587382 5214642.3040552875,-9267929.620233739
5214629.454150022,-9267991.458210874 5214629.258313381,-9268052.171861153
5214636.63985105,-9268099.404721096 5214645.6935408255,-9268145.46872639
5214657.534153168,-9268189.796147622 5214665.427902955,-9268312.715129355
5214658.52840373,-9268365.569623584 5214642.891566027,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry)
) AS t(geom)) AS q;
SELECT ST_IsValid(geom), ST_IsValidReason(geom), ST_Area(geom),
substring(st_astext(geom) FOR 40) FROM (SELECT ST_Collect(geom) geom FROM
(VALUES
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267785.83997943
5212881.969248425,-9267744.930066563 5212353.6841178,-9267302.724521339
5212418.611175549,-9267290.724280229 5212414.921270518,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9266988.859217046 5213697.935206385,-9267005.278841939
5214068.187909914,-9267249.101922622 5214061.1834017765,-9267259.031621201
5214379.554829701,-9267016.98965237 5214383.652227536,-9267027.698587382
5214642.3040552875,-9267929.620233739 5214629.454150022,-9267991.458210874
5214629.258313381,-9268052.171861153 5214636.63985105,-9268099.404721096
5214645.6935408255,-9268145.46872639 5214657.534153168,-9268189.796147622
5214665.427902955,-9268312.715129355 5214658.52840373,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry),
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267744.930066563
5212353.6841178,-9267302.724521339 5212418.611175549,-9267290.724280229
5212414.921270518,-9266816.224950723 5212491.023970135,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9267005.278841939 5214068.187909914,-9267249.101922622
5214061.1834017765,-9267259.031621201 5214379.554829701,-9267016.98965237
5214383.652227536,-9267027.698587382 5214642.3040552875,-9267929.620233739
5214629.454150022,-9267991.458210874 5214629.258313381,-9268052.171861153
5214636.63985105,-9268099.404721096 5214645.6935408255,-9268145.46872639
5214657.534153168,-9268189.796147622 5214665.427902955,-9268312.715129355
5214658.52840373,-9268365.569623584 5214642.891566027,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry)
) AS t(geom)) AS q;
}}}
{{{
st_isvalid | st_isvalidreason | st_area |
substring
------------+------------------+-------------------+------------------------------------------
t | Valid Geometry | 2935806.900413173 |
POLYGON((-9268999.088845689 5214595.9360
t | Valid Geometry | 2935811.528830427 |
POLYGON((-9268999.088845689 5214595.9360
(2 rows)
NOTICE: Self-intersection at or near point -9267016.9896523692
5214383.6522275358
st_isvalid | st_isvalidreason |
st_area | substring
------------+-------------------------------------------------------+-----------------+------------------------------------------
f | Self-intersection[-9267016.98965237 5214383.65222754] |
5871618.4292436 | MULTIPOLYGON(((-9268999.088845689 521459
(1 row)
}}}
This is not a "harmless" invalidity as
https://github.com/pnorman/spirit/issues/87 shows that with the right
geometries and subsequent processing with ST_SimplifyPreserveTopology and
ST_AsMVTGeom results in a null geometry
A work-around appears to be to wrap each ST_Collect which may contain
touching polygons with a ST_ForceCollection, turning the MULTIPOLYGON into
a GEOMETRYCOLLECTION
{{{
SELECT ST_IsValid(geom), ST_IsValidReason(geom), ST_Area(geom),
substring(st_astext(geom) FOR 40) FROM (SELECT
ST_ForceCollection(ST_Collect(geom)) geom FROM (VALUES
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267785.83997943
5212881.969248425,-9267744.930066563 5212353.6841178,-9267302.724521339
5212418.611175549,-9267290.724280229 5212414.921270518,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9266988.859217046 5213697.935206385,-9267005.278841939
5214068.187909914,-9267249.101922622 5214061.1834017765,-9267259.031621201
5214379.554829701,-9267016.98965237 5214383.652227536,-9267027.698587382
5214642.3040552875,-9267929.620233739 5214629.454150022,-9267991.458210874
5214629.258313381,-9268052.171861153 5214636.63985105,-9268099.404721096
5214645.6935408255,-9268145.46872639 5214657.534153168,-9268189.796147622
5214665.427902955,-9268312.715129355 5214658.52840373,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry),
('SRID=3857;POLYGON((-9268999.088845689
5214595.936014973,-9267812.06685146 5213205.196203408,-9267809.584426817
5213197.55972637,-9267809.7848019 5213191.263779518,-9267744.930066563
5212353.6841178,-9267302.724521339 5212418.611175549,-9267290.724280229
5212414.921270518,-9266816.224950723 5212491.023970135,-9266682.975520242
5212512.470794289,-9266579.793484228 5212522.109832203,-9266528.809157444
5212523.133980559,-9266547.277060967 5212567.036914927,-9266554.24566109
5212610.111689871,-9266659.954649547 5213035.237094378,-9266714.378748598
5213312.84557948,-9266702.979632739 5213313.583630357,-9266745.025004411
5213471.723654301,-9266767.077395536 5213559.146894767,-9266982.63645751
5213557.429752363,-9267005.278841939 5214068.187909914,-9267249.101922622
5214061.1834017765,-9267259.031621201 5214379.554829701,-9267016.98965237
5214383.652227536,-9267027.698587382 5214642.3040552875,-9267929.620233739
5214629.454150022,-9267991.458210874 5214629.258313381,-9268052.171861153
5214636.63985105,-9268099.404721096 5214645.6935408255,-9268145.46872639
5214657.534153168,-9268189.796147622 5214665.427902955,-9268312.715129355
5214658.52840373,-9268365.569623584 5214642.891566027,-9268415.529811053
5214628.49003122,-9268506.84518935 5214613.922810667,-9268986.108993063
5214604.718508099,-9268999.088845689 5214595.936014973))'::geometry)
) AS t(geom)) AS q;
}}}
{{{
st_isvalid | st_isvalidreason | st_area |
substring
------------+------------------+-----------------+------------------------------------------
t | Valid Geometry | 5871618.4292436 |
GEOMETRYCOLLECTION(POLYGON((-9268999.088
}}}
This technique is not satisfactory for a few reasons
1. There's still an invalid geometry appearing in an intermediate stage
2. No one will expect valid geometries to be invalid when collected
3. There is no single aggregate function to form a geometry collection
The reason for needing the multiple POLYGONs in one geometry is later on
the output is passed to ST_AsMVT. MVT multipolygon geometries are a
sequence of polygons encoded into a POLYGON geometry with multiple
exterior/interior rings and all the validity constraints are on the
individual exterior/interior sets in the sequence.
--
Ticket URL: <https://trac.osgeo.org/postgis/ticket/6098>
PostGIS <http://trac.osgeo.org/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.
More information about the postgis-tickets
mailing list