Skip to content

Commit 1fdc0f1

Browse files
sufyanAbbasiGoogle Earth Engine Authors
authored andcommitted
Handle unbound arguments from map and iterate calls.
PiperOrigin-RevId: 681170281
1 parent f2e8070 commit 1fdc0f1

12 files changed

Lines changed: 52 additions & 28 deletions

File tree

javascript/src/collection.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ goog.requireType('ee.Geometry');
2121
* @param {ee.Function} func The same argument as in ee.ComputedObject().
2222
* @param {Object} args The same argument as in ee.ComputedObject().
2323
* @param {string?=} opt_varName The same argument as in ee.ComputedObject().
24+
* @param {boolean?=} opt_unbound The same argument as in ee.ComputedObject().
2425
* @constructor
2526
* @extends {ee.Element}
2627
*/
27-
ee.Collection = function(func, args, opt_varName) {
28-
ee.Collection.base(this, 'constructor', func, args, opt_varName);
28+
ee.Collection = function(func, args, opt_varName, opt_unbound) {
29+
ee.Collection.base(this, 'constructor', func, args, opt_varName, opt_unbound);
2930
ee.Collection.initialize();
3031
};
3132
goog.inherits(ee.Collection, ee.Element);
@@ -219,8 +220,12 @@ ee.Collection.prototype.elementType = function() {
219220
* @export
220221
*/
221222
ee.Collection.prototype.map = function(algorithm, opt_dropNulls) {
222-
var elementType = this.elementType();
223-
var withCast = function(e) { return algorithm(new elementType(e)); };
223+
const elementType = this.elementType();
224+
const withCast = function(e) {
225+
const el = /** @type {!ee.ComputedObject} */ (new elementType(e));
226+
el.unbound = true;
227+
return algorithm(el);
228+
};
224229
return this.castInternal(ee.ApiFunction._call(
225230
'Collection.map', this, withCast, opt_dropNulls));
226231
};
@@ -241,8 +246,12 @@ ee.Collection.prototype.map = function(algorithm, opt_dropNulls) {
241246
* @export
242247
*/
243248
ee.Collection.prototype.iterate = function(algorithm, opt_first) {
244-
var first = (opt_first !== undefined) ? opt_first : null;
245-
var elementType = this.elementType();
246-
var withCast = function(e, p) { return algorithm(new elementType(e), p); };
249+
const first = (opt_first !== undefined) ? opt_first : null;
250+
const elementType = this.elementType();
251+
const withCast = function(e, p) {
252+
const el = /** @type {!ee.ComputedObject} */ (new elementType(e));
253+
el.unbound = true;
254+
return algorithm(el, p);
255+
};
247256
return ee.ApiFunction._call('Collection.iterate', this, withCast, first);
248257
};

javascript/src/computedobject.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ goog.requireType('ee.Function');
3939
* and both 'func' and 'args' must be null. If all arguments are null, the
4040
* object is considered an unnamed variable, and a name will be generated
4141
* when it is included in an ee.CustomFunction.
42+
* @param {?boolean=} opt_unbound Whether the object is unbound, i.e., called
43+
* from a mapped or iterated function.
4244
* @constructor
4345
* @extends {ee.Encodable}
4446
* @template T
4547
*/
46-
ee.ComputedObject = function(func, args, opt_varName) {
48+
ee.ComputedObject = function(func, args, opt_varName, opt_unbound) {
4749
// Constructor safety.
4850
if (!(this instanceof ee.ComputedObject)) {
4951
return ee.ComputedObject.construct(ee.ComputedObject, arguments);
@@ -76,6 +78,12 @@ ee.ComputedObject = function(func, args, opt_varName) {
7678
* @protected
7779
*/
7880
this.varName = opt_varName || null;
81+
82+
/**
83+
* Whether the computed object is an unbound variable.
84+
* @type {boolean}
85+
*/
86+
this.unbound = !!opt_unbound;
7987
};
8088
goog.inherits(ee.ComputedObject, ee.Encodable);
8189
// Exporting manually to avoid marking the class public in the docs.
@@ -153,14 +161,20 @@ ee.ComputedObject.prototype.encodeCloudValue = function(serializer) {
153161
if (this.isVariable()) {
154162
const name = this.varName || serializer.unboundName;
155163
if (!name) {
156-
// We are trying to call getInfo() or make some other server call inside a
157-
// function passed to collection.map() or .iterate(), and the call uses
158-
// one of the function arguments. The argument will be unbound outside of
159-
// the map operation and cannot be evaluated. See the Count Functions case
160-
// in customfunction.js for details on the unboundName mechanism.
161-
// TODO(user): Report the name of the offending argument.
162-
throw new Error(
163-
'A mapped function\'s arguments cannot be used in client-side operations');
164+
if (this.unbound) {
165+
// We are trying to call getInfo() or make some other server call inside
166+
// a function passed to collection.map() or .iterate(), and the call
167+
// uses one of the function arguments. The argument will be unbound
168+
// outside of the map operation and cannot be evaluated. See the Count
169+
// Functions case in customfunction.js for details on the unboundName
170+
// mechanism.
171+
// TODO(user): Report the name of the offending argument.
172+
throw new Error(`A mapped function's arguments (${
173+
this.name()}) cannot be used in client-side operations`);
174+
} else {
175+
throw new Error(
176+
`Invalid cast to ${this.name()} from a client-side object`);
177+
}
164178
}
165179
return ee.rpc_node.argumentReference(name);
166180
} else {

javascript/src/dictionary.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ee.Dictionary = function(opt_dict) {
5151
if (opt_dict instanceof ee.ComputedObject && opt_dict.func &&
5252
opt_dict.func.getSignature()['returns'] == 'Dictionary') {
5353
// If it's a call that's already returning a Dictionary, just cast.
54-
ee.Dictionary.base(this, 'constructor', opt_dict.func, opt_dict.args, opt_dict.varName);
54+
ee.Dictionary.base(this, 'constructor', opt_dict.func, opt_dict.args, opt_dict.varName, opt_dict.unbound);
5555
} else {
5656
// Delegate everything else to the server-side constructor.
5757
ee.Dictionary.base(

javascript/src/element.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ goog.requireType('ee.Function');
1919
* @param {ee.Function} func The same argument as in ee.ComputedObject().
2020
* @param {Object} args The same argument as in ee.ComputedObject().
2121
* @param {string?=} opt_varName The same argument as in ee.ComputedObject().
22+
* @param {boolean?=} opt_unbound The same argument as in ee.ComputedObject().
2223
* @constructor
2324
* @extends {ee.ComputedObject}
2425
*/
25-
ee.Element = function(func, args, opt_varName) {
26-
ee.Element.base(this, 'constructor', func, args, opt_varName);
26+
ee.Element = function(func, args, opt_varName, opt_unbound) {
27+
ee.Element.base(this, 'constructor', func, args, opt_varName, opt_unbound);
2728
ee.Element.initialize();
2829
};
2930
goog.inherits(ee.Element, ee.ComputedObject);

javascript/src/feature.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ ee.Feature = function(geometry, opt_properties) {
5757
});
5858
} else if (geometry instanceof ee.ComputedObject) {
5959
// A custom object to reinterpret as a Feature.
60-
ee.Feature.base(this, 'constructor', geometry.func, geometry.args, geometry.varName);
60+
ee.Feature.base(this, 'constructor', geometry.func, geometry.args, geometry.varName, geometry.unbound);
6161
} else if (geometry['type'] == 'Feature') {
6262
// Try to convert a GeoJSON Feature.
6363
var properties = geometry['properties'] || {};

javascript/src/filter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ ee.Filter = function(opt_filter) {
6565
}
6666
} else if (opt_filter instanceof ee.ComputedObject) {
6767
// Actual filter object.
68-
ee.Filter.base(this, 'constructor', opt_filter.func, opt_filter.args, opt_filter.varName);
68+
ee.Filter.base(this, 'constructor', opt_filter.func, opt_filter.args, opt_filter.varName, opt_filter.unbound);
6969
this.filter_ = [opt_filter];
7070
} else if (opt_filter === undefined) {
7171
// A silly call with no arguments left for backward-compatibility.

javascript/src/geometry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ ee.Geometry = function(geoJson, opt_proj, opt_geodesic, opt_evenOdd) {
8787
'Setting the CRS, geodesic, or evenOdd flag on a computed Geometry ' +
8888
'is not supported. Use Geometry.transform().');
8989
} else {
90-
ee.Geometry.base(this, 'constructor', geoJson.func, geoJson.args, geoJson.varName);
90+
ee.Geometry.base(this, 'constructor', geoJson.func, geoJson.args, geoJson.varName, geoJson.unbound);
9191
return;
9292
}
9393
}

javascript/src/image.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ ee.Image = function(opt_args) {
7777
{'value': opt_args});
7878
} else {
7979
// A custom object to reinterpret as an Image.
80-
ee.Image.base(this, 'constructor', opt_args.func, opt_args.args, opt_args.varName);
80+
ee.Image.base(this, 'constructor', opt_args.func, opt_args.args, opt_args.varName, opt_args.unbound);
8181
}
8282
} else {
8383
throw Error('Unrecognized argument type to convert to an Image: ' +

javascript/src/imagecollection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ ee.ImageCollection = function(args) {
7070
});
7171
} else if (args instanceof ee.ComputedObject) {
7272
// A custom object to reinterpret as an ImageCollection.
73-
ee.ImageCollection.base(this, 'constructor', args.func, args.args, args.varName);
73+
ee.ImageCollection.base(this, 'constructor', args.func, args.args, args.varName, args.unbound);
7474
} else {
7575
throw Error('Unrecognized argument type to convert to an ' +
7676
'ImageCollection: ' + args);

javascript/src/list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ ee.List = function(list) {
4545
ee.List.base(this, 'constructor', null, null);
4646
this.list_ = /** @type {IArrayLike} */ (list);
4747
} else if (list instanceof ee.ComputedObject) {
48-
ee.List.base(this, 'constructor', list.func, list.args, list.varName);
48+
ee.List.base(this, 'constructor', list.func, list.args, list.varName, list.unbound);
4949
this.list_ = null;
5050
} else {
5151
throw Error('Invalid argument specified for ee.List(): ' + list);

0 commit comments

Comments
 (0)