earthengine-api 1.6.11__py3-none-any.whl → 1.6.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/METADATA +1 -1
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/RECORD +48 -46
- ee/__init__.py +5 -5
- ee/_cloud_api_utils.py +33 -10
- ee/_state.py +105 -0
- ee/apifunction.py +1 -1
- ee/apitestcase.py +15 -21
- ee/batch.py +1 -1
- ee/cli/commands.py +153 -63
- ee/cli/eecli.py +1 -1
- ee/cli/utils.py +25 -15
- ee/collection.py +27 -18
- ee/computedobject.py +5 -5
- ee/customfunction.py +3 -3
- ee/data.py +104 -210
- ee/ee_array.py +4 -2
- ee/ee_number.py +1 -1
- ee/ee_string.py +18 -26
- ee/ee_types.py +2 -2
- ee/element.py +1 -1
- ee/featurecollection.py +10 -7
- ee/filter.py +2 -2
- ee/geometry.py +20 -21
- ee/image.py +7 -12
- ee/imagecollection.py +3 -3
- ee/mapclient.py +9 -9
- ee/oauth.py +13 -6
- ee/tests/_cloud_api_utils_test.py +16 -0
- ee/tests/_helpers_test.py +9 -9
- ee/tests/_state_test.py +49 -0
- ee/tests/apifunction_test.py +5 -5
- ee/tests/batch_test.py +61 -50
- ee/tests/collection_test.py +13 -13
- ee/tests/data_test.py +65 -60
- ee/tests/dictionary_test.py +9 -9
- ee/tests/ee_number_test.py +32 -26
- ee/tests/ee_string_test.py +8 -0
- ee/tests/ee_test.py +37 -19
- ee/tests/element_test.py +2 -2
- ee/tests/feature_test.py +6 -6
- ee/tests/function_test.py +5 -5
- ee/tests/geometry_test.py +73 -51
- ee/tests/oauth_test.py +21 -2
- ee/tests/serializer_test.py +8 -8
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/WHEEL +0 -0
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/entry_points.txt +0 -0
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/licenses/LICENSE +0 -0
- {earthengine_api-1.6.11.dist-info → earthengine_api-1.6.12.dist-info}/top_level.txt +0 -0
ee/ee_string.py
CHANGED
|
@@ -37,19 +37,19 @@ class String(computedobject.ComputedObject):
|
|
|
37
37
|
if isinstance(string, str):
|
|
38
38
|
super().__init__(None, None)
|
|
39
39
|
self._string = string
|
|
40
|
-
|
|
41
40
|
elif isinstance(string, computedobject.ComputedObject):
|
|
42
41
|
if self.is_func_returning_same(string):
|
|
43
42
|
# If it's a call that's already returning a String, just cast.
|
|
44
43
|
super().__init__(string.func, string.args, string.varName)
|
|
45
44
|
else:
|
|
45
|
+
# Wrap some other ComputedObject.
|
|
46
46
|
super().__init__(
|
|
47
47
|
apifunction.ApiFunction(self.name()), {'input': string}
|
|
48
48
|
)
|
|
49
49
|
self._string = None
|
|
50
50
|
else:
|
|
51
51
|
raise ee_exception.EEException(
|
|
52
|
-
'Invalid argument specified for ee.String():
|
|
52
|
+
f'Invalid argument specified for ee.String(): {string}')
|
|
53
53
|
|
|
54
54
|
@classmethod
|
|
55
55
|
def initialize(cls) -> None:
|
|
@@ -91,8 +91,7 @@ class String(computedobject.ComputedObject):
|
|
|
91
91
|
Returns:
|
|
92
92
|
Returns the result of joining self and string2.
|
|
93
93
|
"""
|
|
94
|
-
|
|
95
|
-
return apifunction.ApiFunction.call_(self.name() + '.cat', self, string2)
|
|
94
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.cat', self, string2)
|
|
96
95
|
|
|
97
96
|
def compareTo(self, string2: _arg_types.String) -> ee_number.Number:
|
|
98
97
|
"""Compares two strings lexicographically.
|
|
@@ -107,15 +106,15 @@ class String(computedobject.ComputedObject):
|
|
|
107
106
|
"""
|
|
108
107
|
|
|
109
108
|
return apifunction.ApiFunction.call_(
|
|
110
|
-
self.name()
|
|
109
|
+
f'{self.name()}.compareTo', self, string2
|
|
111
110
|
)
|
|
112
111
|
|
|
113
112
|
def decodeJSON(self) -> computedobject.ComputedObject:
|
|
114
113
|
"""Decodes self as a JSON string."""
|
|
115
|
-
|
|
116
|
-
return apifunction.ApiFunction.call_(self.name() + '.decodeJSON', self)
|
|
114
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.decodeJSON', self)
|
|
117
115
|
|
|
118
116
|
@staticmethod
|
|
117
|
+
# pylint: disable=redefined-builtin
|
|
119
118
|
def encodeJSON(object: _arg_types.Any) -> String:
|
|
120
119
|
"""Returns an ee.String with an object encoded as JSON.
|
|
121
120
|
|
|
@@ -124,7 +123,7 @@ class String(computedobject.ComputedObject):
|
|
|
124
123
|
Args:
|
|
125
124
|
object: The object to encode.
|
|
126
125
|
"""
|
|
127
|
-
|
|
126
|
+
# object is probably not an ee.String, so use "String." instead of object.
|
|
128
127
|
return apifunction.ApiFunction.call_('String.encodeJSON', object)
|
|
129
128
|
|
|
130
129
|
def equals(self, target: _arg_types.String) -> computedobject.ComputedObject:
|
|
@@ -137,8 +136,7 @@ class String(computedobject.ComputedObject):
|
|
|
137
136
|
True if the target is a string and is lexicographically equal to the
|
|
138
137
|
reference, or false otherwise.
|
|
139
138
|
"""
|
|
140
|
-
|
|
141
|
-
return apifunction.ApiFunction.call_(self.name() + '.equals', self, target)
|
|
139
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.equals', self, target)
|
|
142
140
|
|
|
143
141
|
def index(self, pattern: _arg_types.String) -> ee_number.Number:
|
|
144
142
|
"""Searches a string for the first occurrence of a substring.
|
|
@@ -149,13 +147,11 @@ class String(computedobject.ComputedObject):
|
|
|
149
147
|
Returns:
|
|
150
148
|
The index of the first match, or -1.
|
|
151
149
|
"""
|
|
152
|
-
|
|
153
|
-
return apifunction.ApiFunction.call_(self.name() + '.index', self, pattern)
|
|
150
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.index', self, pattern)
|
|
154
151
|
|
|
155
152
|
def length(self) -> ee_number.Number:
|
|
156
153
|
"""Returns the length of a string."""
|
|
157
|
-
|
|
158
|
-
return apifunction.ApiFunction.call_(self.name() + '.length', self)
|
|
154
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.length', self)
|
|
159
155
|
|
|
160
156
|
def match(
|
|
161
157
|
self, regex: _arg_types.String, flags: _arg_types.String | None = None
|
|
@@ -172,7 +168,7 @@ class String(computedobject.ComputedObject):
|
|
|
172
168
|
"""
|
|
173
169
|
|
|
174
170
|
return apifunction.ApiFunction.call_(
|
|
175
|
-
self.name()
|
|
171
|
+
f'{self.name()}.match', self, regex, flags
|
|
176
172
|
)
|
|
177
173
|
|
|
178
174
|
def replace(
|
|
@@ -194,7 +190,7 @@ class String(computedobject.ComputedObject):
|
|
|
194
190
|
"""
|
|
195
191
|
|
|
196
192
|
return apifunction.ApiFunction.call_(
|
|
197
|
-
self.name()
|
|
193
|
+
f'{self.name()}.replace', self, regex, replacement, flags
|
|
198
194
|
)
|
|
199
195
|
|
|
200
196
|
def rindex(self, pattern: _arg_types.String) -> ee_number.Number:
|
|
@@ -206,8 +202,7 @@ class String(computedobject.ComputedObject):
|
|
|
206
202
|
Returns:
|
|
207
203
|
The index of the first match or -1 if no match.
|
|
208
204
|
"""
|
|
209
|
-
|
|
210
|
-
return apifunction.ApiFunction.call_(self.name() + '.rindex', self, pattern)
|
|
205
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.rindex', self, pattern)
|
|
211
206
|
|
|
212
207
|
def slice(
|
|
213
208
|
self, start: _arg_types.Integer, end: _arg_types.Integer | None = None
|
|
@@ -228,7 +223,7 @@ class String(computedobject.ComputedObject):
|
|
|
228
223
|
"""
|
|
229
224
|
|
|
230
225
|
return apifunction.ApiFunction.call_(
|
|
231
|
-
self.name()
|
|
226
|
+
f'{self.name()}.slice', self, start, end
|
|
232
227
|
)
|
|
233
228
|
|
|
234
229
|
def split(
|
|
@@ -246,20 +241,17 @@ class String(computedobject.ComputedObject):
|
|
|
246
241
|
"""
|
|
247
242
|
|
|
248
243
|
return apifunction.ApiFunction.call_(
|
|
249
|
-
self.name()
|
|
244
|
+
f'{self.name()}.split', self, regex, flags
|
|
250
245
|
)
|
|
251
246
|
|
|
252
247
|
def toLowerCase(self) -> String:
|
|
253
248
|
"""Converts all of the characters in a string to lower case."""
|
|
254
|
-
|
|
255
|
-
return apifunction.ApiFunction.call_(self.name() + '.toLowerCase', self)
|
|
249
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.toLowerCase', self)
|
|
256
250
|
|
|
257
251
|
def toUpperCase(self) -> String:
|
|
258
252
|
"""Converts all of the characters in a string to upper case."""
|
|
259
|
-
|
|
260
|
-
return apifunction.ApiFunction.call_(self.name() + '.toUpperCase', self)
|
|
253
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.toUpperCase', self)
|
|
261
254
|
|
|
262
255
|
def trim(self) -> String:
|
|
263
256
|
"""Returns a string with any leading and trailing whitespace removed."""
|
|
264
|
-
|
|
265
|
-
return apifunction.ApiFunction.call_(self.name() + '.trim', self)
|
|
257
|
+
return apifunction.ApiFunction.call_(f'{self.name()}.trim', self)
|
ee/ee_types.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Any
|
|
|
9
9
|
from ee import computedobject
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
# A dictionary of the classes in the ee module.
|
|
12
|
+
# A dictionary of the classes in the ee module. Set by registerClasses.
|
|
13
13
|
_registered_classes = {}
|
|
14
14
|
|
|
15
15
|
|
|
@@ -48,7 +48,7 @@ def classToName(a_class: type[Any]) -> str:
|
|
|
48
48
|
|
|
49
49
|
# TODO(user): Any -> Optional[type[Any]].
|
|
50
50
|
def nameToClass(name: str) -> Any:
|
|
51
|
-
"""Converts a class name to a class.
|
|
51
|
+
"""Converts a class name to a class. Returns None if not an ee class.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
name: The class name.
|
ee/element.py
CHANGED
|
@@ -187,7 +187,7 @@ class Element(computedobject.ComputedObject):
|
|
|
187
187
|
"""Returns properties from a feature as a dictionary.
|
|
188
188
|
|
|
189
189
|
Args:
|
|
190
|
-
properties: The list of properties to extract.
|
|
190
|
+
properties: The list of properties to extract. Defaults to all non-system
|
|
191
191
|
properties
|
|
192
192
|
"""
|
|
193
193
|
|
ee/featurecollection.py
CHANGED
|
@@ -22,7 +22,7 @@ from ee import geometry
|
|
|
22
22
|
from ee import image
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class FeatureCollection(collection.Collection):
|
|
25
|
+
class FeatureCollection(collection.Collection[feature.Feature]):
|
|
26
26
|
"""A representation of a FeatureCollection."""
|
|
27
27
|
|
|
28
28
|
_initialized = False
|
|
@@ -47,10 +47,13 @@ class FeatureCollection(collection.Collection):
|
|
|
47
47
|
"""Constructs a collection features.
|
|
48
48
|
|
|
49
49
|
Args:
|
|
50
|
-
args: constructor argument.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
args: constructor argument. One of:
|
|
51
|
+
1) A string - assumed to be the name of a collection.
|
|
52
|
+
2) A geometry.
|
|
53
|
+
3) A feature.
|
|
54
|
+
4) An array of features.
|
|
55
|
+
5) A GeoJSON FeatureCollection.
|
|
56
|
+
6) A computed object - reinterpreted as a collection.
|
|
54
57
|
column: The name of the geometry column to use. Only useful with the
|
|
55
58
|
string constructor.
|
|
56
59
|
|
|
@@ -187,9 +190,9 @@ class FeatureCollection(collection.Collection):
|
|
|
187
190
|
propertySelectors: An array of names or regexes specifying the properties
|
|
188
191
|
to select.
|
|
189
192
|
newProperties: An array of strings specifying the new names for the
|
|
190
|
-
selected properties.
|
|
193
|
+
selected properties. If supplied, the length must match the number
|
|
191
194
|
of properties selected.
|
|
192
|
-
retainGeometry: A boolean.
|
|
195
|
+
retainGeometry: A boolean. When false, the result will have no geometry.
|
|
193
196
|
*args: Selector elements as varargs.
|
|
194
197
|
|
|
195
198
|
Returns:
|
ee/filter.py
CHANGED
|
@@ -108,7 +108,7 @@ class Filter(computedobject.ComputedObject):
|
|
|
108
108
|
These are implicitly ANDed.
|
|
109
109
|
|
|
110
110
|
Args:
|
|
111
|
-
new_filter: The filter to append to this one.
|
|
111
|
+
new_filter: The filter to append to this one. Possible types are:
|
|
112
112
|
1) another fully constructed Filter,
|
|
113
113
|
2) a JSON representation of a filter,
|
|
114
114
|
3) an array of 1 or 2.
|
|
@@ -141,7 +141,7 @@ class Filter(computedobject.ComputedObject):
|
|
|
141
141
|
Returns:
|
|
142
142
|
The new filter.
|
|
143
143
|
|
|
144
|
-
Deprecated.
|
|
144
|
+
Deprecated. Use ee.Filter.eq(), ee.Filter.gte(), etc.'
|
|
145
145
|
"""
|
|
146
146
|
operator = operator.lower()
|
|
147
147
|
|
ee/geometry.py
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Iterable, Sequence
|
|
7
6
|
import json
|
|
8
7
|
import math
|
|
9
8
|
from typing import Any
|
|
@@ -79,7 +78,7 @@ class Geometry(computedobject.ComputedObject):
|
|
|
79
78
|
if options:
|
|
80
79
|
raise ee_exception.EEException(
|
|
81
80
|
'Setting the CRS or geodesic on a computed Geometry is not '
|
|
82
|
-
'supported.
|
|
81
|
+
'supported. Use Geometry.transform().')
|
|
83
82
|
else:
|
|
84
83
|
super().__init__(geo_json.func, geo_json.args, geo_json.varName)
|
|
85
84
|
return
|
|
@@ -157,7 +156,7 @@ class Geometry(computedobject.ComputedObject):
|
|
|
157
156
|
if isinstance(name, str):
|
|
158
157
|
return name
|
|
159
158
|
raise ee_exception.EEException(
|
|
160
|
-
'Invalid CRS declaration in GeoJSON:
|
|
159
|
+
f'Invalid CRS declaration in GeoJSON: {json.dumps(crs)}'
|
|
161
160
|
)
|
|
162
161
|
|
|
163
162
|
@classmethod
|
|
@@ -342,12 +341,12 @@ class Geometry(computedobject.ComputedObject):
|
|
|
342
341
|
# negated, we also reject NaN.
|
|
343
342
|
if not south <= 90:
|
|
344
343
|
raise ee_exception.EEException(
|
|
345
|
-
'Geometry.BBox: south must be at most +90°, but was {}°'
|
|
346
|
-
|
|
344
|
+
f'Geometry.BBox: south must be at most +90°, but was {south}°'
|
|
345
|
+
)
|
|
347
346
|
if not north >= -90:
|
|
348
347
|
raise ee_exception.EEException(
|
|
349
|
-
'Geometry.BBox: north must be at least -90°, but was {}°'
|
|
350
|
-
|
|
348
|
+
f'Geometry.BBox: north must be at least -90°, but was {north}°'
|
|
349
|
+
)
|
|
351
350
|
# On the other hand, allow a box whose extent lies past the pole, but
|
|
352
351
|
# canonicalize it to being exactly the pole.
|
|
353
352
|
south = max(south, -90)
|
|
@@ -402,7 +401,7 @@ class Geometry(computedobject.ComputedObject):
|
|
|
402
401
|
"""Constructs an ee.Geometry describing a LineString.
|
|
403
402
|
|
|
404
403
|
Args:
|
|
405
|
-
coords: A list of at least two points.
|
|
404
|
+
coords: A list of at least two points. May be a list of coordinates in
|
|
406
405
|
the GeoJSON 'LineString' format, a list of at least two ee.Geometry
|
|
407
406
|
objects describing a point, or a list of at least four numbers
|
|
408
407
|
defining the [x,y] coordinates of at least two points.
|
|
@@ -477,7 +476,7 @@ class Geometry(computedobject.ComputedObject):
|
|
|
477
476
|
"""Constructs an ee.Geometry describing a MultiLineString.
|
|
478
477
|
|
|
479
478
|
Create a GeoJSON MultiLineString from either a list of points, or an array
|
|
480
|
-
of lines (each an array of Points).
|
|
479
|
+
of lines (each an array of Points). If a list of points is specified,
|
|
481
480
|
only a single line is created.
|
|
482
481
|
|
|
483
482
|
Args:
|
|
@@ -647,12 +646,12 @@ class Geometry(computedobject.ComputedObject):
|
|
|
647
646
|
)
|
|
648
647
|
return json.dumps(self.toGeoJSON())
|
|
649
648
|
|
|
650
|
-
def serialize(self, for_cloud_api=True):
|
|
649
|
+
def serialize(self, for_cloud_api: bool = True) -> str:
|
|
651
650
|
"""Returns the serialized representation of this object."""
|
|
652
651
|
return serializer.toJSON(self, for_cloud_api=for_cloud_api)
|
|
653
652
|
|
|
654
653
|
def __str__(self) -> str:
|
|
655
|
-
return 'ee.Geometry(
|
|
654
|
+
return f'ee.Geometry({serializer.toReadableJSON(self)})'
|
|
656
655
|
|
|
657
656
|
def __repr__(self) -> str:
|
|
658
657
|
return self.__str__()
|
|
@@ -702,10 +701,10 @@ class Geometry(computedobject.ComputedObject):
|
|
|
702
701
|
Returns:
|
|
703
702
|
The number of nested arrays or -1 on error.
|
|
704
703
|
"""
|
|
705
|
-
if not isinstance(shape,
|
|
704
|
+
if not isinstance(shape, Iterable):
|
|
706
705
|
return -1
|
|
707
706
|
|
|
708
|
-
if (shape and isinstance(shape[0],
|
|
707
|
+
if (shape and isinstance(shape[0], Iterable) and
|
|
709
708
|
not isinstance(shape[0], str)):
|
|
710
709
|
count = Geometry._isValidCoordinates(shape[0])
|
|
711
710
|
# If more than 1 ring or polygon, they should have the same nesting.
|
|
@@ -730,7 +729,7 @@ class Geometry(computedobject.ComputedObject):
|
|
|
730
729
|
"""Create a line from a list of points.
|
|
731
730
|
|
|
732
731
|
Args:
|
|
733
|
-
coordinates: The points to convert.
|
|
732
|
+
coordinates: The points to convert. Must be list of numbers of
|
|
734
733
|
even length, in the format [x1, y1, x2, y2, ...]
|
|
735
734
|
|
|
736
735
|
Returns:
|
|
@@ -742,13 +741,13 @@ class Geometry(computedobject.ComputedObject):
|
|
|
742
741
|
return coordinates
|
|
743
742
|
if len(coordinates) % 2 != 0:
|
|
744
743
|
raise ee_exception.EEException(
|
|
745
|
-
'Invalid number of coordinates:
|
|
744
|
+
f'Invalid number of coordinates: {len(coordinates)}'
|
|
745
|
+
)
|
|
746
746
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
return line
|
|
747
|
+
return [
|
|
748
|
+
[coordinates[i], coordinates[i + 1]]
|
|
749
|
+
for i in range(0, len(coordinates), 2)
|
|
750
|
+
]
|
|
752
751
|
|
|
753
752
|
@staticmethod
|
|
754
753
|
def _parseArgs(ctor_name: str, depth: int, args: Any) -> dict[str, Any]:
|
ee/image.py
CHANGED
|
@@ -430,8 +430,7 @@ class Image(element.Element):
|
|
|
430
430
|
# pylint: enable=protected-access
|
|
431
431
|
return band_image
|
|
432
432
|
|
|
433
|
-
if params['format'] == 'ZIPPED_GEO_TIFF_PER_BAND' and params.get(
|
|
434
|
-
'bands') and len(params.get('bands')):
|
|
433
|
+
if params['format'] == 'ZIPPED_GEO_TIFF_PER_BAND' and params.get('bands'):
|
|
435
434
|
# Build a new image based on the constituent band images.
|
|
436
435
|
image = Image.combine_(
|
|
437
436
|
[_build_image_per_band(band) for band in params['bands']])
|
|
@@ -3579,7 +3578,6 @@ class Image(element.Element):
|
|
|
3579
3578
|
# pylint: enable=invalid-name
|
|
3580
3579
|
**kwargs,
|
|
3581
3580
|
) -> Image:
|
|
3582
|
-
# pylint: disable=g-doc-args
|
|
3583
3581
|
"""Returns an image with the values of a band remapped.
|
|
3584
3582
|
|
|
3585
3583
|
Maps from input values to output values, represented by two parallel lists.
|
|
@@ -3589,24 +3587,21 @@ class Image(element.Element):
|
|
|
3589
3587
|
floating point precision errors.
|
|
3590
3588
|
|
|
3591
3589
|
Args:
|
|
3592
|
-
|
|
3590
|
+
from_: The source values (numbers or ee.Array). All values in this list
|
|
3593
3591
|
will be mapped to the corresponding value in 'to'.
|
|
3594
3592
|
to: The destination values (numbers or ee.Array). These are used to
|
|
3595
3593
|
replace the corresponding values in 'from'. Must have the same number of
|
|
3596
|
-
values as '
|
|
3594
|
+
values as 'from_'.
|
|
3597
3595
|
defaultValue: The default value to replace values that weren't matched by
|
|
3598
|
-
a value in '
|
|
3596
|
+
a value in 'from_'. If not specified, unmatched values are masked out.
|
|
3599
3597
|
bandName: The name of the band to remap. If not specified, the first band
|
|
3600
3598
|
in the image is used.
|
|
3601
3599
|
"""
|
|
3602
|
-
# pylint: enable=g-doc-args
|
|
3603
3600
|
|
|
3601
|
+
if 'from' in kwargs:
|
|
3602
|
+
from_ = kwargs.pop('from')
|
|
3604
3603
|
if kwargs:
|
|
3605
|
-
|
|
3606
|
-
raise TypeError(
|
|
3607
|
-
f'Unexpected arguments: {list(kwargs.keys())}. Expected: from.'
|
|
3608
|
-
)
|
|
3609
|
-
from_ = kwargs['from']
|
|
3604
|
+
raise TypeError(f'remap() got unexpected keyword arguments: {list(kwargs)}')
|
|
3610
3605
|
|
|
3611
3606
|
if not from_:
|
|
3612
3607
|
raise TypeError('from is required.')
|
ee/imagecollection.py
CHANGED
|
@@ -20,7 +20,7 @@ from ee import image
|
|
|
20
20
|
REDUCE_PREFIX = 'reduce'
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class ImageCollection(collection.Collection):
|
|
23
|
+
class ImageCollection(collection.Collection[image.Image]):
|
|
24
24
|
"""Representation for an Earth Engine ImageCollection."""
|
|
25
25
|
|
|
26
26
|
_initialized = False
|
|
@@ -114,7 +114,7 @@ class ImageCollection(collection.Collection):
|
|
|
114
114
|
selectors: An array of names, regexes or numeric indices specifying the
|
|
115
115
|
bands to select.
|
|
116
116
|
names: An array of strings specifying the new names for the selected
|
|
117
|
-
bands.
|
|
117
|
+
bands. If supplied, the length must match the number of bands selected.
|
|
118
118
|
*args: Selector elements as varargs.
|
|
119
119
|
|
|
120
120
|
Returns:
|
|
@@ -194,7 +194,7 @@ class ImageCollection(collection.Collection):
|
|
|
194
194
|
"""Get the URL for an animated video thumbnail of the given collection.
|
|
195
195
|
|
|
196
196
|
Note: Videos can only be created when the image visualization
|
|
197
|
-
creates an RGB or RGBA image.
|
|
197
|
+
creates an RGB or RGBA image. This can be done by mapping a visualization
|
|
198
198
|
onto the collection or specifying three bands in the params.
|
|
199
199
|
|
|
200
200
|
Args:
|
ee/mapclient.py
CHANGED
|
@@ -8,7 +8,7 @@ This currently has several spots that are hard-coded for 256x256 tiles, even
|
|
|
8
8
|
though MapOverlay tries to track this.
|
|
9
9
|
|
|
10
10
|
Supports mouse-based pan and zoom as well as tile upsampling while waiting
|
|
11
|
-
for new tiles to load.
|
|
11
|
+
for new tiles to load. The map to display is specified by a MapOverlay, and
|
|
12
12
|
added to the GUI on creation or manually using addOverlay()
|
|
13
13
|
gui = MapClient(MakeOverlay(mapid))
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@ import urllib.request
|
|
|
35
35
|
from PIL import Image
|
|
36
36
|
from PIL import ImageTk
|
|
37
37
|
|
|
38
|
-
# The default URL to fetch tiles from.
|
|
38
|
+
# The default URL to fetch tiles from. We could pull this from the EE library,
|
|
39
39
|
# however this doesn't have any other dependencies on that yet, so let's not.
|
|
40
40
|
BASE_URL = 'https://earthengine.googleapis.com'
|
|
41
41
|
|
|
@@ -52,7 +52,7 @@ class MapClient(threading.Thread):
|
|
|
52
52
|
"""Initialize the MapClient UI.
|
|
53
53
|
|
|
54
54
|
Args:
|
|
55
|
-
opt_overlay: A mapoverlay to display.
|
|
55
|
+
opt_overlay: A mapoverlay to display. If not specified, the default
|
|
56
56
|
Google Maps basemap is used.
|
|
57
57
|
opt_width: The default width of the frame to construct.
|
|
58
58
|
opt_height: The default height of the frame to construct.
|
|
@@ -178,7 +178,7 @@ class MapClient(threading.Thread):
|
|
|
178
178
|
image: The image tile to display.
|
|
179
179
|
key: A tuple containing the key of the image (level, x, y)
|
|
180
180
|
overlay: The overlay this tile belongs to.
|
|
181
|
-
layer: The layer number this overlay corresponds to.
|
|
181
|
+
layer: The layer number this overlay corresponds to. Only used
|
|
182
182
|
for caching purposes.
|
|
183
183
|
"""
|
|
184
184
|
# TODO(user): This function is called from multiple threads, and
|
|
@@ -205,7 +205,7 @@ class MapClient(threading.Thread):
|
|
|
205
205
|
|
|
206
206
|
Args:
|
|
207
207
|
event: The event that caused this zoom request.
|
|
208
|
-
direction: The direction to zoom.
|
|
208
|
+
direction: The direction to zoom. +1 for higher zoom, -1 for lower.
|
|
209
209
|
"""
|
|
210
210
|
if self.level + direction >= 0:
|
|
211
211
|
# Discard everything cached in the MapClient, and flush the fetch queues.
|
|
@@ -319,13 +319,13 @@ class MapOverlay:
|
|
|
319
319
|
"""Get the requested tile.
|
|
320
320
|
|
|
321
321
|
If the requested tile is already cached, it's returned (sent to the
|
|
322
|
-
callback) directly.
|
|
322
|
+
callback) directly. If it's not cached, a check is made to see if
|
|
323
323
|
a lower-res version is cached, and if so that's interpolated up, before
|
|
324
324
|
a request for the actual tile is made.
|
|
325
325
|
|
|
326
326
|
Args:
|
|
327
327
|
key: The key of the tile to fetch.
|
|
328
|
-
callback: The callback to call when the tile is available.
|
|
328
|
+
callback: The callback to call when the tile is available. The callback
|
|
329
329
|
may be called more than once if a low-res version is available.
|
|
330
330
|
"""
|
|
331
331
|
result = self.GetCachedTile(key)
|
|
@@ -456,12 +456,12 @@ def addToMap(eeobject, vis_params=None, *args):
|
|
|
456
456
|
|
|
457
457
|
Args:
|
|
458
458
|
eeobject: the object to add to the map.
|
|
459
|
-
vis_params: a dictionary of visualization parameters.
|
|
459
|
+
vis_params: a dictionary of visualization parameters. See
|
|
460
460
|
ee.data.getMapId().
|
|
461
461
|
*args: unused arguments, left for compatibility with the JS API.
|
|
462
462
|
|
|
463
463
|
This call exists to be an equivalent to the playground addToMap() call.
|
|
464
|
-
It uses a global MapInstance to hang on to "the map".
|
|
464
|
+
It uses a global MapInstance to hang on to "the map". If the MapInstance
|
|
465
465
|
isn't initialized, this creates a new one.
|
|
466
466
|
"""
|
|
467
467
|
del args # Unused.
|
ee/oauth.py
CHANGED
|
@@ -185,7 +185,7 @@ def request_token(
|
|
|
185
185
|
urllib.parse.urlencode(request_args).encode()).read().decode()
|
|
186
186
|
except urllib.error.HTTPError as e:
|
|
187
187
|
# pylint:disable=broad-exception-raised,raise-missing-from
|
|
188
|
-
raise Exception('Problem requesting tokens. Please try again.
|
|
188
|
+
raise Exception('Problem requesting tokens. Please try again. %s %s' %
|
|
189
189
|
(e, e.read()))
|
|
190
190
|
# pylint:enable=broad-exception-raised,raise-missing-from
|
|
191
191
|
|
|
@@ -352,7 +352,7 @@ def _nonce_table(*nonce_keys: str) -> dict[str, str]:
|
|
|
352
352
|
table[key] = _base64param(os.urandom(32))
|
|
353
353
|
if key.endswith('_verifier'):
|
|
354
354
|
# Generate a challenge that the server will use to ensure that requests
|
|
355
|
-
# only work with our verifiers.
|
|
355
|
+
# only work with our verifiers. https://tools.ietf.org/html/rfc7636
|
|
356
356
|
pkce_challenge = _base64param(hashlib.sha256(table[key]).digest())
|
|
357
357
|
table[key.replace('_verifier', '_challenge')] = pkce_challenge
|
|
358
358
|
return {k: v.decode() for k, v in table.items()}
|
|
@@ -445,7 +445,7 @@ def _start_server(port: int):
|
|
|
445
445
|
self.end_headers()
|
|
446
446
|
self.wfile.write(
|
|
447
447
|
b'\n\nGoogle Earth Engine authorization successful!\n\n\n'
|
|
448
|
-
b'Credentials have been retrieved.
|
|
448
|
+
b'Credentials have been retrieved. Please close this window.\n\n'
|
|
449
449
|
b' \xf0\x9f\x8c\x8d \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8f'
|
|
450
450
|
b' \xe2\x9a\x99\xef\xb8\x8f \xf0\x9f\x8c\x8e ') # Earth emoji
|
|
451
451
|
|
|
@@ -479,14 +479,14 @@ def authenticate(
|
|
|
479
479
|
"""Prompts the user to authorize access to Earth Engine via OAuth2.
|
|
480
480
|
|
|
481
481
|
Args:
|
|
482
|
-
cli_authorization_code: An optional authorization code.
|
|
482
|
+
cli_authorization_code: An optional authorization code. Supports CLI mode,
|
|
483
483
|
where the code is passed as an argument to `earthengine authenticate`.
|
|
484
484
|
quiet: If true, do not require interactive prompts and force --no-browser
|
|
485
485
|
mode for gcloud-legacy. If false, never supply --no-browser. Default is
|
|
486
486
|
None, which autodetects the --no-browser setting.
|
|
487
|
-
cli_code_verifier: PKCE verifier to prevent auth code stealing.
|
|
487
|
+
cli_code_verifier: PKCE verifier to prevent auth code stealing. Must be
|
|
488
488
|
provided if cli_authorization_code is given.
|
|
489
|
-
auth_mode: The authorization mode.
|
|
489
|
+
auth_mode: The authorization mode. One of:
|
|
490
490
|
"colab" - use the Colab authentication flow.
|
|
491
491
|
"notebook" - send user to notebook authenticator page. Intended for
|
|
492
492
|
web users who do not run code locally. Credentials expire in 7 days.
|
|
@@ -514,6 +514,13 @@ def authenticate(
|
|
|
514
514
|
Exception: on invalid arguments.
|
|
515
515
|
"""
|
|
516
516
|
|
|
517
|
+
if auth_mode == 'colab' and scopes is not None and set(scopes) != set(SCOPES):
|
|
518
|
+
raise ee_exception.EEException(
|
|
519
|
+
'Scopes cannot be customized when auth_mode is "colab". Please see'
|
|
520
|
+
' https://developers.google.com/earth-engine/guides/auth#quick_reference_guide_and_table'
|
|
521
|
+
' for more information.'
|
|
522
|
+
)
|
|
523
|
+
|
|
517
524
|
if cli_authorization_code:
|
|
518
525
|
_obtain_and_write_token(cli_authorization_code, cli_code_verifier, scopes)
|
|
519
526
|
return
|
|
@@ -550,6 +550,22 @@ class CloudApiUtilsTest(unittest.TestCase):
|
|
|
550
550
|
expected,
|
|
551
551
|
_cloud_api_utils.convert_sources_to_one_platform_sources(old_sources))
|
|
552
552
|
|
|
553
|
+
def test_convert_to_operation_state(self):
|
|
554
|
+
def convert(state):
|
|
555
|
+
return _cloud_api_utils.convert_to_operation_state(state)
|
|
556
|
+
|
|
557
|
+
self.assertEqual('CANCELLED', convert('CANCELLED'))
|
|
558
|
+
self.assertEqual('CANCELLING', convert('CANCEL_REQUESTED'))
|
|
559
|
+
self.assertEqual('CANCELLING', convert('CANCELLING'))
|
|
560
|
+
self.assertEqual('FAILED', convert('FAILED'))
|
|
561
|
+
self.assertEqual('PENDING', convert('PENDING'))
|
|
562
|
+
self.assertEqual('PENDING', convert('READY'))
|
|
563
|
+
self.assertEqual('RUNNING', convert('RUNNING'))
|
|
564
|
+
self.assertEqual('SUCCEEDED', convert('COMPLETED'))
|
|
565
|
+
self.assertEqual('SUCCEEDED', convert('SUCCEEDED'))
|
|
566
|
+
self.assertEqual('UNKNOWN', convert('random_string'))
|
|
567
|
+
self.assertEqual('UNKNOWN', convert('UNKNOWN'))
|
|
568
|
+
|
|
553
569
|
|
|
554
570
|
if __name__ == '__main__':
|
|
555
571
|
unittest.main()
|
ee/tests/_helpers_test.py
CHANGED
|
@@ -23,19 +23,19 @@ from ee import oauth
|
|
|
23
23
|
|
|
24
24
|
class ServiceAccountCredentialsTest(unittest.TestCase):
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def test_no_args(self):
|
|
27
27
|
with self.assertRaisesRegex(ValueError, 'At least one of'):
|
|
28
28
|
ee.ServiceAccountCredentials()
|
|
29
29
|
|
|
30
30
|
@mock.patch('google.oauth2.service_account.Credentials')
|
|
31
|
-
def
|
|
31
|
+
def test_json_file(self, mock_credentials):
|
|
32
32
|
ee.ServiceAccountCredentials(key_file='foo.json')
|
|
33
33
|
mock_credentials.from_service_account_file.assert_called_with(
|
|
34
34
|
'foo.json', scopes=oauth.SCOPES
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
@mock.patch('google.oauth2.service_account.Credentials')
|
|
38
|
-
def
|
|
38
|
+
def test_json_key_data(self, mock_credentials):
|
|
39
39
|
key_data = {'client_email': 'foo@bar.com'}
|
|
40
40
|
ee.ServiceAccountCredentials(key_data=json.dumps(key_data))
|
|
41
41
|
mock_credentials.from_service_account_info.assert_called_with(
|
|
@@ -44,7 +44,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
|
|
|
44
44
|
|
|
45
45
|
@mock.patch('google.auth.crypt.RSASigner')
|
|
46
46
|
@mock.patch('google.oauth2.service_account.Credentials')
|
|
47
|
-
def
|
|
47
|
+
def test_pem_key_data(self, mock_credentials, mock_signer):
|
|
48
48
|
ee.ServiceAccountCredentials(email='foo@bar.com', key_data='pem_key_data')
|
|
49
49
|
mock_signer.from_string.assert_called_with('pem_key_data')
|
|
50
50
|
self.assertEqual(
|
|
@@ -53,7 +53,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
|
|
|
53
53
|
|
|
54
54
|
@mock.patch('google.auth.crypt.RSASigner')
|
|
55
55
|
@mock.patch('google.oauth2.service_account.Credentials')
|
|
56
|
-
def
|
|
56
|
+
def test_pem_file(self, mock_credentials, mock_signer):
|
|
57
57
|
with mock.patch.object(
|
|
58
58
|
_helpers, 'open', mock.mock_open(read_data='pem_key_data')
|
|
59
59
|
):
|
|
@@ -63,7 +63,7 @@ class ServiceAccountCredentialsTest(unittest.TestCase):
|
|
|
63
63
|
mock_credentials.call_args[0][1], 'foo@bar.com'
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def test_bad_json_key_data(self):
|
|
67
67
|
# This causes a different failure based on where the test is run.
|
|
68
68
|
message = r'Could not deserialize key data|No key could be detected'
|
|
69
69
|
with self.assertRaisesRegex(ValueError, message):
|
|
@@ -80,21 +80,21 @@ class ProfilingTest(apitestcase.ApiTestCase):
|
|
|
80
80
|
) and value.func == apifunction.ApiFunction.lookup('Profile.getProfiles')
|
|
81
81
|
return f'hooked={hooked} getProfiles={is_get_profiles}'
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def test_profile_printing(self):
|
|
84
84
|
ee.data.computeValue = self.MockValue
|
|
85
85
|
out = io.StringIO()
|
|
86
86
|
with ee.profilePrinting(destination=out):
|
|
87
87
|
self.assertEqual('hooked=True getProfiles=False', ee.Number(1).getInfo())
|
|
88
88
|
self.assertEqual('hooked=False getProfiles=True', out.getvalue())
|
|
89
89
|
|
|
90
|
-
def
|
|
90
|
+
def test_profile_printing_default_smoke(self):
|
|
91
91
|
# This will print to sys.stderr, so we can't make any assertions about the
|
|
92
92
|
# output. But we can check that it doesn't fail.
|
|
93
93
|
ee.data.computeValue = self.MockValue
|
|
94
94
|
with ee.profilePrinting():
|
|
95
95
|
self.assertEqual('hooked=True getProfiles=False', ee.Number(1).getInfo())
|
|
96
96
|
|
|
97
|
-
def
|
|
97
|
+
def test_profile_printing_error_getting_profiles(self):
|
|
98
98
|
ee.data.computeValue = self.MockValue
|
|
99
99
|
mock = unittest.mock.Mock()
|
|
100
100
|
mock.call.side_effect = ee_exception.EEException('test')
|