diff --git a/geetools/ee_image.py b/geetools/ee_image.py index acda2c32..26c67b8e 100644 --- a/geetools/ee_image.py +++ b/geetools/ee_image.py @@ -22,7 +22,7 @@ from xee.ext import REQUEST_BYTE_LIMIT from .accessors import register_class_accessor -from .utils import format_class_info, plot_data +from .utils import area_units_to_m2, format_class_info, plot_data @register_class_accessor(ee.Image, "geetools") @@ -2305,3 +2305,34 @@ def plot_hist( ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left") return ax + + @classmethod + def pixelArea(cls, area_unit: str = "m2", rename_to_units: bool = False) -> ee.Image: + """Extend the :py:method:`ee.Image.pixelArea` method by setting the unit of the output. + + Args: + area_unit: the unit of the output area. can be one of "m2", "ha", "kha", "km2" or "acres". + rename_to_units: if ``True``, the output image will be renamed to the given unit. + + Returns: + the area ``ee.Image`` using the given unit. + + Examples: + .. jupyter-execute:: + + import ee, geetools + from geetools.utils import initialize_documentation + + initialize_documentation() + + hectares = ee.Image.geetools.pixelArea("ha").rename("ha") + acres = ee.Image.geetools.pixelArea("acres").rename("acres") + total = hectares.addBands(acres) + + buffer = ee.Geometry.Point(0,0).buffer(100) + values = total.reduceRegion(ee.Reducer.mean(), buffer, 1) + values.getInfo() + """ + name = area_unit if rename_to_units is True else "area" + divisor = area_units_to_m2(area_unit) + return ee.Image.pixelArea().divide(divisor).rename(name) diff --git a/geetools/utils.py b/geetools/utils.py index f9829361..deadfd47 100644 --- a/geetools/utils.py +++ b/geetools/utils.py @@ -282,3 +282,26 @@ def format_class_info(class_info: dict) -> dict: # make sure class value is an int, but store as str final[str(int(class_value))] = format_bandname(class_name) return final + + +def area_units_to_m2(area_units: str) -> float: + """Convert area units to m2. + + Args: + area_units (str): Area units to convert to m2. + + Returns: + float: The conversion factor. + """ + areas = { + "m2": 1, + "ha": 1e4, + "km2": 1e6, + "kha": 1e7, + "acres": 4046.86, + } + + if area_units not in areas: + raise ValueError(f"Area units '{area_units}' not supported. Use one of {list(areas.keys())}") + + return areas.get(area_units, 1) diff --git a/tests/test_Image.py b/tests/test_Image.py index 9219831c..30d696e8 100644 --- a/tests/test_Image.py +++ b/tests/test_Image.py @@ -946,3 +946,34 @@ def image(self): @property def region(self): return ee.Geometry.Rectangle([-112.60, 40.60, -111.18, 41.22]) + + +class TestPixelArea: + """Test the ``pixelArea`` method.""" + + def test_pixel_area_default(self): + surface = ee.Image.pixelArea().reduceRegion(ee.Reducer.mean(), self.buffer, 1) + ldcSurface = ee.Image.geetools.pixelArea().reduceRegion(ee.Reducer.mean(), self.buffer, 1) + isClose = ee.Number(surface.get("area")).subtract(ldcSurface.get("area")).abs().lte(1e-6) + assert bool(isClose.getInfo()) is True + + def test_pixel_area(self, ee_dictionary_regression): + hectares = ee.Image.geetools.pixelArea("ha") + total = ee.Image.geetools.pixelArea("acres").addBands(hectares) + dict = total.reduceRegion(ee.Reducer.mean(), self.buffer, 1) + ee_dictionary_regression.check(dict) + + def test_pixel_area_rename(self, ee_dictionary_regression): + hectares = ee.Image.geetools.pixelArea("ha", True) + acres = ee.Image.geetools.pixelArea("acres", True) + total = hectares.addBands(acres) + dict = total.reduceRegion(ee.Reducer.mean(), self.buffer, 1) + ee_dictionary_regression.check(dict) + + def test_pixel_area_error(self): + with pytest.raises(ValueError): + ee.Image.geetools.pixelArea("not_a_unit") + + @property + def buffer(self) -> ee.Geometry: + return ee.Geometry.Point(0, 0).buffer(100) diff --git a/tests/test_Image/serialized_test_pixel_area.yml b/tests/test_Image/serialized_test_pixel_area.yml new file mode 100644 index 00000000..42c00dc2 --- /dev/null +++ b/tests/test_Image/serialized_test_pixel_area.yml @@ -0,0 +1,73 @@ +result: '0' +values: + '0': + functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + distance: + constantValue: 100 + geometry: + functionInvocationValue: + arguments: + coordinates: + constantValue: + - 0 + - 0 + functionName: GeometryConstructors.Point + functionName: Geometry.buffer + image: + functionInvocationValue: + arguments: + dstImg: + functionInvocationValue: + arguments: + input: + functionInvocationValue: + arguments: + image1: + valueReference: '1' + image2: + functionInvocationValue: + arguments: + value: + constantValue: 4046.86 + functionName: Image.constant + functionName: Image.divide + names: + valueReference: '2' + functionName: Image.rename + srcImg: + functionInvocationValue: + arguments: + input: + functionInvocationValue: + arguments: + image1: + valueReference: '1' + image2: + functionInvocationValue: + arguments: + value: + constantValue: 10000.0 + functionName: Image.constant + functionName: Image.divide + names: + valueReference: '2' + functionName: Image.rename + functionName: Image.addBands + reducer: + functionInvocationValue: + arguments: {} + functionName: Reducer.mean + scale: + constantValue: 1 + functionName: Image.reduceRegion + '1': + functionInvocationValue: + arguments: {} + functionName: Image.pixelArea + '2': + constantValue: + - area diff --git a/tests/test_Image/serialized_test_pixel_area_rename.yml b/tests/test_Image/serialized_test_pixel_area_rename.yml new file mode 100644 index 00000000..32e0fbac --- /dev/null +++ b/tests/test_Image/serialized_test_pixel_area_rename.yml @@ -0,0 +1,72 @@ +result: '0' +values: + '0': + functionInvocationValue: + arguments: + geometry: + functionInvocationValue: + arguments: + distance: + constantValue: 100 + geometry: + functionInvocationValue: + arguments: + coordinates: + constantValue: + - 0 + - 0 + functionName: GeometryConstructors.Point + functionName: Geometry.buffer + image: + functionInvocationValue: + arguments: + dstImg: + functionInvocationValue: + arguments: + input: + functionInvocationValue: + arguments: + image1: + valueReference: '1' + image2: + functionInvocationValue: + arguments: + value: + constantValue: 10000.0 + functionName: Image.constant + functionName: Image.divide + names: + constantValue: + - ha + functionName: Image.rename + srcImg: + functionInvocationValue: + arguments: + input: + functionInvocationValue: + arguments: + image1: + valueReference: '1' + image2: + functionInvocationValue: + arguments: + value: + constantValue: 4046.86 + functionName: Image.constant + functionName: Image.divide + names: + constantValue: + - acres + functionName: Image.rename + functionName: Image.addBands + reducer: + functionInvocationValue: + arguments: {} + functionName: Reducer.mean + scale: + constantValue: 1 + functionName: Image.reduceRegion + '1': + functionInvocationValue: + arguments: {} + functionName: Image.pixelArea diff --git a/tests/test_Image/test_pixel_area.yml b/tests/test_Image/test_pixel_area.yml new file mode 100644 index 00000000..cb2f8563 --- /dev/null +++ b/tests/test_Image/test_pixel_area.yml @@ -0,0 +1,2 @@ +area: 0.000245 +area_1: 9.9e-05 diff --git a/tests/test_Image/test_pixel_area_rename.yml b/tests/test_Image/test_pixel_area_rename.yml new file mode 100644 index 00000000..54ec722b --- /dev/null +++ b/tests/test_Image/test_pixel_area_rename.yml @@ -0,0 +1,2 @@ +acres: 0.000245 +ha: 9.9e-05 diff --git a/tests/test_ImageCollection/serialized_test_deprecated_composite_by_month.yml b/tests/test_ImageCollection/serialized_test_deprecated_composite_by_month.yml index d53d4a42..a757a177 100644 --- a/tests/test_ImageCollection/serialized_test_deprecated_composite_by_month.yml +++ b/tests/test_ImageCollection/serialized_test_deprecated_composite_by_month.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 1ed13eebdf434fc6a0b5b90d4c5d2f6b + constantValue: aa357a2c1ed94d89ace8fd9df72a194b '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_deprecated_composite_regular_intervals.yml b/tests/test_ImageCollection/serialized_test_deprecated_composite_regular_intervals.yml index 074424b1..614ab5c8 100644 --- a/tests/test_ImageCollection/serialized_test_deprecated_composite_regular_intervals.yml +++ b/tests/test_ImageCollection/serialized_test_deprecated_composite_regular_intervals.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 6d1778b0807247609992e68ea882c8b3 + constantValue: 1929b220a31045588235d28322edfffc '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_deprecated_reduce_day_intervals.yml b/tests/test_ImageCollection/serialized_test_deprecated_reduce_day_intervals.yml index 4a85b9ee..9e9e710e 100644 --- a/tests/test_ImageCollection/serialized_test_deprecated_reduce_day_intervals.yml +++ b/tests/test_ImageCollection/serialized_test_deprecated_reduce_day_intervals.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 57a15b9dd7f3477fb4d7cfad1bd14ece + constantValue: 7fc0194a21ec4f159ce49a4944c01c2c '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_deprecated_reduce_equal_interval.yml b/tests/test_ImageCollection/serialized_test_deprecated_reduce_equal_interval.yml index 16f83f25..46c7b955 100644 --- a/tests/test_ImageCollection/serialized_test_deprecated_reduce_equal_interval.yml +++ b/tests/test_ImageCollection/serialized_test_deprecated_reduce_equal_interval.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 08eeb603579441949b1ecbaf29a58065 + constantValue: 61765dc2248c4c26be316aa2436828d4 '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_interval.yml b/tests/test_ImageCollection/serialized_test_reduce_interval.yml index 72789dfd..a0fc6423 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_interval.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_interval.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 70c747284efa428d91f24b7bc7c3f4f2 + constantValue: da1cbb74f3334b598c512e29451dd8e8 '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_interval_properties.yml b/tests/test_ImageCollection/serialized_test_reduce_interval_properties.yml index ed6d3a82..00c4fca7 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_interval_properties.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_interval_properties.yml @@ -602,7 +602,7 @@ values: valueReference: '24' functionName: String.cat '4': - constantValue: 4c9645642d9c46cc81c4ce3cfcb0cb0c + constantValue: 559148b2c5634e518fabdd8b816b1580 '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_interval_with_multi_output_reducer.yml b/tests/test_ImageCollection/serialized_test_reduce_interval_with_multi_output_reducer.yml index de5ca4af..21c85915 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_interval_with_multi_output_reducer.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_interval_with_multi_output_reducer.yml @@ -630,7 +630,7 @@ values: valueReference: '12' functionName: String.cat '4': - constantValue: 97e61c7a29e54ea69d4962b4f1052dec + constantValue: 7e31ed9377544e4daae5de452a856eff '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_interval_with_reducer.yml b/tests/test_ImageCollection/serialized_test_reduce_interval_with_reducer.yml index 550e8357..bcd3781d 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_interval_with_reducer.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_interval_with_reducer.yml @@ -580,7 +580,7 @@ values: valueReference: '23' functionName: String.cat '4': - constantValue: 6f17dfd5be2c4403849fcda8e0d23985 + constantValue: ff481e85e8204b0a8bb4f29556fe496f '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_interval_without_original_names.yml b/tests/test_ImageCollection/serialized_test_reduce_interval_without_original_names.yml index bf22b839..c6479b00 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_interval_without_original_names.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_interval_without_original_names.yml @@ -628,7 +628,7 @@ values: valueReference: '12' functionName: String.cat '4': - constantValue: 03eefcd116f0442c8896b4c42df4dbfd + constantValue: e9ff3a6e43be4c1a826b403e980d152b '5': functionInvocationValue: arguments: diff --git a/tests/test_ImageCollection/serialized_test_reduce_regions_by_date_property.yml b/tests/test_ImageCollection/serialized_test_reduce_regions_by_date_property.yml index 2e0f6cc9..49b67c82 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_regions_by_date_property.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_regions_by_date_property.yml @@ -183,7 +183,7 @@ values: functionName: Date.format functionName: Element.set '16': - constantValue: c9280f1d888e4856b98fa47c8f00ed81 + constantValue: 7611f16ad1be428298bd6bfc6501488a '17': constantValue: system:time_start '18': diff --git a/tests/test_ImageCollection/serialized_test_reduce_regions_by_dates.yml b/tests/test_ImageCollection/serialized_test_reduce_regions_by_dates.yml index 7889102b..e8f97c66 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_regions_by_dates.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_regions_by_dates.yml @@ -183,7 +183,7 @@ values: functionName: Date.format functionName: Element.set '16': - constantValue: 97d382d899cf4a8ba1fbc811cdb2ff2d + constantValue: 6b6455876d2f4a718a88089238522ec8 '17': constantValue: system:time_start '18': diff --git a/tests/test_ImageCollection/serialized_test_reduce_regions_by_doy.yml b/tests/test_ImageCollection/serialized_test_reduce_regions_by_doy.yml index 6f341ef9..cc0daf9f 100644 --- a/tests/test_ImageCollection/serialized_test_reduce_regions_by_doy.yml +++ b/tests/test_ImageCollection/serialized_test_reduce_regions_by_doy.yml @@ -202,7 +202,7 @@ values: functionName: Date.format functionName: Element.set '16': - constantValue: e4c444e0325147748bdf755b077e296d + constantValue: 259e4b9943d948a290d6e00844977ed9 '17': constantValue: system:time_start '18':