Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions geetools/ee_feature_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,3 +796,73 @@ 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 <https://developers.google.com/earth-engine/guides/geometries>`__

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.geetools.breakGeometries().geetools.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)

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)
42 changes: 34 additions & 8 deletions tests/test_FeatureCollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,37 @@ 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)


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"})])
100 changes: 100 additions & 0 deletions tests/test_FeatureCollection/serialized_test_break_geometries.yml
Original file line number Diff line number Diff line change
@@ -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
110 changes: 110 additions & 0 deletions tests/test_FeatureCollection/serialized_test_filter_geometry_type.yml
Original file line number Diff line number Diff line change
@@ -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__
20 changes: 20 additions & 0 deletions tests/test_FeatureCollection/test_break_geometries.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/test_FeatureCollection/test_filter_geometry_type.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
features:
- geometry:
coordinates:
- 0.0
- 0.0
type: Point
id: '0'
properties:
test: test
type: Feature
type: FeatureCollection