From 17ef30531af7839d95f540003968e81b3e97e9c0 Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:20:50 +0000 Subject: [PATCH 1/3] docs: typo in example --- geetools/ee_feature_collection.py | 36 ++++++ tests/test_FeatureCollection.py | 27 +++-- .../serialized_test_filter_geometry_type.yml | 110 ++++++++++++++++++ .../test_filter_geometry_type.yml | 11 ++ 4 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 tests/test_FeatureCollection/serialized_test_filter_geometry_type.yml create mode 100644 tests/test_FeatureCollection/test_filter_geometry_type.yml diff --git a/geetools/ee_feature_collection.py b/geetools/ee_feature_collection.py index c4f58551..f91011bf 100644 --- a/geetools/ee_feature_collection.py +++ b/geetools/ee_feature_collection.py @@ -796,3 +796,39 @@ def areaSort(self, ascending: bool = True) -> ee.FeatureCollection: # sort by area and remove the property from the output properties = fc.first().propertyNames().remove(name) return fc.sort(name, ascending).map(lambda feat: feat.select(properties)) + + def filterGeometryType(self, type_: str | ee.String) -> ee.FeatureCollection: + """Filter the collection by geometry type. + + Only keep in the Final featureCollection the Feature with the specified geometry type. + Can be combined with ``breakGeometries`` to filter out multi geometries. + + Args: + type_: The geometry type to filter on. Must be one of the `GEE compatible types `__ + + Returns: + The filtered collection + + Examples: + .. jupyter-execute:: + + import ee, geetools + from geetools.utils import initialize_documentation + + initialize_documentation() + + geoms = [ee.Geometry.Point([0, 0]), ee.Geometry.Point([0, 0]).buffer(1)] + fc = ee.FeatureCollection([ee.Feature(g, {"test": "test"}) for g in geoms]) + fc = fc.ldc.breakGeometries().ldc.filterGeometryType("Point") + fc.aggregate_array("test").getInfo() + """ + # extract the properties of the features before filtering + # and create an extra column with the geometry type + type_, name = ee.String(type_), "__geetools_type__" + properties = self._obj.first().propertyNames() + fc = self._obj.map(lambda feat: feat.set(name, feat.geometry().type())) + + # filter the collection and remove the extra column + fc = fc.filter(ee.Filter.eq(name, type_)).select(properties) + + return ee.FeatureCollection(fc) diff --git a/tests/test_FeatureCollection.py b/tests/test_FeatureCollection.py index 28b7bfa8..f52ae745 100644 --- a/tests/test_FeatureCollection.py +++ b/tests/test_FeatureCollection.py @@ -363,11 +363,22 @@ def gdfZ(self): } return gpd.GeoDataFrame.from_features(data["features"]) - class TestAreaSort: - """Test the ``areaSort`` method.""" - - def test_area_sort(self, ee_list_regression): - fc = ee.FeatureCollection("FAO/GAUL/2015/level0").limit(5) - fc = fc.geetools.areaSort() - property = fc.aggregate_array("ADM0_NAME") - ee_list_regression.check(property) + +class TestAreaSort: + """Test the ``areaSort`` method.""" + + def test_area_sort(self, ee_list_regression): + fc = ee.FeatureCollection("FAO/GAUL/2015/level0").limit(5) + fc = fc.geetools.areaSort() + property = fc.aggregate_array("ADM0_NAME") + ee_list_regression.check(property) + + +class TestFilterGeometryType: + """Test the ``filterGeometryType`` method.""" + + def test_filter_geometry_type(self, ee_feature_collection_regression): + geometries = [ee.Geometry.Point([0, 0]), ee.Geometry.Point([0, 0]).buffer(1)] + fc = ee.FeatureCollection([ee.Feature(g, {"test": "test"}) for g in geometries]) + fc = fc.geetools.filterGeometryType("Point") + ee_feature_collection_regression.check(fc) diff --git a/tests/test_FeatureCollection/serialized_test_filter_geometry_type.yml b/tests/test_FeatureCollection/serialized_test_filter_geometry_type.yml new file mode 100644 index 00000000..5ea0beab --- /dev/null +++ b/tests/test_FeatureCollection/serialized_test_filter_geometry_type.yml @@ -0,0 +1,110 @@ +result: '0' +values: + '0': + functionInvocationValue: + arguments: + baseAlgorithm: + functionDefinitionValue: + argumentNames: + - _MAPPING_VAR_0_0 + body: '1' + collection: + functionInvocationValue: + arguments: + collection: + functionInvocationValue: + arguments: + baseAlgorithm: + functionDefinitionValue: + argumentNames: + - _MAPPING_VAR_0_0 + body: '5' + collection: + valueReference: '2' + functionName: Collection.map + filter: + functionInvocationValue: + arguments: + leftField: + valueReference: '6' + rightValue: + constantValue: Point + functionName: Filter.equals + functionName: Collection.filter + functionName: Collection.map + '1': + functionInvocationValue: + arguments: + input: + argumentReference: _MAPPING_VAR_0_0 + propertySelectors: + functionInvocationValue: + arguments: + element: + functionInvocationValue: + arguments: + collection: + valueReference: '2' + functionName: Collection.first + functionName: Element.propertyNames + retainGeometry: + constantValue: true + functionName: Feature.select + '2': + functionInvocationValue: + arguments: + features: + arrayValue: + values: + - functionInvocationValue: + arguments: + geometry: + valueReference: '3' + metadata: + valueReference: '4' + functionName: Feature + - functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + distance: + constantValue: 1 + geometry: + valueReference: '3' + functionName: Geometry.buffer + metadata: + valueReference: '4' + functionName: Feature + functionName: Collection + '3': + functionInvocationValue: + arguments: + coordinates: + constantValue: + - 0 + - 0 + functionName: GeometryConstructors.Point + '4': + constantValue: + test: test + '5': + functionInvocationValue: + arguments: + key: + valueReference: '6' + object: + argumentReference: _MAPPING_VAR_0_0 + value: + functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + feature: + argumentReference: _MAPPING_VAR_0_0 + functionName: Feature.geometry + functionName: Geometry.type + functionName: Element.set + '6': + constantValue: __geetools_type__ diff --git a/tests/test_FeatureCollection/test_filter_geometry_type.yml b/tests/test_FeatureCollection/test_filter_geometry_type.yml new file mode 100644 index 00000000..c5eebfba --- /dev/null +++ b/tests/test_FeatureCollection/test_filter_geometry_type.yml @@ -0,0 +1,11 @@ +features: +- geometry: + coordinates: + - 0.0 + - 0.0 + type: Point + id: '0' + properties: + test: test + type: Feature +type: FeatureCollection From 0547eee8405518d1c8edfc8add30eeddd6b82462 Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:32:49 +0000 Subject: [PATCH 2/3] docs: fix the docstring --- geetools/ee_feature_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geetools/ee_feature_collection.py b/geetools/ee_feature_collection.py index f91011bf..99413a70 100644 --- a/geetools/ee_feature_collection.py +++ b/geetools/ee_feature_collection.py @@ -819,7 +819,7 @@ def filterGeometryType(self, type_: str | ee.String) -> ee.FeatureCollection: geoms = [ee.Geometry.Point([0, 0]), ee.Geometry.Point([0, 0]).buffer(1)] fc = ee.FeatureCollection([ee.Feature(g, {"test": "test"}) for g in geoms]) - fc = fc.ldc.breakGeometries().ldc.filterGeometryType("Point") + fc = fc.geetools.breakGeometries().geetools.filterGeometryType("Point") fc.aggregate_array("test").getInfo() """ # extract the properties of the features before filtering From e143d7720a22a274e4e2086bd92082a7ca6e195a Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:49:30 +0000 Subject: [PATCH 3/3] fix: add the missing breakFeature --- geetools/ee_feature_collection.py | 34 ++++++ tests/test_FeatureCollection.py | 15 +++ .../serialized_test_break_geometries.yml | 100 ++++++++++++++++++ .../test_break_geometries.yml | 20 ++++ 4 files changed, 169 insertions(+) create mode 100644 tests/test_FeatureCollection/serialized_test_break_geometries.yml create mode 100644 tests/test_FeatureCollection/test_break_geometries.yml diff --git a/geetools/ee_feature_collection.py b/geetools/ee_feature_collection.py index 99413a70..1338f06d 100644 --- a/geetools/ee_feature_collection.py +++ b/geetools/ee_feature_collection.py @@ -832,3 +832,37 @@ def filterGeometryType(self, type_: str | ee.String) -> ee.FeatureCollection: fc = fc.filter(ee.Filter.eq(name, type_)).select(properties) return ee.FeatureCollection(fc) + + def breakGeometries(self) -> ee.FeatureCollection: + """Break every feature using geometries into it's constituents. + + Each Geometry that is using a multi parts geometry type will be duplicated + into multiple features with each one carrying one of the constituent of the multiPolygon. + + Returns: + ee.FeatureCollection: The collection with the broken down geometries + + Examples: + .. code-block:: python + + import ee, geetools + from geetools.utils import initialize_documentation + + initialize_documentation() + + multipoint = ee.Geometry.MultiPoint([ee.Geometry.Point([0, 0]), ee.Geometry.Point([1, 0])]) + fc = ee.FeatureCollection([ee.Feature(multipoint, {"test": "test"})]) + fc = fc.geetools.breakGeometries() + fc.aggregate_array("test").getInfo() + """ + # helper function to break the geometry of a feature + def split(feat): + feat = ee.Feature(feat) + geometries = feat.geometry().geometries() + return geometries.map(lambda g: ee.Feature(ee.Geometry(g), feat.toDictionary())) + + # apply the function to the collection and flatten the list as each feature + # can have multiple geometries (thus list of list) and we want a single list + list = self._obj.toList(self._obj.size()).map(split).flatten() + + return ee.FeatureCollection(list) diff --git a/tests/test_FeatureCollection.py b/tests/test_FeatureCollection.py index f52ae745..fd9903f5 100644 --- a/tests/test_FeatureCollection.py +++ b/tests/test_FeatureCollection.py @@ -382,3 +382,18 @@ def test_filter_geometry_type(self, ee_feature_collection_regression): fc = ee.FeatureCollection([ee.Feature(g, {"test": "test"}) for g in geometries]) fc = fc.geetools.filterGeometryType("Point") ee_feature_collection_regression.check(fc) + + +class TestBreakGeometries: + """Test the ``breakGeometries`` method.""" + + def test_break_geometries(self, fc, ee_feature_collection_regression): + fc = fc.geetools.breakGeometries() + ee_feature_collection_regression.check(fc) + + @pytest.fixture + def fc(self): + point0 = ee.Geometry.Point([0, 0]) + point1 = ee.Geometry.Point([1, 0]) + multipoint = ee.Geometry.MultiPoint([point0, point1]) + return ee.FeatureCollection([ee.Feature(multipoint, {"test": "test"})]) diff --git a/tests/test_FeatureCollection/serialized_test_break_geometries.yml b/tests/test_FeatureCollection/serialized_test_break_geometries.yml new file mode 100644 index 00000000..fc8f2057 --- /dev/null +++ b/tests/test_FeatureCollection/serialized_test_break_geometries.yml @@ -0,0 +1,100 @@ +result: '0' +values: + '0': + functionInvocationValue: + arguments: + features: + functionInvocationValue: + arguments: + list: + functionInvocationValue: + arguments: + baseAlgorithm: + functionDefinitionValue: + argumentNames: + - _MAPPING_VAR_1_0 + body: '1' + dropNulls: + constantValue: false + list: + functionInvocationValue: + arguments: + collection: + valueReference: '3' + count: + functionInvocationValue: + arguments: + collection: + valueReference: '3' + functionName: Collection.size + functionName: Collection.toList + functionName: List.map + functionName: List.flatten + functionName: Collection + '1': + functionInvocationValue: + arguments: + baseAlgorithm: + functionDefinitionValue: + argumentNames: + - _MAPPING_VAR_0_0 + body: '2' + dropNulls: + constantValue: false + list: + functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + feature: + argumentReference: _MAPPING_VAR_1_0 + functionName: Feature.geometry + functionName: Geometry.geometries + functionName: List.map + '2': + functionInvocationValue: + arguments: + geometry: + argumentReference: _MAPPING_VAR_0_0 + metadata: + functionInvocationValue: + arguments: + element: + argumentReference: _MAPPING_VAR_1_0 + functionName: Element.toDictionary + functionName: Feature + '3': + functionInvocationValue: + arguments: + features: + arrayValue: + values: + - functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + coordinates: + arrayValue: + values: + - functionInvocationValue: + arguments: + coordinates: + constantValue: + - 0 + - 0 + functionName: GeometryConstructors.Point + - functionInvocationValue: + arguments: + coordinates: + constantValue: + - 1 + - 0 + functionName: GeometryConstructors.Point + functionName: GeometryConstructors.MultiPoint + metadata: + constantValue: + test: test + functionName: Feature + functionName: Collection diff --git a/tests/test_FeatureCollection/test_break_geometries.yml b/tests/test_FeatureCollection/test_break_geometries.yml new file mode 100644 index 00000000..61f921d5 --- /dev/null +++ b/tests/test_FeatureCollection/test_break_geometries.yml @@ -0,0 +1,20 @@ +features: +- geometry: + coordinates: + - 0.0 + - 0.0 + type: Point + id: '0' + properties: + test: test + type: Feature +- geometry: + coordinates: + - 1.0 + - 0.0 + type: Point + id: '1' + properties: + test: test + type: Feature +type: FeatureCollection