From 944aefbd6db67b6c0e2878105ceda8cb7afe43aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=BCnzi?= Date: Thu, 3 Jul 2025 13:52:02 +0200 Subject: [PATCH 1/2] Allow Geometry Collection to be validated by turf-boolean-valid Issue: We want to validate geometry collections, and the booleanValid() function does not allow us to do so. Fix: We add a verification for the `GeometryCollection` type in the booleanValid() function, and ensure it can't recurse indefinitely over itself. --- packages/turf-boolean-valid/index.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/turf-boolean-valid/index.ts b/packages/turf-boolean-valid/index.ts index b61d79f3f9..e65829fc34 100644 --- a/packages/turf-boolean-valid/index.ts +++ b/packages/turf-boolean-valid/index.ts @@ -83,6 +83,25 @@ function booleanValid(feature: Feature | Geometry) { } } return true; + case "GeometryCollection": + if (!geom.geometries) { + return false; + } + + // To avoid situations where Geometry Collections nested within one another + // could lead to a situation where there is an infinite validation loop, + // we enforce the GeoJSON RFC recommendations + // (https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8) + // which states that we should not have Nested Geometry Collections + return ( + Array.isArray(geom.geometries) && + geom.geometries.every( + (geometry: any): geometry is Geometry => + geometry.type && + geometry.type !== "GeometryCollection" && + booleanValid(geometry) + ) + ); default: return false; } From aa7fe7553ca8ff01fb2d0ece9f3a24b8eb4dfa75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=BCnzi?= Date: Fri, 4 Jul 2025 15:16:42 +0200 Subject: [PATCH 2/2] Adding test files for geometry collection validation We are also modifying the type checking --- packages/turf-boolean-valid/index.ts | 14 +++++++--- packages/turf-boolean-valid/package.json | 3 ++- .../malformed-geometry-in-collection.geojson | 26 ++++++++++++++++++ .../nested-collection.geojson | 27 +++++++++++++++++++ .../no-type-in-nested-geometry.geojson | 25 +++++++++++++++++ .../non-geometry-in-collection.geojson | 26 ++++++++++++++++++ .../geometry-collection.geojson | 26 ++++++++++++++++++ 7 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 packages/turf-boolean-valid/test/false/GeometryCollection/malformed-geometry-in-collection.geojson create mode 100644 packages/turf-boolean-valid/test/false/GeometryCollection/nested-collection.geojson create mode 100644 packages/turf-boolean-valid/test/false/GeometryCollection/no-type-in-nested-geometry.geojson create mode 100644 packages/turf-boolean-valid/test/false/GeometryCollection/non-geometry-in-collection.geojson create mode 100644 packages/turf-boolean-valid/test/true/GeometryCollection/geometry-collection.geojson diff --git a/packages/turf-boolean-valid/index.ts b/packages/turf-boolean-valid/index.ts index e65829fc34..4587128db7 100644 --- a/packages/turf-boolean-valid/index.ts +++ b/packages/turf-boolean-valid/index.ts @@ -95,11 +95,17 @@ function booleanValid(feature: Feature | Geometry) { // which states that we should not have Nested Geometry Collections return ( Array.isArray(geom.geometries) && + geom.geometries.length > 0 && geom.geometries.every( - (geometry: any): geometry is Geometry => - geometry.type && - geometry.type !== "GeometryCollection" && - booleanValid(geometry) + (geometry: Geometry) => + [ + "Point", + "LineString", + "MultiLineString", + "MultiPoint", + "Polygon", + "MultiPolygon", + ].includes(geometry.type) && booleanValid(geometry) ) ); default: diff --git a/packages/turf-boolean-valid/package.json b/packages/turf-boolean-valid/package.json index f7d4b1612d..4fa9427aca 100644 --- a/packages/turf-boolean-valid/package.json +++ b/packages/turf-boolean-valid/package.json @@ -5,7 +5,8 @@ "author": "Turf Authors", "contributors": [ "Rowan Winsemius <@rowanwins>", - "Denis Carriere <@DenisCarriere>" + "Denis Carriere <@DenisCarriere>", + "Martin Künzi <@ltkum>" ], "license": "MIT", "bugs": { diff --git a/packages/turf-boolean-valid/test/false/GeometryCollection/malformed-geometry-in-collection.geojson b/packages/turf-boolean-valid/test/false/GeometryCollection/malformed-geometry-in-collection.geojson new file mode 100644 index 0000000000..879f4b2eba --- /dev/null +++ b/packages/turf-boolean-valid/test/false/GeometryCollection/malformed-geometry-in-collection.geojson @@ -0,0 +1,26 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Point", + "coordinates": [2.8113711933311403] + }, + { + "type": "LineString", + "coordinates": [ + [1.285400390625, 4.214943141390651], + [0.472412109375, 2.9649843693339677], + [1.900634765625, 2.1857489471296665] + ] + } + ] + } + } + ] +} diff --git a/packages/turf-boolean-valid/test/false/GeometryCollection/nested-collection.geojson b/packages/turf-boolean-valid/test/false/GeometryCollection/nested-collection.geojson new file mode 100644 index 0000000000..5dd36a107b --- /dev/null +++ b/packages/turf-boolean-valid/test/false/GeometryCollection/nested-collection.geojson @@ -0,0 +1,27 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "GeometryCollection", + "geometries": [ + { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Point", + "coordinates": [6.7575683593749996, 2.8113711933311403] + }, + { + "type": "Point", + "coordinates": [2.7575683593749996, 2.8113711933311403] + } + ] + } + ] + } + } + ] +} diff --git a/packages/turf-boolean-valid/test/false/GeometryCollection/no-type-in-nested-geometry.geojson b/packages/turf-boolean-valid/test/false/GeometryCollection/no-type-in-nested-geometry.geojson new file mode 100644 index 0000000000..b133619de8 --- /dev/null +++ b/packages/turf-boolean-valid/test/false/GeometryCollection/no-type-in-nested-geometry.geojson @@ -0,0 +1,25 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "GeometryCollection", + "geometries": [ + { + "coordinates": [6.7575683593749996, 2.8113711933311403] + }, + { + "type": "LineString", + "coordinates": [ + [1.285400390625, 4.214943141390651], + [0.472412109375, 2.9649843693339677], + [1.900634765625, 2.1857489471296665] + ] + } + ] + } + } + ] +} diff --git a/packages/turf-boolean-valid/test/false/GeometryCollection/non-geometry-in-collection.geojson b/packages/turf-boolean-valid/test/false/GeometryCollection/non-geometry-in-collection.geojson new file mode 100644 index 0000000000..c055410626 --- /dev/null +++ b/packages/turf-boolean-valid/test/false/GeometryCollection/non-geometry-in-collection.geojson @@ -0,0 +1,26 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [2.7575683593749996, 2.8113711933311403] + } + }, + { + "type": "Point", + "coordinates": [2.7575683593749996, 2.8113711933311403] + } + ] + } + } + ] +} diff --git a/packages/turf-boolean-valid/test/true/GeometryCollection/geometry-collection.geojson b/packages/turf-boolean-valid/test/true/GeometryCollection/geometry-collection.geojson new file mode 100644 index 0000000000..942a96f0a0 --- /dev/null +++ b/packages/turf-boolean-valid/test/true/GeometryCollection/geometry-collection.geojson @@ -0,0 +1,26 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "GeometryCollection", + "geometries": [ + { + "type": "Point", + "coordinates": [6.7575683593749996, 2.8113711933311403] + }, + { + "type": "LineString", + "coordinates": [ + [1.285400390625, 4.214943141390651], + [0.472412109375, 2.9649843693339677], + [1.900634765625, 2.1857489471296665] + ] + } + ] + } + } + ] +}