Skip to content
Open
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
5 changes: 5 additions & 0 deletions geetools/ee_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,11 @@ def date_in_str(d: str | int | float | datetime | date) -> str:
d = datetime.fromtimestamp(int(d) / 1000).isoformat() + "Z"
return str(d) # if any other format is used, we will simply return it as a string

# early exit if kwargs is empty
if len(kwargs) == 0:
return self

# Convert the system properties to the correct format
if "system:time_start" in kwargs:
kwargs["system:time_start"] = date_in_str(kwargs.pop("system:time_start"))
if "system:time_end" in kwargs:
Expand Down
79 changes: 38 additions & 41 deletions geetools/ee_image_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,51 +1233,48 @@ def sortMany(
) -> ee.ImageCollection:
"""Sort an ImageCollection using more than 1 property.

The properties are set in the order of priority. The first property is the most important one,
in case of a tie, the second property is used to break the tie, and so on.

Warning:
This method will raise an error if the 2 parameter are not the same size.

Args:
properties: the list of properties to sort by.
ascending: the list of order. If not passed all properties will be sorted ascending

Examples:
.. jupyter-execute::

import ee, geetools
from geetools.utils import initialize_documentation

initialize_documentation()

# order the 500 first images of the NOAA forecast by forecasted date. in case of tie,
# order by creation date. We limit to 500 to not exceed GEE computation limits²
ic = ee.ImageCollection("NOAA/GFS0P25").limit(500)
icSorted = ic.geetools.sortMany(["forecast_time", "creation_time"])

# print the sorted list of images with forecast and creation time properties for the 10 first
icSorted = icSorted.limit(10)
info = icSorted.toList(icSorted.size()).map(lambda x: ee.Dictionary({
"forecast_time": ee.Date(ee.Image(x).get("forecast_time")).format(),
"creation_time": ee.Date(ee.Image(x).get("creation_time")).format()
}))
for item in info.getInfo():
print(f"Forecast Time: {item['forecast_time']}, Creation Time: {item['creation_time']}")
"""
properties = ee.List(properties)
asc = ee.List(ascending or properties.map(lambda p: True))
order_dict = ee.Dictionary.fromLists(properties.slice(0, asc.size()), asc)
# position order of each prop will be converted to string using this format
length = self._obj.size().toInt().format().length()
format = ee.String("%0").cat(length.format()).cat("d")
# suffix for temporal properties
pos_suffix = ee.String("_geetools_position")

def compute_position(prop, cum):
"""Add the order position of the property to each image."""
cum = ee.ImageCollection(cum)
order = ee.Algorithms.If(order_dict.get(prop, True), True, False)
sorted_values = self._obj.sort(prop, order).aggregate_array(prop).distinct()
position_name = ee.String(prop).cat(pos_suffix)

def add_position(img):
index = sorted_values.indexOf(img.get(prop))
return img.set(position_name, index.format(format))

return cum.map(add_position)

with_positions = ee.ImageCollection(properties.iterate(compute_position, self._obj))
# put temp properties in a list to further remove them
position_properties = properties.map(lambda p: ee.String(p).cat(pos_suffix))
final_order_property = "_geetools_sort_many_"

def compute_final_prop(img):
"""Join order position string of each property into a single number."""
img = ee.Image(img)
# values = img.toDictionary(position_properties).values() # this should work but doesn't
values = position_properties.map(lambda p: img.get(p))
return img.set(final_order_property, values.join(""))

with_order = with_positions.map(compute_final_prop)
# add final property to properties to remove
prop_to_remove = position_properties.add(final_order_property)
# sort using the final property and remove temp properties
sorted = with_order.sort(final_order_property)
sorted = sorted.map(lambda i: ee.Image(i.copyProperties(i, exclude=prop_to_remove)))
return sorted
# sanity checks
props = ee.List(properties)
asc = ee.List(ascending or props.map(lambda _: True))

# Compute the sort chain in reverse order so that the first key is the primary one and so on.
ic = self._obj
propertiesIndex = ee.List.sequence(0, props.size().subtract(1)).reverse()
ic = propertiesIndex.iterate(lambda i, c: ee.ImageCollection(c).sort(props.get(i), asc.get(i)), ic)

return ee.ImageCollection(ic)

def datesByBands(
self,
Expand Down
5 changes: 5 additions & 0 deletions tests/test_Asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,8 @@ def test_set_properties(self, gee_test_folder):
asset = ee.Asset(gee_test_folder) / "folder" / "image"
asset.setProperties(foo="bar")
assert ee.Image(asset.as_posix()).get("foo").getInfo() == "bar"

def test_set_properties_no_kwargs(self, gee_test_folder):
asset = ee.Asset(gee_test_folder) / "folder" / "image"
asset.setProperties()
assert ee.Image(asset.as_posix()).exists()
9 changes: 4 additions & 5 deletions tests/test_ImageCollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import ee
import numpy as np
import pytest
from ee.ee_exception import EEException
from jsonschema import validate
from matplotlib import pyplot as plt

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

def test_sort_many_missing_asc(self, l8_toa, ee_list_regression):
def test_sort_many_missing_asc(self, l8_toa):
l8_toa = l8_toa.map(self.adjust_cloud_cover)
prop1 = "CLOUD_COVER"
prop2 = "system:time_start"
process = l8_toa.geetools.sortMany([prop1, prop2], [False])
dates = process.aggregate_array(prop2).map(lambda milli: ee.Date(milli).format())
result = process.aggregate_array(prop1).zip(dates)
ee_list_regression.check(result)
with pytest.raises(EEException):
l8_toa.geetools.sortMany([prop1, prop2], [True]).getInfo()


class TestPlotDatesByBands:
Expand Down
Loading
Loading