Skip to content

Commit e0cb548

Browse files
authored
Merge branch 'main' into match-histogram
2 parents 10adf4c + 4e4563f commit e0cb548

File tree

46 files changed

+4752
-1037
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4752
-1037
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
project = "geetools"
2121
author = "Rodrigo E. Principe"
2222
copyright = f"2017-{datetime.now().year}, {author}"
23-
release = "1.15.0"
23+
release = "1.16.0"
2424

2525
# -- General configuration -----------------------------------------------------
2626
extensions = [

geetools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
__title__ = "geetools"
4343
__summary__ = "A set of useful tools to use with Google Earth Engine Python" "API"
4444
__uri__ = "http://geetools.readthedocs.io"
45-
__version__ = "1.15.0"
45+
__version__ = "1.16.0"
4646

4747
__author__ = "Rodrigo E. Principe"
4848
__email__ = "fitoprincipe82@gmail.com"

geetools/_deprecated_algorithms.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ def distanceToMask(
1919
normalize=False,
2020
):
2121
"""Compute the distance to the mask in meters."""
22-
return (
23-
ee.Image(image)
24-
.geetools.distanceToMask(mask, radius=radius, band_name=band_name)
25-
.select(band_name)
26-
)
22+
return ee.Image(image).geetools.distanceToMask(mask, radius=radius, band_name=band_name).select(band_name)
2723

2824

2925
@deprecated(version="1.5.0", reason="Use ee.Image.geetools.maskCover instead.")

geetools/_deprecated_composite.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def compositeRegularIntervals(
4444

4545

4646
@deprecated(version="1.5.0", reason="Use ee.ImageCollection.geetools.reduceInterval instead")
47-
def compositeByMonth(
48-
collection, composite_function=None, composite_args=None, composite_kwargs=None
49-
):
47+
def compositeByMonth(collection, composite_function=None, composite_args=None, composite_kwargs=None):
5048
"""Make a composite at regular intervals parsing a composite."""
5149
return ee.ImageCollection(collection).geetools.reduceInterval(unit="month")
5250

geetools/ee_asset.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ def __hash__(self):
7777
"""make the Asset object hashable."""
7878
return hash(self.as_posix())
7979

80-
def __getattr__(self, name):
81-
"""Return the attribute of the path object."""
82-
return getattr(self, name)
83-
8480
@classmethod
8581
def home(cls) -> Asset:
8682
"""Return the root asset folder of the used cloud project.
@@ -467,9 +463,7 @@ def iterdir(self, recursive: bool = False) -> list:
467463
"""
468464
# sanity check on variables
469465
if not (self.is_project() or self.is_folder() or self.is_image_collection()):
470-
raise ValueError(
471-
f"Asset {self.as_posix()} is not a container and cannot contain other assets."
472-
)
466+
raise ValueError(f"Asset {self.as_posix()} is not a container and cannot contain other assets.")
473467

474468
# no need for recursion if recursive is false we directly return the result of th API call
475469
if recursive is False:

geetools/ee_dictionary.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ def getMany(self, list: list | ee.List) -> ee.List:
8787
"""
8888
return ee.List(list).map(lambda key: self._obj.get(key))
8989

90-
def toTable(
91-
self, valueType: Literal["dict", "list", "value"] = "value"
92-
) -> ee.FeatureCollection:
90+
def toTable(self, valueType: Literal["dict", "list", "value"] = "value") -> ee.FeatureCollection:
9391
"""Convert a :py:class:`ee.Dictionary` to a :py:class:`ee.FeatureCollection` with no geometries (table).
9492
9593
There are 3 different type of values handled by this method:

geetools/ee_feature_collection.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,15 @@ def toDictionary(
127127
print(json.dumps(countries.getInfo(), indent=2))
128128
"""
129129
uniqueIds = self._obj.aggregate_array(keyColumn)
130-
selectors = (
131-
ee.List(selectors) if selectors is not None else self._obj.first().propertyNames()
132-
)
130+
selectors = ee.List(selectors) if selectors is not None else self._obj.first().propertyNames()
133131
keyColumn = ee.String(keyColumn)
134132

135133
features = self._obj.toList(self._obj.size())
136134
values = features.map(lambda feat: ee.Feature(feat).toDictionary(selectors))
137135
keys = uniqueIds.map(lambda uid: ee.String(ee.Algorithms.String(uid)))
138136
return ee.Dictionary.fromLists(keys, values)
139137

140-
def addId(
141-
self, name: str | ee.String = "id", start: int | ee.Number = 1
142-
) -> ee.FeatureCollection:
138+
def addId(self, name: str | ee.String = "id", start: int | ee.Number = 1) -> ee.FeatureCollection:
143139
"""Add a unique numeric identifier, starting from parameter ``start``.
144140
145141
Args:
@@ -226,6 +222,35 @@ def mergeGeometries(self, maxError: float | int | ee.Number | None = None) -> ee
226222
union = self._obj.iterate(lambda f, g: f.geometry().union(g, maxError=maxError), first)
227223
return ee.Geometry(union).dissolve(maxError=maxError)
228224

225+
def columnNames(self) -> ee.List:
226+
"""Get the name of the columns (Feature's properties).
227+
228+
get a flatten list of all the columns names in the FeatureCollection including the ones that
229+
are not in all features.
230+
231+
Returns:
232+
A list of all the column names in the FeatureCollection.
233+
234+
Example:
235+
.. jupyter-execute::
236+
237+
import ee, geetools
238+
from geetools.utils import initialize_documentation
239+
240+
initialize_documentation()
241+
242+
fc = ee.FeatureCollection([
243+
ee.Feature(ee.Geometry.Point([0, 0]), {"name": "A", "value": 1}),
244+
ee.Feature(ee.Geometry.Point([1, 1]), {"name": "B", "value": 2, "extra": "extra_value"})
245+
])
246+
247+
column_names = fc.geetools.columnNames()
248+
column_names.getInfo()
249+
"""
250+
temp_property_name = "__geetools_properties__"
251+
fc = self._obj.map(lambda feat: feat.set(temp_property_name, feat.propertyNames()))
252+
return fc.aggregate_array(temp_property_name).flatten().distinct()
253+
229254
def toPolygons(self) -> ee.FeatureCollection:
230255
"""Drop any geometry that is not a Polygon or a multipolygon.
231256
@@ -318,23 +343,19 @@ def byProperties(
318343
"""
319344
# get all the id values, they must be string so we are forced to cast them manually
320345
# the default casting is broken from Python side: https://issuetracker.google.com/issues/329106322
321-
features = self._obj.aggregate_array(featureId)
346+
feats = self._obj.aggregate_array(featureId)
322347
isString = lambda i: ee.Algorithms.ObjectType(i).compareTo("String").eq(0) # noqa: E731
323-
features = features.map(lambda i: ee.Algorithms.If(isString(i), i, ee.Number(i).format()))
348+
feats = feats.map(lambda i: ee.Algorithms.If(isString(i), i, ee.Number(i).format()))
324349

325350
# retrieve properties for each feature
326-
properties = (
327-
ee.List(properties) if properties is not None else self._obj.first().propertyNames()
328-
)
329-
properties = properties.remove(featureId)
330-
values = properties.map(
331-
lambda p: ee.Dictionary.fromLists(features, self._obj.aggregate_array(p))
332-
)
351+
eeProps = self._obj.first().propertyNames() if properties is None else ee.List(properties)
352+
eeProps = eeProps.remove(featureId)
353+
values = eeProps.map(lambda p: ee.Dictionary.fromLists(feats, self._obj.aggregate_array(p)))
333354

334355
# get the label to use in the dictionary if requested
335-
labels = ee.List(labels) if labels is not None else properties
356+
eeLabels = eeProps if labels is None else ee.List(labels)
336357

337-
return ee.Dictionary.fromLists(labels, values)
358+
return ee.Dictionary.fromLists(eeLabels, values)
338359

339360
def byFeatures(
340361
self,
@@ -459,11 +480,7 @@ def plot_by_features(
459480
label.set_rotation(45)
460481
"""
461482
# Get the features and properties
462-
props = (
463-
ee.List(properties)
464-
if properties is not None
465-
else self._obj.first().propertyNames().getInfo()
466-
)
483+
props = ee.List(properties) if properties is not None else self._obj.first().propertyNames().getInfo()
467484
props = props.remove(featureId)
468485

469486
# get the data from server

geetools/ee_image.py

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ def addDate(self, format: str | ee.String = "") -> ee.Image:
7171

7272
return self._obj.addBands(dateBand)
7373

74-
def addSuffix(
75-
self, suffix: str | ee.String, bands: list[str] | ee.List | None = None
76-
) -> ee.Image:
74+
def addSuffix(self, suffix: str | ee.String, bands: list[str] | ee.List | None = None) -> ee.Image:
7775
"""Add a suffix to the image selected band.
7876
7977
Add a suffix to the selected band. If no band is specified, the suffix is added to all bands.
@@ -97,16 +95,12 @@ def addSuffix(
9795
print(image.bandNames().getInfo())
9896
"""
9997
suffix = ee.String(suffix)
100-
bands = ee.List(bands) if bands is not None else self._obj.bandNames()
101-
bandNames = bands.iterate(
102-
lambda b, n: ee.List(n).replace(b, ee.String(b).cat(suffix)),
103-
self._obj.bandNames(),
104-
)
98+
srcBands = self._obj.bandNames()
99+
eeBands = srcBands if bands is None else ee.List(bands)
100+
bandNames = eeBands.iterate(lambda b, n: ee.List(n).replace(b, ee.String(b).cat(suffix)), srcBands)
105101
return self._obj.rename(bandNames)
106102

107-
def addPrefix(
108-
self, prefix: str | ee.String, bands: list[str] | ee.List | None = None
109-
) -> ee.Image:
103+
def addPrefix(self, prefix: str | ee.String, bands: list[str] | ee.List | None = None) -> ee.Image:
110104
"""Add a prefix to the image selected band.
111105
112106
Add a prefix to the selected band. If no band is specified, the prefix is added to all bands.
@@ -130,11 +124,9 @@ def addPrefix(
130124
print(image.bandNames().getInfo())
131125
"""
132126
prefix = ee.String(prefix)
133-
bands = ee.List(bands) if bands is not None else self._obj.bandNames()
134-
bandNames = bands.iterate(
135-
lambda b, n: ee.List(n).replace(b, prefix.cat(ee.String(b))),
136-
self._obj.bandNames(),
137-
)
127+
srcBands = self._obj.bandNames()
128+
eeBands = srcBands if bands is None else ee.List(bands)
129+
bandNames = eeBands.iterate(lambda b, n: ee.List(n).replace(b, prefix.cat(ee.String(b))), srcBands)
138130
return self._obj.rename(bandNames)
139131

140132
def rename(self, names: dict | ee.Dictionary) -> ee.Image:
@@ -162,9 +154,7 @@ def rename(self, names: dict | ee.Dictionary) -> ee.Image:
162154
print(image.bandNames().getInfo())
163155
"""
164156
names = ee.Dictionary(names)
165-
bands = names.keys().iterate(
166-
lambda b, n: ee.List(n).replace(b, names.get(b)), self._obj.bandNames()
167-
)
157+
bands = names.keys().iterate(lambda b, n: ee.List(n).replace(b, names.get(b)), self._obj.bandNames())
168158
return self._obj.rename(bands)
169159

170160
def remove(self, bands: list[str] | ee.List) -> ee.Image:
@@ -228,9 +218,7 @@ def doyToDate(
228218

229219
doyList = ee.List.sequence(0, 365)
230220
remapList = doyList.map(
231-
lambda d: ee.Number.parse(
232-
ee.Date.fromYMD(year, 1, 1).advance(d, "day").format(dateFormat)
233-
)
221+
lambda d: ee.Number.parse(ee.Date.fromYMD(year, 1, 1).advance(d, "day").format(dateFormat))
234222
)
235223
return self._obj.remap(doyList, remapList, bandName=band).rename(band)
236224

@@ -361,9 +349,7 @@ def toGrid(
361349
index = ee.List.sequence(0, lat.size().subtract(2))
362350
squares = index.map(
363351
lambda i: (
364-
ee.Geometry.Point([lon.get(i), lat.get(i)])
365-
.buffer(size.divide(2))
366-
.bounds(0.01, projection)
352+
ee.Geometry.Point([lon.get(i), lat.get(i)]).buffer(size.divide(2)).bounds(0.01, projection)
367353
)
368354
)
369355

@@ -402,9 +388,7 @@ def clipOnCollection(
402388

403389
def fcClip(feat):
404390
image = self._obj.clip(feat.geometry())
405-
return ee.Algorithms.If(
406-
ee.Number(keepProperties).toInt(), image.copyProperties(feat), image
407-
)
391+
return ee.Algorithms.If(ee.Number(keepProperties).toInt(), image.copyProperties(feat), image)
408392

409393
return ee.ImageCollection(fc.map(fcClip))
410394

@@ -469,18 +453,18 @@ def full(
469453
image = ee.Image.geetools.full([1, 2, 3], ['a', 'b', 'c'])
470454
print(image.bandNames().getInfo())
471455
"""
472-
values = ee.List(values) if values is not None else ee.List([0])
473-
names = ee.List(names) if names is not None else ee.List(["constant"])
456+
eeValues = ee.List([0]) if values is None else ee.List(values)
457+
eeNames = ee.List(["constant"]) if names is None else ee.List(names)
474458

475459
# resize value to the same length as names
476-
values = ee.List(
460+
eeValues = ee.List(
477461
ee.Algorithms.If(
478-
values.size().eq(1),
479-
ee.List.repeat(ee.Number(values.get(0)), names.size()),
480-
values,
462+
eeValues.size().eq(1),
463+
ee.List.repeat(ee.Number(eeValues.get(0)), eeNames.size()),
464+
eeValues,
481465
)
482466
)
483-
return ee.Image.constant(values).rename(names)
467+
return ee.Image.constant(eeValues).rename(eeNames)
484468

485469
def fullLike(
486470
self,
@@ -574,12 +558,12 @@ def reduceBands(
574558
if not isinstance(reducer, str):
575559
raise TypeError("reducer must be a Python string")
576560

577-
bands = ee.List(bands) if bands is not None else ee.List([])
561+
eeBands = ee.List(bands) if bands is not None else ee.List([])
578562
name = ee.String(name)
579-
bands = ee.Algorithms.If(bands.size().eq(0), self._obj.bandNames(), bands)
563+
eeBands = ee.Algorithms.If(eeBands.size().eq(0), self._obj.bandNames(), eeBands)
580564
name = ee.Algorithms.If(name.equals(ee.String("")), reducer, name)
581565
red = getattr(ee.Reducer, reducer)() if isinstance(reducer, str) else reducer
582-
reduceImage = self._obj.select(ee.List(bands)).reduce(red).rename([name])
566+
reduceImage = self._obj.select(ee.List(eeBands)).reduce(red).rename([name])
583567
return self._obj.addBands(reduceImage)
584568

585569
def negativeClip(self, geometry: ee.Geometry | ee.Feature | ee.FeatureCollection) -> ee.Image:
@@ -827,9 +811,7 @@ def isletMask(self, offset: float | int | ee.Number) -> ee.Image:
827811
scale = self._obj.projection().nominalScale()
828812
pixelsLimit = offset.multiply(2).sqrt().divide(scale).max(ee.Number(2)).toInt()
829813
area = ee.Image.pixelArea().rename("area")
830-
isletArea = (
831-
self._obj.select(0).mask().toInt().connectedPixelCount(pixelsLimit).multiply(area)
832-
)
814+
isletArea = self._obj.select(0).mask().toInt().connectedPixelCount(pixelsLimit).multiply(area)
833815
return isletArea.lt(offset).rename("mask").selfMask()
834816

835817
# -- ee-extra wrapper ------------------------------------------------------
@@ -1119,7 +1101,7 @@ def getDOI(self) -> str:
11191101
"""
11201102
stac = self.getSTAC()
11211103
error_msg = "DOI not found in the STAC"
1122-
return stac["sci:doi"] if "sci:doi" in stac else error_msg
1104+
return stac.get("sci:doi", error_msg)
11231105

11241106
def getCitation(self) -> str:
11251107
"""Gets the citation of the image, if available.
@@ -1142,7 +1124,7 @@ def getCitation(self) -> str:
11421124
"""
11431125
stac = self.getSTAC()
11441126
error_msg = "Citation not found in the STAC"
1145-
return stac["sci:citation"] if "sci:citation" in stac else error_msg
1127+
return stac.get("sci:citation", error_msg)
11461128

11471129
def panSharpen(self, method: str = "SFIM", qa: str = "", **kwargs) -> ee.Image:
11481130
"""Apply panchromatic sharpening to the Image.

0 commit comments

Comments
 (0)