Skip to content

Commit 6038a99

Browse files
authored
feat: ee.Dictionary to ee.FeatureCollection (#372)
2 parents a55e959 + 3b8bcae commit 6038a99

File tree

5 files changed

+198
-0
lines changed

5 files changed

+198
-0
lines changed

geetools/ee_dictionary.py

+121
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Extra methods for the :py:class:`ee.Dictionary` class."""
22
from __future__ import annotations
33

4+
from typing import Literal
5+
46
import ee
57

68
from .accessors import register_class_accessor
@@ -84,3 +86,122 @@ def getMany(self, list: list | ee.List) -> ee.List:
8486
d.getInfo()
8587
"""
8688
return ee.List(list).map(lambda key: self._obj.get(key))
89+
90+
def toTable(
91+
self, valueType: Literal["dict", "list", "value"] = "value"
92+
) -> ee.FeatureCollection:
93+
"""Convert a :py:class:`ee.Dictionary` to a :py:class:`ee.FeatureCollection` with no geometries (table).
94+
95+
There are 3 different type of values handled by this method:
96+
97+
1. value (default): when values are a :py:class:`ee.String` or
98+
:py:class:`ee.Number`, the keys will be saved in the column
99+
``system:index`` and the values in the column "value".
100+
101+
2. dict: when values are a :py:class:`ee.Dictionary`, the keys will be
102+
saved in the column ``system:index`` and the values will be treated
103+
as each Feature's properties.
104+
105+
3. list: when values are a :py:class:`ee.List` of numbers or strings,
106+
the keys will be saved in the column ``system:index`` and the values
107+
in as many columns as items in the list. The column name pattern is
108+
"value_{i}" where i is the position of the element in the list.
109+
110+
These are the only supported patterns. Other patterns should be converted
111+
to one of these. For example, the values of a reduction using the
112+
reducer :py:meth:`ee.Reducer.frequencyHistogram` are of type
113+
:py:class:`ee.Array` and the array contains lists.
114+
115+
Parameters:
116+
valueType: this will define how to process the values.
117+
118+
Returns:
119+
a collection in which the keys of the :py:class:`ee.Dictionary` are
120+
in the ``system:index`` and the values are in new columns.
121+
122+
Examples:
123+
.. jupyter-execute::
124+
125+
import ee, geetools
126+
from geetools.utils import initialize_documentation
127+
128+
initialize_documentation()
129+
130+
d = ee.Dictionary({"foo": 1, "bar": 2})
131+
d.geetools.toTable().getInfo()
132+
133+
.. jupyter-execute::
134+
135+
import ee, geetools
136+
from geetools.utils import initialize_documentation
137+
138+
initialize_documentation()
139+
140+
d = ee.Dictionary({
141+
"Argentina": {"ADM0_CODE": 12, "Shape_Area": 278.289196625},
142+
"Armenia": {"ADM0_CODE": 13, "Shape_Area": 3.13783139285},
143+
})
144+
d.geetools.toTable('dict').getInfo()
145+
146+
.. jupyter-execute::
147+
148+
import ee, geetools
149+
from geetools.utils import initialize_documentation
150+
151+
initialize_documentation()
152+
153+
d = ee.Dictionary({
154+
"Argentina": [12, 278.289196625],
155+
"Armenia": [13, 3.13783139285],
156+
})
157+
d.geetools.toTable().getInfo()
158+
159+
.. jupyter-execute::
160+
161+
import ee, geetools
162+
from geetools.utils import initialize_documentation
163+
164+
initialize_documentation()
165+
166+
# reduction
167+
ran = ee.Image.random().multiply(10).reduceRegion(
168+
reducer=ee.Reducer.fixedHistogram(0, 1.1, 11),
169+
geometry=ee.Geometry.Point([0,0]).buffer(1000),
170+
scale=100
171+
)
172+
173+
# process to get desired format
174+
res = ee.Array(ee.Dictionary(ran).get('random'))
175+
reslist = res.toList()
176+
keys = reslist.map(lambda i: ee.Number(ee.List(i).get(0)).multiply(100).toInt().format())
177+
values = reslist.map(lambda i: ee.Number(ee.List(i).get(1)).toInt())
178+
final = ee.Dictionary.fromLists(keys, values)
179+
180+
# fetch
181+
final.geetools.toTable().getInfo()
182+
"""
183+
184+
def features_from_dict(key, value) -> ee.Feature:
185+
index = {"system:index": ee.String(key)}
186+
props = ee.Dictionary(value).combine(index)
187+
return ee.Feature(None, props)
188+
189+
def features_from_list(key, value) -> ee.Feature:
190+
index = {"system:index": ee.String(key)}
191+
values = ee.List(value)
192+
columns = ee.List.sequence(0, values.size().subtract(1))
193+
columns = columns.map(lambda k: ee.String("value_").cat(ee.Number(k).toInt()))
194+
props = ee.Dictionary.fromLists(columns, values).combine(index)
195+
return ee.Feature(None, props)
196+
197+
def features_from_any(key, value) -> ee.Feature:
198+
props = {"system:index": ee.String(key), "value": value}
199+
return ee.Feature(None, props)
200+
201+
make_features = {
202+
"list": features_from_list,
203+
"dict": features_from_dict,
204+
"value": features_from_any,
205+
}
206+
features = self._obj.map(make_features[valueType]).values()
207+
return ee.FeatureCollection(features)

tests/test_Dictionary.py

+26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Test the Dictionary class methods."""
22
import ee
33

4+
import geetools # noqa: F401
5+
46

57
class TestFromPairs:
68
"""Test the fromPairs method."""
@@ -28,3 +30,27 @@ class TestGetMany:
2830
def test_getMany(self):
2931
d = ee.Dictionary({"foo": 1, "bar": 2}).geetools.getMany(["foo"])
3032
assert d.getInfo() == [1]
33+
34+
35+
class TestToTable:
36+
"""Test the `toTable` method."""
37+
38+
def test_to_table_any(self, data_regression):
39+
ee_dict = ee.Dictionary({"foo": 1, "bar": 2})
40+
res = ee_dict.geetools.toTable()
41+
data_regression.check(res.getInfo())
42+
43+
def test_to_table_list(self, data_regression):
44+
ee_dict = ee.Dictionary({"Argentina": [12, 278.289196625], "Armenia": [13, 3.13783139285]})
45+
res = ee_dict.geetools.toTable("list")
46+
data_regression.check(res.getInfo())
47+
48+
def test_to_table_dict(self, data_regression):
49+
ee_dict = ee.Dictionary(
50+
{
51+
"Argentina": {"ADM0_CODE": 12, "Shape_Area": 278.289196625},
52+
"Armenia": {"ADM0_CODE": 13, "Shape_Area": 3.13783139285},
53+
}
54+
)
55+
res = ee_dict.geetools.toTable("dict")
56+
data_regression.check(res.getInfo())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
columns:
2+
system:index: String
3+
value: Integer
4+
features:
5+
- geometry: null
6+
id: bar
7+
properties:
8+
value: 2
9+
type: Feature
10+
- geometry: null
11+
id: foo
12+
properties:
13+
value: 1
14+
type: Feature
15+
type: FeatureCollection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
columns:
2+
ADM0_CODE: Integer
3+
Shape_Area: Float
4+
system:index: String
5+
features:
6+
- geometry: null
7+
id: Argentina
8+
properties:
9+
ADM0_CODE: 12
10+
Shape_Area: 278.289196625
11+
type: Feature
12+
- geometry: null
13+
id: Armenia
14+
properties:
15+
ADM0_CODE: 13
16+
Shape_Area: 3.13783139285
17+
type: Feature
18+
type: FeatureCollection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
columns:
2+
system:index: String
3+
value_0: Integer
4+
value_1: Float
5+
features:
6+
- geometry: null
7+
id: Argentina
8+
properties:
9+
value_0: 12
10+
value_1: 278.289196625
11+
type: Feature
12+
- geometry: null
13+
id: Armenia
14+
properties:
15+
value_0: 13
16+
value_1: 3.13783139285
17+
type: Feature
18+
type: FeatureCollection

0 commit comments

Comments
 (0)