Skip to content

Commit 59439db

Browse files
authored
refactor: Better sort (#464)
2 parents 4e7f829 + c2a4d91 commit 59439db

8 files changed

Lines changed: 532 additions & 2057 deletions

geetools/ee_image_collection.py

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,51 +1233,48 @@ def sortMany(
12331233
) -> ee.ImageCollection:
12341234
"""Sort an ImageCollection using more than 1 property.
12351235
1236+
The properties are set in the order of priority. The first property is the most important one,
1237+
in case of a tie, the second property is used to break the tie, and so on.
1238+
1239+
Warning:
1240+
This method will raise an error if the 2 parameter are not the same size.
1241+
12361242
Args:
12371243
properties: the list of properties to sort by.
12381244
ascending: the list of order. If not passed all properties will be sorted ascending
1245+
1246+
Examples:
1247+
.. jupyter-execute::
1248+
1249+
import ee, geetools
1250+
from geetools.utils import initialize_documentation
1251+
1252+
initialize_documentation()
1253+
1254+
# order the 500 first images of the NOAA forecast by forecasted date. in case of tie,
1255+
# order by creation date. We limit to 500 to not exceed GEE computation limits²
1256+
ic = ee.ImageCollection("NOAA/GFS0P25").limit(500)
1257+
icSorted = ic.geetools.sortMany(["forecast_time", "creation_time"])
1258+
1259+
# print the sorted list of images with forecast and creation time properties for the 10 first
1260+
icSorted = icSorted.limit(10)
1261+
info = icSorted.toList(icSorted.size()).map(lambda x: ee.Dictionary({
1262+
"forecast_time": ee.Date(ee.Image(x).get("forecast_time")).format(),
1263+
"creation_time": ee.Date(ee.Image(x).get("creation_time")).format()
1264+
}))
1265+
for item in info.getInfo():
1266+
print(f"Forecast Time: {item['forecast_time']}, Creation Time: {item['creation_time']}")
12391267
"""
1240-
properties = ee.List(properties)
1241-
asc = ee.List(ascending or properties.map(lambda p: True))
1242-
order_dict = ee.Dictionary.fromLists(properties.slice(0, asc.size()), asc)
1243-
# position order of each prop will be converted to string using this format
1244-
length = self._obj.size().toInt().format().length()
1245-
format = ee.String("%0").cat(length.format()).cat("d")
1246-
# suffix for temporal properties
1247-
pos_suffix = ee.String("_geetools_position")
1248-
1249-
def compute_position(prop, cum):
1250-
"""Add the order position of the property to each image."""
1251-
cum = ee.ImageCollection(cum)
1252-
order = ee.Algorithms.If(order_dict.get(prop, True), True, False)
1253-
sorted_values = self._obj.sort(prop, order).aggregate_array(prop).distinct()
1254-
position_name = ee.String(prop).cat(pos_suffix)
1255-
1256-
def add_position(img):
1257-
index = sorted_values.indexOf(img.get(prop))
1258-
return img.set(position_name, index.format(format))
1259-
1260-
return cum.map(add_position)
1261-
1262-
with_positions = ee.ImageCollection(properties.iterate(compute_position, self._obj))
1263-
# put temp properties in a list to further remove them
1264-
position_properties = properties.map(lambda p: ee.String(p).cat(pos_suffix))
1265-
final_order_property = "_geetools_sort_many_"
1266-
1267-
def compute_final_prop(img):
1268-
"""Join order position string of each property into a single number."""
1269-
img = ee.Image(img)
1270-
# values = img.toDictionary(position_properties).values() # this should work but doesn't
1271-
values = position_properties.map(lambda p: img.get(p))
1272-
return img.set(final_order_property, values.join(""))
1273-
1274-
with_order = with_positions.map(compute_final_prop)
1275-
# add final property to properties to remove
1276-
prop_to_remove = position_properties.add(final_order_property)
1277-
# sort using the final property and remove temp properties
1278-
sorted = with_order.sort(final_order_property)
1279-
sorted = sorted.map(lambda i: ee.Image(i.copyProperties(i, exclude=prop_to_remove)))
1280-
return sorted
1268+
# sanity checks
1269+
props = ee.List(properties)
1270+
asc = ee.List(ascending or props.map(lambda _: True))
1271+
1272+
# Compute the sort chain in reverse order so that the first key is the primary one and so on.
1273+
ic = self._obj
1274+
propertiesIndex = ee.List.sequence(0, props.size().subtract(1)).reverse()
1275+
ic = propertiesIndex.iterate(lambda i, c: ee.ImageCollection(c).sort(props.get(i), asc.get(i)), ic)
1276+
1277+
return ee.ImageCollection(ic)
12811278

12821279
def datesByBands(
12831280
self,

tests/test_ImageCollection.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import ee
77
import numpy as np
88
import pytest
9+
from ee.ee_exception import EEException
910
from jsonschema import validate
1011
from matplotlib import pyplot as plt
1112

@@ -518,14 +519,12 @@ def test_sort_many_default(self, l8_toa, ee_list_regression):
518519
result = process.aggregate_array(prop1).zip(dates)
519520
ee_list_regression.check(result)
520521

521-
def test_sort_many_missing_asc(self, l8_toa, ee_list_regression):
522+
def test_sort_many_missing_asc(self, l8_toa):
522523
l8_toa = l8_toa.map(self.adjust_cloud_cover)
523524
prop1 = "CLOUD_COVER"
524525
prop2 = "system:time_start"
525-
process = l8_toa.geetools.sortMany([prop1, prop2], [False])
526-
dates = process.aggregate_array(prop2).map(lambda milli: ee.Date(milli).format())
527-
result = process.aggregate_array(prop1).zip(dates)
528-
ee_list_regression.check(result)
526+
with pytest.raises(EEException):
527+
l8_toa.geetools.sortMany([prop1, prop2], [True]).getInfo()
529528

530529

531530
class TestPlotDatesByBands:

0 commit comments

Comments
 (0)