Skip to content

Commit

Permalink
feat: ee.Dictionary to ee.FeatureCollection (#372)
Browse files Browse the repository at this point in the history
  • Loading branch information
12rambau authored Jan 14, 2025
2 parents a55e959 + 3b8bcae commit 6038a99
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 0 deletions.
121 changes: 121 additions & 0 deletions geetools/ee_dictionary.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Extra methods for the :py:class:`ee.Dictionary` class."""
from __future__ import annotations

from typing import Literal

import ee

from .accessors import register_class_accessor
Expand Down Expand Up @@ -84,3 +86,122 @@ def getMany(self, list: list | ee.List) -> ee.List:
d.getInfo()
"""
return ee.List(list).map(lambda key: self._obj.get(key))

def toTable(
self, valueType: Literal["dict", "list", "value"] = "value"
) -> ee.FeatureCollection:
"""Convert a :py:class:`ee.Dictionary` to a :py:class:`ee.FeatureCollection` with no geometries (table).
There are 3 different type of values handled by this method:
1. value (default): when values are a :py:class:`ee.String` or
:py:class:`ee.Number`, the keys will be saved in the column
``system:index`` and the values in the column "value".
2. dict: when values are a :py:class:`ee.Dictionary`, the keys will be
saved in the column ``system:index`` and the values will be treated
as each Feature's properties.
3. list: when values are a :py:class:`ee.List` of numbers or strings,
the keys will be saved in the column ``system:index`` and the values
in as many columns as items in the list. The column name pattern is
"value_{i}" where i is the position of the element in the list.
These are the only supported patterns. Other patterns should be converted
to one of these. For example, the values of a reduction using the
reducer :py:meth:`ee.Reducer.frequencyHistogram` are of type
:py:class:`ee.Array` and the array contains lists.
Parameters:
valueType: this will define how to process the values.
Returns:
a collection in which the keys of the :py:class:`ee.Dictionary` are
in the ``system:index`` and the values are in new columns.
Examples:
.. jupyter-execute::
import ee, geetools
from geetools.utils import initialize_documentation
initialize_documentation()
d = ee.Dictionary({"foo": 1, "bar": 2})
d.geetools.toTable().getInfo()
.. jupyter-execute::
import ee, geetools
from geetools.utils import initialize_documentation
initialize_documentation()
d = ee.Dictionary({
"Argentina": {"ADM0_CODE": 12, "Shape_Area": 278.289196625},
"Armenia": {"ADM0_CODE": 13, "Shape_Area": 3.13783139285},
})
d.geetools.toTable('dict').getInfo()
.. jupyter-execute::
import ee, geetools
from geetools.utils import initialize_documentation
initialize_documentation()
d = ee.Dictionary({
"Argentina": [12, 278.289196625],
"Armenia": [13, 3.13783139285],
})
d.geetools.toTable().getInfo()
.. jupyter-execute::
import ee, geetools
from geetools.utils import initialize_documentation
initialize_documentation()
# reduction
ran = ee.Image.random().multiply(10).reduceRegion(
reducer=ee.Reducer.fixedHistogram(0, 1.1, 11),
geometry=ee.Geometry.Point([0,0]).buffer(1000),
scale=100
)
# process to get desired format
res = ee.Array(ee.Dictionary(ran).get('random'))
reslist = res.toList()
keys = reslist.map(lambda i: ee.Number(ee.List(i).get(0)).multiply(100).toInt().format())
values = reslist.map(lambda i: ee.Number(ee.List(i).get(1)).toInt())
final = ee.Dictionary.fromLists(keys, values)
# fetch
final.geetools.toTable().getInfo()
"""

def features_from_dict(key, value) -> ee.Feature:
index = {"system:index": ee.String(key)}
props = ee.Dictionary(value).combine(index)
return ee.Feature(None, props)

def features_from_list(key, value) -> ee.Feature:
index = {"system:index": ee.String(key)}
values = ee.List(value)
columns = ee.List.sequence(0, values.size().subtract(1))
columns = columns.map(lambda k: ee.String("value_").cat(ee.Number(k).toInt()))
props = ee.Dictionary.fromLists(columns, values).combine(index)
return ee.Feature(None, props)

def features_from_any(key, value) -> ee.Feature:
props = {"system:index": ee.String(key), "value": value}
return ee.Feature(None, props)

make_features = {
"list": features_from_list,
"dict": features_from_dict,
"value": features_from_any,
}
features = self._obj.map(make_features[valueType]).values()
return ee.FeatureCollection(features)
26 changes: 26 additions & 0 deletions tests/test_Dictionary.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Test the Dictionary class methods."""
import ee

import geetools # noqa: F401


class TestFromPairs:
"""Test the fromPairs method."""
Expand Down Expand Up @@ -28,3 +30,27 @@ class TestGetMany:
def test_getMany(self):
d = ee.Dictionary({"foo": 1, "bar": 2}).geetools.getMany(["foo"])
assert d.getInfo() == [1]


class TestToTable:
"""Test the `toTable` method."""

def test_to_table_any(self, data_regression):
ee_dict = ee.Dictionary({"foo": 1, "bar": 2})
res = ee_dict.geetools.toTable()
data_regression.check(res.getInfo())

def test_to_table_list(self, data_regression):
ee_dict = ee.Dictionary({"Argentina": [12, 278.289196625], "Armenia": [13, 3.13783139285]})
res = ee_dict.geetools.toTable("list")
data_regression.check(res.getInfo())

def test_to_table_dict(self, data_regression):
ee_dict = ee.Dictionary(
{
"Argentina": {"ADM0_CODE": 12, "Shape_Area": 278.289196625},
"Armenia": {"ADM0_CODE": 13, "Shape_Area": 3.13783139285},
}
)
res = ee_dict.geetools.toTable("dict")
data_regression.check(res.getInfo())
15 changes: 15 additions & 0 deletions tests/test_Dictionary/test_to_table_any.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
columns:
system:index: String
value: Integer
features:
- geometry: null
id: bar
properties:
value: 2
type: Feature
- geometry: null
id: foo
properties:
value: 1
type: Feature
type: FeatureCollection
18 changes: 18 additions & 0 deletions tests/test_Dictionary/test_to_table_dict.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
columns:
ADM0_CODE: Integer
Shape_Area: Float
system:index: String
features:
- geometry: null
id: Argentina
properties:
ADM0_CODE: 12
Shape_Area: 278.289196625
type: Feature
- geometry: null
id: Armenia
properties:
ADM0_CODE: 13
Shape_Area: 3.13783139285
type: Feature
type: FeatureCollection
18 changes: 18 additions & 0 deletions tests/test_Dictionary/test_to_table_list.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
columns:
system:index: String
value_0: Integer
value_1: Float
features:
- geometry: null
id: Argentina
properties:
value_0: 12
value_1: 278.289196625
type: Feature
- geometry: null
id: Armenia
properties:
value_0: 13
value_1: 3.13783139285
type: Feature
type: FeatureCollection

0 comments on commit 6038a99

Please sign in to comment.