Skip to content

Commit bb9c99d

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

File tree

13 files changed

+45
-25
lines changed

13 files changed

+45
-25
lines changed

javascript/src/collection.js

Lines changed: 3 additions & 2 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);

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/customfunction.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ ee.CustomFunction.variable = function(type, name) {
138138
this.func = null;
139139
this.args = null;
140140
this.varName = name;
141+
this.unbound = true;
141142
};
142143
klass.prototype = type.prototype;
143144
return new klass(name);

javascript/src/dictionary.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,19 @@ ee.Dictionary = function(opt_dict) {
4646
if (ee.Types.isRegularObject(opt_dict)) {
4747
// Cast to a dictionary.
4848
ee.Dictionary.base(this, 'constructor', null, null);
49-
this.dict_ = /** @type {Object} */ (opt_dict);
49+
this.dict_ = /** @type {!Object} */ (opt_dict);
5050
} else {
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 {
56+
const unbound =
57+
opt_dict instanceof ee.ComputedObject ? opt_dict.unbound : null;
5658
// Delegate everything else to the server-side constructor.
5759
ee.Dictionary.base(
58-
this, 'constructor', new ee.ApiFunction('Dictionary'), {'input': opt_dict}, null);
60+
this, 'constructor', new ee.ApiFunction('Dictionary'),
61+
{'input': opt_dict}, null, unbound);
5962
}
6063
this.dict_ = null;
6164
}

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);

0 commit comments

Comments
 (0)