From 387e4517c279b30f6a332725eac0a2368b826eca Mon Sep 17 00:00:00 2001 From: Google Earth Engine Authors Date: Wed, 20 Mar 2024 19:40:49 -0700 Subject: [PATCH] v0.1.396 PiperOrigin-RevId: 617697857 --- javascript/build/ee_api_js.js | 293 ++--- javascript/build/ee_api_js_debug.js | 27 +- javascript/build/ee_api_js_npm.js | 70 +- javascript/package.json | 2 +- javascript/src/apiclient.js | 2 +- javascript/src/data.js | 47 +- .../Demos/Landsat8HarmonicModeling.js | 4 +- python/ee/__init__.py | 2 +- python/ee/_cloud_api_utils.py | 16 +- python/ee/batch.py | 37 +- python/ee/data.py | 18 +- python/ee/daterange.py | 81 ++ python/ee/deprecation.py | 3 +- python/ee/ee_array.py | 6 +- python/ee/ee_date.py | 2 +- python/ee/ee_list.py | 585 ++++++++++ python/ee/featurecollection.py | 1 + python/ee/image.py | 1 + python/ee/imagecollection.py | 2 + python/ee/tests/algorithms.json | 38 +- python/ee/tests/batch_test.py | 1020 ++++++++++------- python/ee/tests/data_test.py | 90 +- python/ee/tests/daterange_test.py | 132 ++- python/ee/tests/deprecation_test.py | 175 +++ python/ee/tests/ee_list_test.py | 606 +++++++++- python/pyproject.toml | 2 +- 26 files changed, 2572 insertions(+), 690 deletions(-) diff --git a/javascript/build/ee_api_js.js b/javascript/build/ee_api_js.js index 80b4a0aec..887bf2842 100644 --- a/javascript/build/ee_api_js.js +++ b/javascript/build/ee_api_js.js @@ -10,8 +10,8 @@ da("Promise",function(a){function b(){this.pb=null}function c(f){return f instan })};var e=function(f){this.T=0;this.ia=void 0;this.uc=[];this.Lh=!1;var l=this.cf();try{f(l.resolve,l.reject)}catch(m){l.reject(m)}};e.prototype.cf=function(){function f(p){return function(v){m||(m=!0,p.call(l,v))}}var l=this,m=!1;return{resolve:f(this.vl),reject:f(this.ag)}};e.prototype.vl=function(f){if(f===this)this.ag(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof e)this.xl(f);else{a:switch(typeof f){case "object":var l=null!=f;break a;case "function":l=!0;break a;default:l= !1}l?this.ul(f):this.Ch(f)}};e.prototype.ul=function(f){var l=void 0;try{l=f.then}catch(m){this.ag(m);return}"function"==typeof l?this.yl(l,f):this.Ch(f)};e.prototype.ag=function(f){this.zi(2,f)};e.prototype.Ch=function(f){this.zi(1,f)};e.prototype.zi=function(f,l){if(0!=this.T)throw Error("Cannot settle("+f+", "+l+"): Promise already settled in state"+this.T);this.T=f;this.ia=l;2===this.T&&this.wl();this.Wk()};e.prototype.wl=function(){var f=this;d(function(){if(f.ml()){var l=k.console;"undefined"!== typeof l&&l.error(f.ia)}},1)};e.prototype.ml=function(){if(this.Lh)return!1;var f=k.CustomEvent,l=k.Event,m=k.dispatchEvent;if("undefined"===typeof m)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):"function"===typeof l?f=new l("unhandledrejection",{cancelable:!0}):(f=k.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.ia;return m(f)};e.prototype.Wk=function(){if(null!=this.uc){for(var f=0;f>>0),Ba=0,Da=function(a,b,c){return a.call.apply(a.bind, arguments)},Ea=function(a,b,c){if(!a)throw Error();if(2c&&(c=Math.max(0,a.length+c));if("string"===typeof a)return"string"!==typeof b|| 1!=b.length?-1:a.lastIndexOf(b,c);for(;0<=c;c--)if(c in a&&a[c]===b)return c;return-1},Ua=Array.prototype.forEach?function(a,b){B(null!=a.length);Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d="string"===typeof a?a.split(""):a,e=0;ea-0)return[];for(var c=0;c>>0),cc=function(a){B(a,"Listener can not be null.");if("function"===typeof a)return a;B(a.handleEvent,"An object listener must have handleEvent method.");a[kc]||(a[kc]=function(b){return a.handleEvent(b)}); +a.Wc(b,c,t(d)?!!d.capture:!!d,e):dc(a,b,c,!0,d,e)},ic=function(a,b,c,d,e){if(Array.isArray(b))for(var g=0;g>>0),cc=function(a){B(a,"Listener can not be null.");if("function"===typeof a)return a;B(a.handleEvent,"An object listener must have handleEvent method.");a[kc]||(a[kc]=function(b){return a.handleEvent(b)}); return a[kc]};var D=function(){y.call(this);this.Ia=new Vb(this);this.Kk=this;this.Uf=null};x(D,y);D.prototype[Ab]=!0;h=D.prototype;h.addEventListener=function(a,b,c,d){bc(this,a,b,c,d)};h.removeEventListener=function(a,b,c,d){ic(this,a,b,c,d)}; -h.dispatchEvent=function(a){lc(this);var b=this.Uf;if(b){var c=[];for(var d=1;b;b=b.Uf)c.push(b),B(1E3>++d,"infinite loop")}b=this.Kk;d=a.type||a;if("string"===typeof a)a=new z(a,b);else if(a instanceof z)a.target=a.target||b;else{var e=a;a=new z(d,b);Ub(a,e)}e=!0;if(c)for(var g=c.length-1;!a.cd&&0<=g;g--){var f=a.currentTarget=c[g];e=mc(f,d,!0,a)&&e}a.cd||(f=a.currentTarget=b,e=mc(f,d,!0,a)&&e,a.cd||(e=mc(f,d,!1,a)&&e));if(c)for(g=0;!a.cd&&g++d,"infinite loop")}b=this.Kk;d=a.type||a;if("string"===typeof a)a=new z(a,b);else if(a instanceof z)a.target=a.target||b;else{var e=a;a=new z(d,b);Ub(a,e)}e=!0;if(c)for(var g=c.length-1;!a.dd&&0<=g;g--){var f=a.currentTarget=c[g];e=mc(f,d,!0,a)&&e}a.dd||(f=a.currentTarget=b,e=mc(f,d,!0,a)&&e,a.dd||(e=mc(f,d,!1,a)&&e));if(c)for(g=0;!a.dd&&g=a.length)return sc;if(b in a)return{value:a[b++],done:!1};b++}};return c}throw Error("Not implemented");},uc=function(a,b){if(ya(a))Ua(a,b);else for(a=tc(a);;){var c=a.next();if(c.done)break;b.call(void 0,c.value,void 0,a)}};var yc=function(a){if(a instanceof vc||a instanceof wc||a instanceof xc)return a;if("function"==typeof a.next)return new vc(function(){return a});if("function"==typeof a[Symbol.iterator])return new vc(function(){return a[Symbol.iterator]()});if("function"==typeof a.Da)return new vc(function(){return a.Da()});throw Error("Not an iterator or iterable.");},vc=function(a){this.uf=a};vc.prototype.Da=function(){return new wc(this.uf())};vc.prototype[Symbol.iterator]=function(){return new xc(this.uf())}; -vc.prototype.xg=function(){return new xc(this.uf())};var wc=function(a){this.Sc=a};q(wc,rc);wc.prototype.next=function(){return this.Sc.next()};wc.prototype[Symbol.iterator]=function(){return new xc(this.Sc)};wc.prototype.xg=function(){return new xc(this.Sc)};var xc=function(a){vc.call(this,function(){return a});this.Sc=a};q(xc,vc);xc.prototype.next=function(){return this.Sc.next()};var zc=function(a,b){this.A={};this.K=[];this.od=this.size=0;var c=arguments.length;if(12*this.size&&Ac(this),!0):!1};var Ac=function(a){if(a.size!=a.K.length){for(var b=0,c=0;b=d.K.length)return sc;var g=d.K[b++];return{value:a?g:d.A[g],done:!1}};return e};h.mb=function(a){this.size=a};var Bc=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};var Dc=function(a){if(a.R&&"function"==typeof a.R)a=a.R();else if(ya(a)||"string"===typeof a)a=a.length;else{var b=0,c;for(c in a)b++;a=b}return a},Ec=function(a){if(a.aa&&"function"==typeof a.aa)return a.aa();if("undefined"!==typeof Map&&a instanceof Map||"undefined"!==typeof Set&&a instanceof Set)return Array.from(a.values());if("string"===typeof a)return a.split("");if(ya(a)){for(var b=[],c=a.length,d=0;d2*this.size&&Ac(this),!0):!1};var Ac=function(a){if(a.size!=a.K.length){for(var b=0,c=0;b=d.K.length)return sc;var g=d.K[b++];return{value:a?g:d.A[g],done:!1}};return e};h.mb=function(a){this.size=a};var Bc=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};var Dc=function(a){if(a.R&&"function"==typeof a.R)a=a.R();else if(ya(a)||"string"===typeof a)a=a.length;else{var b=0,c;for(c in a)b++;a=b}return a},Ec=function(a){if(a.aa&&"function"==typeof a.aa)return a.aa();if("undefined"!==typeof Map&&a instanceof Map||"undefined"!==typeof Set&&a instanceof Set)return Array.from(a.values());if("string"===typeof a)return a.split("");if(ya(a)){for(var b=[],c=a.length,d=0;dc)return!1;!(b instanceof Ic)&&5e&&(e+=d);return[a.il,c,e,b.y].join("/")};Nc.prototype.Nc=function(){return this.Ab.length};var Pc=function(a){z.call(this,"tileevent");this.count=a};x(Pc,z);var Qc=function(){},Rc=new Qc;function Sc(a){return Object.assign({},{P:{},Nl:{},keys:[],Ma:{},s:{},H:{},sh:!1},a)}var E=function(){this.h={}},F=function(a,b){return a.h.hasOwnProperty(b)?a.h[b]:null},G=function(a,b){return null!=a.h[b]};function Tc(a,b){return F(b,a)}function Uc(a,b,c){b[a]=c}function Vc(){return{}}function Wc(a,b){var c=new a;return null==b?c:Xc(b,Yc,Zc,$c,a)}function Yc(a,b){return b[a]}function Zc(a,b,c){b.h[a]=c} function $c(a){if(null==a)throw Error("Cannot deserialize, target constructor was null.");return new a} @@ -80,9 +80,9 @@ var gd=function(a){if(a instanceof fd&&a.constructor===fd)return a.li;Na("expect SPDX-License-Identifier: Apache-2.0 */ var id=ia([""]),jd=ja(["\x00"],["\\0"]),kd=ja(["\n"],["\\n"]),ld=ja(["\x00"],["\\u0000"]),md=ia([""]),nd=ja(["\x00"],["\\0"]),od=ja(["\n"],["\\n"]),pd=ja(["\x00"],["\\u0000"]);function qd(a){return Object.isFrozen(a)&&Object.isFrozen(a.raw)}function rd(a){return-1===a.toString().indexOf("`")}var sd=rd(function(a){return a(id)})||rd(function(a){return a(jd)})||rd(function(a){return a(kd)})||rd(function(a){return a(ld)}),td=qd(md)&&qd(nd)&&qd(od)&&qd(pd);var vd=function(a,b){if(b!==ud)throw Error("SafeUrl is not meant to be built directly");this.ki=a};vd.prototype.toString=function(){return this.ki.toString()};var wd=function(a){if(a instanceof vd&&a.constructor===vd)return a.ki;Na("expected object of type SafeUrl, got '"+a+"' of type "+xa(a));return"type_error:SafeUrl"},ud={},xd=function(a){return new vd(a,ud)};xd("about:invalid#zClosurez");xd("about:blank");xd("about:blank");var yd=xd("about:invalid#zClosurez");function zd(a){if("undefined"!==typeof MediaSource&&a instanceof MediaSource)return xd(URL.createObjectURL(a));var b=a.type.match(/^([^;]+)(?:;\w+=(?:\w+|"[\w;,= ]+"))*$/i);if(2!==(null==b?void 0:b.length)||!(/^image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp|x-icon|heic|heif|avif|x-ms-bmp)$/i.test(b[1])||/^video\/(?:mpeg|mp4|ogg|webm|x-matroska|quicktime|x-ms-wmv)$/i.test(b[1])||/^audio\/(?:3gpp2|3gpp|aac|amr|L16|midi|mp3|mp4|mpeg|oga|ogg|opus|x-m4a|x-matroska|x-wav|wav|webm)$/i.test(b[1])||/^font\/\w+/i.test(b[1])))throw Error("unsafe blob MIME type: "+ -a.type);return xd(URL.createObjectURL(a))}var Ad=[],Bd=function(a){console.warn("A URL with content '"+a+"' was sanitized away.")};-1===Ad.indexOf(Bd)&&Ad.push(Bd);var Cd={},Dd=function(){if(Cd!==Cd)throw Error("SafeStyle is not meant to be built directly");this.ql=""};Dd.prototype.toString=function(){return this.ql.toString()};new Dd;var Ed={},Fd=function(){if(Ed!==Ed)throw Error("SafeStyleSheet is not meant to be built directly");this.pl=""};Fd.prototype.toString=function(){return this.pl.toString()};new Fd;var Gd={},Hd=function(){var a=r.trustedTypes&&r.trustedTypes.emptyHTML||"";if(Gd!==Gd)throw Error("SafeHtml is not meant to be built directly");this.ol=a};Hd.prototype.toString=function(){return this.ol.toString()};new Hd;var Id=function(a,b){this.name=a;this.value=b};Id.prototype.toString=function(){return this.name};var Jd=new Id("OFF",Infinity),Kd=new Id("SEVERE",1E3),Ld=new Id("CONFIG",700),Md=new Id("FINE",500),Nd=function(){this.Bd=0;this.clear()},Od;Nd.prototype.clear=function(){this.yd=Array(this.Bd);this.kh=-1;this.Kh=!1};var Pd=function(a,b,c){this.reset(a||Jd,b,c,void 0,void 0)};Pd.prototype.reset=function(){}; +a.type);return xd(URL.createObjectURL(a))}var Ad=[],Bd=function(a){console.warn("A URL with content '"+a+"' was sanitized away.")};-1===Ad.indexOf(Bd)&&Ad.push(Bd);var Cd={},Dd=function(){if(Cd!==Cd)throw Error("SafeStyle is not meant to be built directly");this.ql=""};Dd.prototype.toString=function(){return this.ql.toString()};new Dd;var Ed={},Fd=function(){if(Ed!==Ed)throw Error("SafeStyleSheet is not meant to be built directly");this.pl=""};Fd.prototype.toString=function(){return this.pl.toString()};new Fd;var Gd={},Hd=function(){var a=r.trustedTypes&&r.trustedTypes.emptyHTML||"";if(Gd!==Gd)throw Error("SafeHtml is not meant to be built directly");this.ol=a};Hd.prototype.toString=function(){return this.ol.toString()};new Hd;var Id=function(a,b){this.name=a;this.value=b};Id.prototype.toString=function(){return this.name};var Jd=new Id("OFF",Infinity),Kd=new Id("SEVERE",1E3),Ld=new Id("CONFIG",700),Md=new Id("FINE",500),Nd=function(){this.Cd=0;this.clear()},Od;Nd.prototype.clear=function(){this.zd=Array(this.Cd);this.kh=-1;this.Kh=!1};var Pd=function(a,b,c){this.reset(a||Jd,b,c,void 0,void 0)};Pd.prototype.reset=function(){}; var Qd=function(a,b){this.level=null;this.Zk=[];this.parent=(void 0===b?null:b)||null;this.children=[];this.gl={wf:function(){return a}}},Rd=function(a){if(a.level)return a.level;if(a.parent)return Rd(a.parent);Na("Root logger has no level set.");return Jd},Sd=function(a,b){for(;a;)a.Zk.forEach(function(c){c(b)}),a=a.parent},Td=function(){this.entries={};var a=new Qd("");a.level=Ld;this.entries[""]=a},Ud,Vd=function(a,b){var c=a.entries[b];if(c)return c;c=Vd(a,b.slice(0,Math.max(b.lastIndexOf("."), -0)));var d=new Qd(b,c);a.entries[b]=d;c.children.push(d);return d},Wd=function(){Ud||(Ud=new Td);return Ud},Xd=function(a,b,c){var d;if(d=a)if(d=a&&b){d=b.value;var e=a?Rd(Vd(Wd(),a.wf())):Jd;d=d>=e.value}if(d){b=b||Jd;d=Vd(Wd(),a.wf());"function"===typeof c&&(c=c());Od||(Od=new Nd);e=Od;a=a.wf();if(0=e.value}if(d){b=b||Jd;d=Vd(Wd(),a.wf());"function"===typeof c&&(c=c());Od||(Od=new Nd);e=Od;a=a.wf();if(0"}else d=void 0===a?"undefined":null===a?"null":typeof a;Na("Argument is not an HTML Element with tag name "+(c+d))}a:{c=(a.ownerDocument&&a.ownerDocument.defaultView|| r).document;if(c.querySelector&&(c=c.querySelector("script[nonce]"))&&(c=c.nonce||c.getAttribute("nonce"))&&Ph.test(c))break a;c=""}c&&a.setAttribute("nonce",c);a.src=gd(b)},Ph=/^[\w+/_-]+[=]{0,2}$/;var Sh=function(a,b){Fb(b,function(c,d){"style"==d?a.style.cssText=c:"class"==d?a.className=c:"for"==d?a.htmlFor=c:Rh.hasOwnProperty(d)?a.setAttribute(Rh[d],c):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,c):a[d]=c})},Rh={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",nonce:"nonce",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"},Uh=function(a, b,c){return Th(document,arguments)},Th=function(a,b){var c=b[1],d=Vh(a,String(b[0]));c&&("string"===typeof c?d.className=c:Array.isArray(c)?d.className=c.join(" "):Sh(d,c));2\r\n\r\n"+m+"\r\n"}).join("")+"--batch_EARTHENGINE_batch--\r\n",g=function(f){var l={};a.forEach(function(m){var p=n(m);m=p.next().value;p=n(p.next().value);p.next();p=p.next().value;null!=f[m]&&(l[m]= Wc(p,f[m]))});return b?b(l):l};return this.callback?(Jj(d,null,function(f,l){return c.callback(l?f:g(f),l)},"multipart/mixed; boundary=batch_EARTHENGINE_batch",e,void 0,this.Rb),null):g(Jj(d,null,void 0,"multipart/mixed; boundary=batch_EARTHENGINE_batch",e,void 0,this.Rb))};var Kj=function(){};q(Kj,Mh); Kj.prototype.send=function(a,b){var c=[a.B+" "+a.path+" HTTP/1.1"];c.push("Content-Type: application/json; charset=utf-8");var d=Mj();null!=d&&c.push("Authorization: "+d);a=a.body?JSON.stringify(a.body):"";return[c.join("\r\n")+"\r\n\r\n"+a,b]}; var Nj=function(a,b,c){a=n(b.split("--"+a.split("; boundary=")[1]));for(b=a.next();!b.done;b=a.next())if(b=b.value.split("\r\n\r\n"),!(3>b.length)){var d=b[0].match(/\r\nContent-ID: ]*)>/)[1],e=Number(b[1].match(/^HTTP\S*\s(\d+)\s/)[1]);c(d,e,b.slice(2).join("\r\n\r\n"))}},Hj=function(){var a=Oj.replace(/\/api$/,"");return"window"in r&&!a.match(/^https?:\/\/content-/)?a.replace(/^(https?:\/\/)(.*\.googleapis\.com)$/,"$1content-$2"):a},Qj=function(a,b,c){var d=[];a&&(d=d.concat(Pj)); b&&d.push("https://www.googleapis.com/auth/devstorage.read_write");a=d=d.concat(c);c=b=0;for(var e={};cg)break;A++}return lk(C.status,function(J){try{return C.getResponseHeader(J)}catch(fa){return null}},C.responseText,l,void 0,e,d,f)},jk=function(a,b,c,d,e,g,f){var l=0,m={url:a,method:c,content:d,headers:e},p=dk,v=null!=g?g:10; m.callback=function(A){A=A.target;if(429==A.getStatus()&&lX||300<=X)return"Server returned HTTP code: "+X+" for "+f+" "+g},A,C;b=b("Content-Type")||"application/json";d=b.replace(/;.*/,"");if("application/json"===d||"text/json"===d)c=p(c),c.parsed? C=c.parsed:A=c;else if("multipart/mixed"===d){C={};var J=[];Nj(b,c,function(X,ea,Jb){Jb=p(Jb);Jb.parsed&&(C[X]=Jb.parsed);(ea=(Jb.parsed?"":Jb)||v(ea))&&J.push(X+": "+ea)});J.length&&(A=J.join("\n"))}else var fa="Response was unexpectedly not JSON, but "+d;A=A||v(a)||fa;if(e)return e(C,A),null;if(!A)return C;throw Error(A);},Uj=function(a){var b=function(){Rj||(Rj=function(c,d){r.google.accounts.oauth2.initTokenClient({client_id:c.client_id,callback:d,scope:c.scope}).requestAccessToken()});a()};t(r.default_gsi)? b():Ui(b)},Vj=function(a,b,c){if(c.access_token){b=c.token_type+" "+c.access_token;if(c.expires_in||0===c.expires_in){c=900*c.expires_in;var d=setTimeout(Wj,.9*c);void 0!==d.unref&&d.unref();Xj=Date.now()+c}Zj=b;a&&a()}else b&&b(c.error||"Unknown error.")},Ij=function(a){var b=new ke;a=n(Object.entries(a));for(var c=a.next();!c.done;c=a.next()){var d=n(c.value);c=d.next().value;d=d.next().value;b.set(c,d)}return b},ik=[],kk=new Hi(function(){var a=ik.shift();if(a){var b=a.url,c=a.callback,d=a.method, -e=a.content;a=a.headers;var g=mk,f=new pj;sj.push(f);c&&f.Xa("complete",c);f.Vc("ready",f.Qk);g&&(f.zc=Math.max(0,g));f.send(b,d,e,a)}0==ik.length||kk.qf()},350),Oj=null,bk=null,ck=null,hk=null,gk=qc,Zj=null,Xj=null,Sj=null,Tj=[],Rj=null,Pj=["https://www.googleapis.com/auth/earthengine","https://www.googleapis.com/auth/cloud-platform"],fk=null,ak=!1,mk=0,dk=null;w("ee.api.ListAssetsResponse",Vg);w("ee.api.EarthEngineAsset",Rf);w("ee.api.Operation",Yg);w("ee.api.ListFeaturesResponse",Wg); +e=a.content;a=a.headers;var g=mk,f=new pj;sj.push(f);c&&f.Xa("complete",c);f.Wc("ready",f.Qk);g&&(f.zc=Math.max(0,g));f.send(b,d,e,a)}0==ik.length||kk.qf()},350),Oj=null,bk=null,ck=null,hk=null,gk=qc,Zj=null,Xj=null,Sj=null,Tj=[],Rj=null,Pj=["https://www.googleapis.com/auth/earthengine","https://www.googleapis.com/auth/cloud-platform"],fk=null,ak=!1,mk=0,dk=null;w("ee.api.ListAssetsResponse",Vg);w("ee.api.EarthEngineAsset",Rf);w("ee.api.Operation",Yg);w("ee.api.ListFeaturesResponse",Wg); w("ee.api.FeatureViewLocation",Uf);var nk=function(){};nk.prototype.Eh=function(){return null};var ok=function(){};w("ee.Encodable.SourceFrame",ok); var pk=function(a){if(void 0===a||null===a)a=Rc;return new rf({hb:a})},qk=function(a){return new rf({Oa:a})},rk=function(a){return new rf({Fa:new qf({values:a})})},sk=function(a){return new rf({Ga:new Of({values:a})})},tk=function(a,b){return new rf({ya:new Bg({functionName:a,arguments:b})})},uk=function(a,b){return new rf({ya:new Bg({Va:a,arguments:b})})},vk=function(a,b){return new rf({Ua:new Ag({lc:a,body:b})})},wk=function(a){if(!a)return"AUTO_JPEG_PNG";a=a.toUpperCase();switch(a){case "JPG":return"JPEG"; case "AUTO":return"AUTO_JPEG_PNG";case "TIF":case "TIFF":case "GEOTIF":case "GEOTIFF":return"GEO_TIFF";case "TF_RECORD":case "TFRECORD":return"TF_RECORD_IMAGE";case "NUMPY":return"NPY";case "ZIPPED_TIF":case "ZIPPED_TIFF":case "ZIPPED_GEOTIF":case "ZIPPED_GEOTIFF":return"ZIPPED_GEO_TIFF";case "ZIPPED_TIF_PER_BAND":case "ZIPPED_TIFF_PER_BAND":case "ZIPPED_GEOTIF_PER_BAND":case "ZIPPED_GEOTIFF_PER_BAND":return"ZIPPED_GEO_TIFF_PER_BAND";default:return a}},xk=function(a){if(!a)return"CSV";a=a.toUpperCase(); -switch(a){case "TF_RECORD":case "TFRECORD":return"TF_RECORD_TABLE";case "JSON":case "GEOJSON":return"GEO_JSON";default:return a}},yk=function(a){if(!a)return"VERTICAL";a=a.toUpperCase();if("HORIZONTAL"!==a&&"VERTICAL"!==a)throw Error('Orientation must be "horizontal" or "vertical"');return a},zk=function(a){if(!a)return[];if("string"===typeof a)return a.split(",");if(Array.isArray(a))return a;throw Error("Invalid band list "+a);},Ck=function(a){var b=new Zf,c=!1;"palette"in a&&(c=a.palette,b.Yc="string"=== -typeof c?c.split(","):c,c=!0);var d=[];if("gain"in a||"bias"in a){if("min"in a||"max"in a)throw Error("Gain and bias can't be specified with min and max");var e=b.Yc?b.Yc.length-1:255;d=Ak(a,"bias","gain").map(function(g){var f=-g.bias/g.gain;return{min:f,max:e/g.gain+f}})}else if("min"in a||"max"in a)d=Ak(a,"min","max");0!==d.length&&(b.Wf=d.map(function(g){return new Pf(g)}),c=!0);a=Bk(a.gamma);if(1>>21);f=e+(g^b^c)+d[7]+4139469664&4294967295;e=g+(f<<16&4294967295|f>>>16);f=c 4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[3]+2399980690&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[10]+4293915773&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[1]+2240044497&4294967295;c=e+(f<<21&4294967295|f>>>11);f=b+(e^(c|~g))+d[8]+1873313359&4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[15]+4264355552&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[6]+2734768916&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[13]+1309151649& 4294967295;c=e+(f<<21&4294967295|f>>>11);f=b+(e^(c|~g))+d[4]+4149444226&4294967295;b=c+(f<<6&4294967295|f>>>26);f=g+(c^(b|~e))+d[11]+3174756917&4294967295;g=b+(f<<10&4294967295|f>>>22);f=e+(b^(g|~c))+d[2]+718787259&4294967295;e=g+(f<<15&4294967295|f>>>17);f=c+(g^(e|~b))+d[9]+3951481745&4294967295;a.W[0]=a.W[0]+b&4294967295;a.W[1]=a.W[1]+(e+(f<<21&4294967295|f>>>11))&4294967295;a.W[2]=a.W[2]+e&4294967295;a.W[3]=a.W[3]+g&4294967295}; dl.prototype.update=function(a,b){void 0===b&&(b=a.length);for(var c=b-this.blockSize,d=this.Nk,e=this.Gc,g=0;gthis.Gc?this.blockSize:2*this.blockSize)-this.Gc);a[0]=128;for(var b=1;bb;++b)for(var d=0;32>d;d+=8)a[c++]=this.W[b]>>>d&255;return a};var fl=function(a){this.qd="__ee_hash__";this.Sd=!1!==a;this.Ya=[];this.wa={};this.Dg=[];this.Od=new WeakMap;this.ng=new WeakMap;this.Qi=void 0};w("ee.Serializer",fl);var gl=new dj,hl=new dl,jl=function(a,b){return il(new fl(void 0!==b?b:!0),a)};w("ee.Serializer.encode",jl);var kl=function(a){return gl.va(jl(a))};w("ee.Serializer.toJSON",kl);var ml=function(a){return ll(jl(a,!1))};w("ee.Serializer.toReadableJSON",ml); -var ll=function(a){return"JSON"in r?r.JSON.stringify(a,null," "):gl.va(a)},il=function(a,b){b=a.Fd(b);a.Sd&&(b=t(b)&&"ValueRef"==b.type&&1==a.Ya.length?a.Ya[0][1]:{type:"CompoundValue",scope:a.Ya,value:b},a.Ya=[],Ua(a.Dg,u(function(c){delete c[this.qd]},a)),a.Dg=[],a.wa={});return b}; -fl.prototype.Fd=function(a){if(void 0===a)throw Error("Can't encode an undefined value.");var b=t(a)?a[this.qd]:null;if(this.Sd&&null!=b&&this.wa[b])return{type:"ValueRef",value:this.wa[b]};if(null===a||"boolean"===typeof a||"number"===typeof a||"string"===typeof a)return a;if(za(a))return{type:"Invocation",functionName:"Date",arguments:{value:Math.floor(a.getTime())}};if(a instanceof nk){var c=a.encode(u(this.Fd,this));if(!(Array.isArray(c)||t(c)&&"ArgumentRef"!=c.type))return c}else if(Array.isArray(a))c= -Wa(a,function(e){return this.Fd(e)},this);else if(t(a)&&"function"!==typeof a)c=Hb(a,function(e){if("function"!==typeof e)return this.Fd(e)},this),Qb(c,this.qd),c={type:"Dictionary",value:c};else throw Error("Can't encode object: "+a);if(this.Sd){b=nl(c);if(this.wa[b])var d=this.wa[b];else d=String(this.Ya.length),this.Ya.push([d,c]),this.wa[b]=d;a[this.qd]=b;this.Dg.push(a);return{type:"ValueRef",value:d}}return c}; +dl.prototype.digest=function(){var a=Array((56>this.Gc?this.blockSize:2*this.blockSize)-this.Gc);a[0]=128;for(var b=1;bb;++b)for(var d=0;32>d;d+=8)a[c++]=this.W[b]>>>d&255;return a};var fl=function(a){this.rd="__ee_hash__";this.Td=!1!==a;this.Ya=[];this.wa={};this.Dg=[];this.Pd=new WeakMap;this.ng=new WeakMap;this.Qi=void 0};w("ee.Serializer",fl);var gl=new dj,hl=new dl,jl=function(a,b){return il(new fl(void 0!==b?b:!0),a)};w("ee.Serializer.encode",jl);var kl=function(a){return gl.va(jl(a))};w("ee.Serializer.toJSON",kl);var ml=function(a){return ll(jl(a,!1))};w("ee.Serializer.toReadableJSON",ml); +var ll=function(a){return"JSON"in r?r.JSON.stringify(a,null," "):gl.va(a)},il=function(a,b){b=a.Gd(b);a.Td&&(b=t(b)&&"ValueRef"==b.type&&1==a.Ya.length?a.Ya[0][1]:{type:"CompoundValue",scope:a.Ya,value:b},a.Ya=[],Ua(a.Dg,u(function(c){delete c[this.rd]},a)),a.Dg=[],a.wa={});return b}; +fl.prototype.Gd=function(a){if(void 0===a)throw Error("Can't encode an undefined value.");var b=t(a)?a[this.rd]:null;if(this.Td&&null!=b&&this.wa[b])return{type:"ValueRef",value:this.wa[b]};if(null===a||"boolean"===typeof a||"number"===typeof a||"string"===typeof a)return a;if(za(a))return{type:"Invocation",functionName:"Date",arguments:{value:Math.floor(a.getTime())}};if(a instanceof nk){var c=a.encode(u(this.Gd,this));if(!(Array.isArray(c)||t(c)&&"ArgumentRef"!=c.type))return c}else if(Array.isArray(a))c= +Wa(a,function(e){return this.Gd(e)},this);else if(t(a)&&"function"!==typeof a)c=Hb(a,function(e){if("function"!==typeof e)return this.Gd(e)},this),Qb(c,this.rd),c={type:"Dictionary",value:c};else throw Error("Can't encode object: "+a);if(this.Td){b=nl(c);if(this.wa[b])var d=this.wa[b];else d=String(this.Ya.length),this.Ya.push([d,c]),this.wa[b]=d;a[this.rd]=b;this.Dg.push(a);return{type:"ValueRef",value:d}}return c}; var nl=function(a){hl.reset();hl.update(gl.va(a));return hl.digest().toString()},pl=function(a){return Xc(ol(a),Tc,Uc,Vc)};w("ee.Serializer.encodeCloudApi",pl); var ol=function(a,b){return ql(new fl(!0),a,b)},ql=function(a,b,c){a.Qi=c;return rl(a,b)},sl=function(a,b){var c={};b=b.values;for(var d in b){var e=a.ng.get(b[d]);c[d]=Object.assign({},e)}return c},tl=function(a){a=rl(new fl(!1),a);var b=a.values,c=function(d){if(!t(d))return d;var e=Array.isArray(d)?[]:{},g=d instanceof Object.getPrototypeOf(rf);d=n(Object.entries(g?d.h:d));for(var f=d.next();!f.done;f=d.next()){var l=n(f.value);f=l.next().value;l=l.next().value;g?null!==l&&(e[f]="functionDefinitionValue"=== f&&null!=l.body?{argumentNames:l.lc,body:c(b[l.body])}:"functionInvocationValue"===f&&null!=l.Va?{arguments:Hb(l.arguments,c),functionReference:c(b[l.Va])}:"constantValue"===f?l===Rc?null:l:c(l)):e[f]=c(l)}return e};return a.result&&c(b[a.result])};w("ee.Serializer.encodeCloudApiPretty",tl);var ul=function(a){return gl.va(pl(a))};w("ee.Serializer.toCloudApiJSON",ul);var vl=function(a){return ll(tl(a))};w("ee.Serializer.toReadableCloudApiJSON",vl); -var rl=function(a,b){try{var c=wl(a,b),d=new xl(c,a.Ya,a.Sd,a.ng),e=yl(d,d.wi);return new Hf({result:e,values:d.hi})}finally{a.Od=new WeakMap,a.wa={},a.Ya=[]}},wl=function(a,b){var c=function(f){var l=nl(f);t(b)&&a.Od.set(b,l);if(a.wa[l])return a.wa[l];var m=String(a.Ya.length);a.Ya.push([m,f]);return a.wa[l]=m};if(t(b)&&a.wa[a.Od.get(b)])return a.wa[a.Od.get(b)];if(null===b||"boolean"===typeof b||"string"===typeof b||"number"===typeof b)return c(pk(b));if(za(b))return c(tk("Date",{value:pk(Math.floor(b.getTime()))})); -if(b instanceof nk){var d=b.na(a),e=b.Eh();null!==e&&a.ng.set(d,e);return c(d)}if(Array.isArray(b))return c(rk(b.map(function(f){return qk(wl(a,f))})));if(t(b)&&"function"!==typeof b){var g={};Object.keys(b).sort().forEach(function(f){g[f]=qk(wl(a,b[f]))});return c(sk(g))}throw Error("Can't encode object: "+b);},xl=function(a,b,c,d){var e=this;this.wi=a;this.values={};b.forEach(function(g){return e.values[g[0]]=g[1]});this.pi=c?zl(this):null;this.hi={};this.Zf={};this.ll=0;this.hd=d},yl=function(a, -b){if(b in a.Zf)return a.Zf[b];var c=String(a.ll++);a.Zf[b]=c;a.hi[c]=Al(a,a.values[b],0);return c},Al=function(a,b,c){var d=function(A){return null!==A.hb},e=function(A){return A===Rc?null:A},g=function(A,C){a.hd&&a.hd.has(A)&&!a.hd.has(C)&&a.hd.set(C,a.hd.get(A));return C};if(d(b)||null!=b.Wa||null!=b.qb||null!=b.Mb)return b;if(null!=b.Oa){d=a.values[b.Oa];if(null===a.pi||50>c&&1===a.pi[b.Oa])return d=Al(a,d,c),g(b,d);if(Bl(d))return g(b,d);d=qk(yl(a,b.Oa));return g(b,d)}if(null!=b.Fa){var f=b.Fa.values.map(function(A){return Al(a, +var rl=function(a,b){try{var c=wl(a,b),d=new xl(c,a.Ya,a.Td,a.ng),e=yl(d,d.wi);return new Hf({result:e,values:d.hi})}finally{a.Pd=new WeakMap,a.wa={},a.Ya=[]}},wl=function(a,b){var c=function(f){var l=nl(f);t(b)&&a.Pd.set(b,l);if(a.wa[l])return a.wa[l];var m=String(a.Ya.length);a.Ya.push([m,f]);return a.wa[l]=m};if(t(b)&&a.wa[a.Pd.get(b)])return a.wa[a.Pd.get(b)];if(null===b||"boolean"===typeof b||"string"===typeof b||"number"===typeof b)return c(pk(b));if(za(b))return c(tk("Date",{value:pk(Math.floor(b.getTime()))})); +if(b instanceof nk){var d=b.na(a),e=b.Eh();null!==e&&a.ng.set(d,e);return c(d)}if(Array.isArray(b))return c(rk(b.map(function(f){return qk(wl(a,f))})));if(t(b)&&"function"!==typeof b){var g={};Object.keys(b).sort().forEach(function(f){g[f]=qk(wl(a,b[f]))});return c(sk(g))}throw Error("Can't encode object: "+b);},xl=function(a,b,c,d){var e=this;this.wi=a;this.values={};b.forEach(function(g){return e.values[g[0]]=g[1]});this.pi=c?zl(this):null;this.hi={};this.Zf={};this.ll=0;this.jd=d},yl=function(a, +b){if(b in a.Zf)return a.Zf[b];var c=String(a.ll++);a.Zf[b]=c;a.hi[c]=Al(a,a.values[b],0);return c},Al=function(a,b,c){var d=function(A){return null!==A.hb},e=function(A){return A===Rc?null:A},g=function(A,C){a.jd&&a.jd.has(A)&&!a.jd.has(C)&&a.jd.set(C,a.jd.get(A));return C};if(d(b)||null!=b.Wa||null!=b.qb||null!=b.Mb)return b;if(null!=b.Oa){d=a.values[b.Oa];if(null===a.pi||50>c&&1===a.pi[b.Oa])return d=Al(a,d,c),g(b,d);if(Bl(d))return g(b,d);d=qk(yl(a,b.Oa));return g(b,d)}if(null!=b.Fa){var f=b.Fa.values.map(function(A){return Al(a, A,c+3)});d=f.every(d)?pk(f.map(function(A){return e(A.hb)})):rk(f);return g(b,d)}if(null!=b.Ga){f={};for(var l={},m=n(Object.entries(b.Ga.values||{})),p=m.next();!p.done;p=m.next()){var v=n(p.value);p=v.next().value;v=v.next().value;f[p]=Al(a,v,c+3);null!==l&&d(f[p])?l[p]=e(f[p].hb):l=null}return null!==l?g(b,pk(l)):g(f,sk(f))}if(null!=b.Ua)return d=b.Ua,d=vk(d.lc||[],yl(a,d.body||"")),g(b,d);if(null!=b.ya){d=b.ya;f={};l=n(Object.keys(d.arguments||{}));for(m=l.next();!m.done;m=l.next())m=m.value, f[m]=Al(a,d.arguments[m],c+3);d=d.functionName?tk(d.functionName,f):uk(yl(a,d.Va||""),f);return g(b,d)}throw Error("Can't optimize value: "+b);},Bl=function(a){var b=a.hb;return null!==b?b===Rc||"number"===typeof b||"boolean"===typeof b:null!=a.Mb},zl=function(a){var b={},c=function(e){b[e]?b[e]++:(b[e]=1,d(a.values[e]))},d=function(e){null!=e.Fa?e.Fa.values.forEach(d):null!=e.Ga?Object.values(e.Ga.values).forEach(d):null!=e.Ua?c(e.Ua.body):null!=e.ya?(e=e.ya,null!=e.Va&&c(e.Va),Object.values(e.arguments).forEach(d)): -null!=e.Oa&&c(e.Oa)};c(a.wi);return b};var Hl=function(a){if(null==a.element)throw Error('"element" not found in params '+a);var b=a.selectors||null;null!=b&&"string"===typeof b&&(b=b.split(","));b=new hg({l:ol(a.element),description:L(a.description),oa:null,sa:null,Id:null,wd:null,selectors:b,Vb:M(a.maxErrorMeters),requestId:L(a.id),maxVertices:M(a.maxVertices),O:M(a.maxWorkers),priority:M(a.priority)});var c=Cl(a);switch(c){case "GOOGLE_CLOUD_STORAGE":case "DRIVE":var d=new kg({ja:null,ma:null,F:xk(a.fileFormat)});"GOOGLE_CLOUD_STORAGE"=== -c?d.ja=Dl(a):d.ma=El(a);b.oa=d;break;case "ASSET":b.sa=new ig({Ha:Fl(a)});break;case "FEATURE_VIEW":c=new sg({name:Ik(a.mapName)});d=null==a?null:new xg({Of:M(a.maxFeaturesPerTile),tg:a.thinningStrategy});var e=null==a?null:new wg({Eg:Gl(a.zOrderRanking),sg:Gl(a.thinningRanking)});d=new tg({rg:d,Yf:e});c=new jg({mf:c,rc:d});b.Id=c;break;case "BIGQUERY":b.wd=new wf({Re:new vf({table:L(a.table),overwrite:!!a.overwrite,append:!!a.append})});break;default:throw Error('Export destination "'+c+'" unknown'); +null!=e.Oa&&c(e.Oa)};c(a.wi);return b};var Hl=function(a){if(null==a.element)throw Error('"element" not found in params '+a);var b=a.selectors||null;null!=b&&"string"===typeof b&&(b=b.split(","));b=new hg({l:ol(a.element),description:L(a.description),oa:null,sa:null,Jd:null,xd:null,selectors:b,Vb:M(a.maxErrorMeters),requestId:L(a.id),maxVertices:M(a.maxVertices),O:M(a.maxWorkers),priority:M(a.priority)});var c=Cl(a);switch(c){case "GOOGLE_CLOUD_STORAGE":case "DRIVE":var d=new kg({ja:null,ma:null,F:xk(a.fileFormat)});"GOOGLE_CLOUD_STORAGE"=== +c?d.ja=Dl(a):d.ma=El(a);b.oa=d;break;case "ASSET":b.sa=new ig({Ha:Fl(a)});break;case "FEATURE_VIEW":c=new sg({name:Ik(a.mapName)});d=null==a?null:new xg({Of:M(a.maxFeaturesPerTile),tg:a.thinningStrategy});var e=null==a?null:new wg({Eg:Gl(a.zOrderRanking),sg:Gl(a.thinningRanking)});d=new tg({rg:d,Yf:e});c=new jg({mf:c,rc:d});b.Jd=c;break;case "BIGQUERY":b.xd=new wf({Re:new vf({table:L(a.table),overwrite:!!a.overwrite,append:!!a.append})});break;default:throw Error('Export destination "'+c+'" unknown'); }a.workloadTag&&(b.workloadTag=a.workloadTag);return b};function L(a){return null!=a?String(a):null}function M(a){return null!=a?Number(a):null} var Cl=function(a){var b="DRIVE";if(null==a)return b;null!=a.outputBucket||null!=a.outputPrefix?b="GOOGLE_CLOUD_STORAGE":null!=a.assetId?b="ASSET":null!=a.mapName&&(b="FEATURE_VIEW");null!=a.table&&(b="BIGQUERY");return b},Jl=function(a){var b=new Lg({Ze:!!a.tfrecordCompressed,Wb:L(a.tfrecordMaxFileSize),gg:!!a.tfrecordSequenceData,Xe:!!a.tfrecordCollapseBands,Pf:M(a.tfrecordMaskedThreshold),defaultValue:M(a.tfrecordDefaultValue),cc:Il(a.tfrecordPatchDimensions),Lf:Il(a.tfrecordKernelSize),le:null}); -a=a.tfrecordTensorDepths;if(null!=a)if(t(a)){var c={};Fb(a,function(d,e){if("string"!==typeof e||"number"!==typeof d)throw Error('"tensorDepths" option must be an object of type Object');c[e]=d});b.le=c}else throw Error('"tensorDepths" option needs to have the form Object.');return b},Kl=function(a,b){var c=new dg({ja:null,ma:null,Jd:null,me:null,F:wk(a.fileFormat)});if("GEO_TIFF"===c.F){if(a.fileDimensions&&a.tiffFileDimensions)throw Error('Export cannot set both "fileDimensions" and "tiffFileDimensions".'); -var d=a.tiffShardSize||a.shardSize;var e=!!a.tiffCloudOptimized;var g=!(!a.skipEmptyTiles&&!a.tiffSkipEmptyFiles),f=Il(a.fileDimensions||a.tiffFileDimensions);d=M(d);var l=a.tiffNoData;l=null!=l?new Dg({rf:Number(l)}):null;e=new Cg({We:e,mg:g,cc:f,tileSize:d,Rf:l});c.Jd=e}else"TF_RECORD_IMAGE"===c.F&&(c.me=Jl(a));"GOOGLE_CLOUD_STORAGE"===b?c.ja=Dl(a):c.ma=El(a);return c},Ll=function(a,b){var c=new ng({ja:null,ma:null,F:"MP4"});"GOOGLE_CLOUD_STORAGE"===b?c.ja=Dl(a):c.ma=El(a);return c},Ml=function(a){var b, +a=a.tfrecordTensorDepths;if(null!=a)if(t(a)){var c={};Fb(a,function(d,e){if("string"!==typeof e||"number"!==typeof d)throw Error('"tensorDepths" option must be an object of type Object');c[e]=d});b.le=c}else throw Error('"tensorDepths" option needs to have the form Object.');return b},Kl=function(a,b){var c=new dg({ja:null,ma:null,Kd:null,me:null,F:wk(a.fileFormat)});if("GEO_TIFF"===c.F){if(a.fileDimensions&&a.tiffFileDimensions)throw Error('Export cannot set both "fileDimensions" and "tiffFileDimensions".'); +var d=a.tiffShardSize||a.shardSize;var e=!!a.tiffCloudOptimized;var g=!(!a.skipEmptyTiles&&!a.tiffSkipEmptyFiles),f=Il(a.fileDimensions||a.tiffFileDimensions);d=M(d);var l=a.tiffNoData;l=null!=l?new Dg({rf:Number(l)}):null;e=new Cg({We:e,mg:g,cc:f,tileSize:d,Rf:l});c.Kd=e}else"TF_RECORD_IMAGE"===c.F&&(c.me=Jl(a));"GOOGLE_CLOUD_STORAGE"===b?c.ja=Dl(a):c.ma=El(a);return c},Ll=function(a,b){var c=new ng({ja:null,ma:null,F:"MP4"});"GOOGLE_CLOUD_STORAGE"===b?c.ja=Dl(a):c.ma=El(a);return c},Ml=function(a){var b, c,d,e,g=M(null!=(b=a.endZoom)?b:a.maxZoom);b=M(null!=(c=a.startZoom)?c:a.minZoom);c=M(a.scale);var f=!(null!=(d=a.skipEmpty)?!d:!a.skipEmptyTiles);d=L(a.mapsApiKey);var l=Il(null!=(e=a.dimensions)?e:a.tileDimensions);e=M(a.stride);var m=M(a.minTimeMachineZoomSubset);a=M(a.maxTimeMachineZoomSubset);a=null==m&&null==a?null:new qh({start:null!=m?m:0,end:a});return new gg({hf:g,og:b,scale:c,lg:f,Kf:d,dimensions:l,stride:e,Fg:a})},Il=function(a){if(null==a)return null;var b=new Eg({height:0,width:0}); "string"===typeof a&&(-1!==a.indexOf("x")?a=a.split("x").map(Number):-1!==a.indexOf(",")&&(a=a.split(",").map(Number)));if(Array.isArray(a))if(2===a.length)b.height=a[0],b.width=a[1];else if(1===a.length)b.height=a[0],b.width=a[0];else throw Error("Unable to construct grid from dimensions: "+a);else if("number"!==typeof a||isNaN(a))if(t(a)&&null!=a.height&&null!=a.width)b.height=a.height,b.width=a.width;else throw Error("Unable to construct grid from dimensions: "+a);else b.height=a,b.width=a;return b}, Dl=function(a){var b=null;null!=a.writePublicTiles&&(b=a.writePublicTiles?"PUBLIC":"DEFAULT_OBJECT_ACL");return new Ef({Ue:L(a.outputBucket),xa:L(a.outputPrefix),Ve:a.bucketCorsUris||null,permissions:b})},El=function(a){return new Qf({tf:L(a.driveFolder),xa:L(a.driveFileNamePrefix)})},Fl=function(a){return new Cf({name:Ik(a.assetId)})},Gl=function(a){if(!a)return null;var b=a;"string"===typeof a&&(a=a.split(","));if(Array.isArray(a))return new lh({Xf:(a||[]).map(Nl)});throw Error("Unable to build ranking rule from rules: "+ @@ -539,10 +539,10 @@ if("string"===typeof a.bands)try{a.bands=JSON.parse(a.bands)}catch(e){a.bands=zk JSON.parse(a.crs_transform)}catch(e){}var c=gm(a.image,a);a=new ph({name:null,l:Ul(ol(c)),F:wk(a.format),xa:a.name,da:a.bands&&zk(a.bands.map(function(e){return e.id})),grid:null});c={fields:"name"};var d=Wl();d&&(c.workloadTag=d);b=new K(b);return b.handle((new Gh(b.M)).create(Gj(),a,c).then(function(e){return{docid:e.name,token:""}}))};w("ee.data.getDownloadId",hm);var im=function(a){Ej();return bk+"/v1/"+a.docid+":getPixels"};w("ee.data.makeDownloadUrl",im); var jm=function(a,b){b=new K(b);var c=xk(a.format),d=Ul(ol(a.table)),e=null;if(null!=a.selectors)if("string"===typeof a.selectors)e=a.selectors.split(",");else if(Array.isArray(a.selectors)&&a.selectors.every(function(g){return"string"===typeof g}))e=a.selectors;else throw Error("'selectors' parameter must be an array of strings.");a=new nh({name:null,l:d,F:c,selectors:e,filename:a.filename||null});c={fields:"name"};if(d=Wl())c.workloadTag=d;return b.handle((new Fh(b.M)).create(Gj(),a,c).then(function(g){return{docid:g.name|| "",token:""}}))};w("ee.data.getTableDownloadId",jm);var km=function(a){return bk+"/v1/"+a.docid+":getFeatures"};w("ee.data.makeTableDownloadUrl",km);var lm=function(a,b){var c=function(d){return Math.floor(Math.random()*Math.pow(2,4*d)).toString(16).padStart(d,"0")};a=fb(a||1).map(function(){return[c(8),c(4),"4"+c(3),(8+Math.floor(4*Math.random())).toString(16)+c(3),c(12)].join("-")});return b?b(a):a};w("ee.data.newTaskId",lm); -var mm=function(a){return a.name&&a.done&&a.error},om=function(a,b){var c=nm(a).map(Uk);if(1===c.length)return a=(new K(b)).pd(mm),a.handle(a.Na().get(c[0]).then(function(e){return[Vk(e)]}));a=(new Lj(b)).pd(mm);var d=a.Na();return a.send(c.map(function(e){return[e,d.get(e)]}),function(e){return c.map(function(g){return Vk(e[g])})})};w("ee.data.getTaskStatus",om); +var mm=function(a){return a.name&&a.done&&a.error},om=function(a,b){var c=nm(a).map(Uk);if(1===c.length)return a=(new K(b)).qd(mm),a.handle(a.Na().get(c[0]).then(function(e){return[Vk(e)]}));a=(new Lj(b)).qd(mm);var d=a.Na();return a.send(c.map(function(e){return[e,d.get(e)]}),function(e){return c.map(function(g){return Vk(e[g])})})};w("ee.data.getTaskStatus",om); var nm=function(a){if("string"===typeof a)return[a];if(Array.isArray(a))return a;throw Error("Invalid value: expected a string or an array of strings.");},qm=function(a){return pm(void 0,a)};w("ee.data.getTaskList",qm);var pm=function(a,b){return b?(rm(a,function(c,d){return b(c?{tasks:c.map(Vk)}:null,d)}),null):{tasks:rm(a).map(Vk)}};w("ee.data.getTaskListWithLimit",pm); var rm=function(a,b){var c=[],d={pageSize:500},e=function(l){db(c,l.Na||[]);!l.nextPageToken||a&&c.length>=a?b&&b(a?c.slice(0,a):c):(d.pageToken=l.nextPageToken,g.handle(f.list(Gj(),d).then(e)));return null},g=new K(b?function(l,m){return m&&b(l,m)}:void 0),f=g.Na();g.handle(f.list(Gj(),d).then(e));return b?null:a?c.slice(0,a):c};w("ee.data.listOperations",rm); -var sm=function(a,b){a=nm(a).map(Uk);var c=new zf;if(1===a.length)b=new K(b),b.handle(b.Na().cancel(a[0],c));else{b=new Lj(b);var d=b.Na();b.send(a.map(function(e){return[e,d.cancel(e,c)]}))}};w("ee.data.cancelOperation",sm);var tm=function(a,b){var c=nm(a).map(Uk);if(!Array.isArray(a))return a=(new K(b)).pd(mm),a.handle(a.Na().get(c[0]));a=(new Lj(b)).pd(mm);var d=a.Na();return a.send(c.map(function(e){return[e,d.get(e)]}))};w("ee.data.getOperation",tm); +var sm=function(a,b){a=nm(a).map(Uk);var c=new zf;if(1===a.length)b=new K(b),b.handle(b.Na().cancel(a[0],c));else{b=new Lj(b);var d=b.Na();b.send(a.map(function(e){return[e,d.cancel(e,c)]}))}};w("ee.data.cancelOperation",sm);var tm=function(a,b){var c=nm(a).map(Uk);if(!Array.isArray(a))return a=(new K(b)).qd(mm),a.handle(a.Na().get(c[0]));a=(new Lj(b)).qd(mm);var d=a.Na();return a.send(c.map(function(e){return[e,d.get(e)]}))};w("ee.data.getOperation",tm); var vm=function(a,b){return um(a,"CANCEL",b)};w("ee.data.cancelTask",vm);var um=function(a,b,c){if(!Mb(wm,b))throw Error("Invalid action: "+b);a=nm(a);a=a.map(Uk);sm(a,c);return null};w("ee.data.updateTask",um); var Cm=function(a,b,c){b.id=a;var d=b.type;a=null!=b.sourceUrl?{__source_url__:b.sourceUrl}:{};if("workloadTag"in b)b.workloadTag||delete b.workloadTag;else{var e=Wl();e&&(b.workloadTag=e)}var g=new K(c);c=function(f){return g.handle(f.then(Wk))};switch(d){case "EXPORT_IMAGE":return b=xm(b,a),c(g.image().Ka(Gj(),b));case "EXPORT_FEATURES":return b=Hl(b),b.l=Ul(b.l,a),c(g.table().Ka(Gj(),b));case "EXPORT_VIDEO":return b=ym(b,a),c(g.video().Ka(Gj(),b));case "EXPORT_TILES":return b=zm(b,a),c(g.map().Ka(Gj(), b));case "EXPORT_VIDEO_MAP":return b=Am(b),c((new Kh(g.M)).Ka(Gj(),b));case "EXPORT_CLASSIFIER":return b=Bm(b,a),c((new xh(g.M)).Ka(Gj(),b));default:throw Error("Unable to start processing for task of type "+d);}};w("ee.data.startProcessing",Cm); @@ -552,9 +552,10 @@ oa:null,requestId:L(c.id),O:M(c.maxWorkers),priority:M(c.priority)});d.oa=Ll(c,C return c},Am=function(a){var b=a.scale;delete a.scale;var c=Em(a);c.scale=b;if(null==c.element)throw Error('"element" not found in params '+c);b=new mg({l:ol(c.element),description:L(c.description),Pa:new og({framesPerSecond:M(c.framesPerSecond),maxFrames:M(c.maxFrames),maxPixelsPerFrame:null}),ec:Ml(c),dc:Ll(c,"GOOGLE_CLOUD_STORAGE"),requestId:L(c.id),version:L(c.version),O:M(c.maxWorkers),priority:M(c.priority)});b.l=Ul(b.l);a.workloadTag&&(b.workloadTag=a.workloadTag);return b},Bm=function(a,b){if(null== a.element)throw Error('"element" not found in params '+a);var c=Cl(a);if("ASSET"!=c)throw Error('Export destination "'+c+'" unknown');c=new ag({l:ol(a.element),description:L(a.description),requestId:L(a.id),sa:new Bf({Ha:Fl(a)}),O:M(a.maxWorkers),priority:M(a.priority)});c.l=Ul(c.l,b);a.workloadTag&&(c.workloadTag=a.workloadTag);return c},Gm=function(a,b,c){b=$k(b);var d=function(e){return e?Wk(e):null};return d(Fm(a,b,c&&function(e,g){return c(d(e),g)}))};w("ee.data.startIngestion",Gm); var Fm=function(a,b,c){b=new Rg({Bf:b,requestId:a,overwrite:null,description:null});a=new K(c,a?void 0:0);return a.handle(a.image().import(Gj(),b))},Hm=function(a,b,c){b=new Sg({qg:b,requestId:a,overwrite:null,description:null});a=new K(c,a?void 0:0);return a.handle(a.table().import(Gj(),b))},Im=function(a,b,c){b=al(b);var d=function(e){return e?Wk(e):null};return d(Hm(a,b,c&&function(e,g){return c(d(e),g)}))};w("ee.data.startTableIngestion",Im); -var Jm=function(a,b){b=new K(b);a=Ik(a);return b.handle(b.assets().get(a,{prettyPrint:!1}).then(Jk))};w("ee.data.getAsset",Jm);w("ee.data.getInfo",Jm);var Km=function(a,b,c,d){b=void 0===b?{}:b;d=void 0===d?qc:d;var e=Gk.test(a);c=new K(c);var g=e?new vh(c.M):c.assets();a=e?"projects/"+Hk(a):Ik(a);return c.handle(g.Ud(a,b).then(d))},Lm=function(a,b){return Km(a.id,Qk(a),b,function(c){return null==c?null:Kk(c)})};w("ee.data.getList",Lm);var Mm=function(a,b,c){b=void 0===b?{}:b;return Km(a,b,c)}; -w("ee.data.listAssets",Mm);var Nm=function(a,b,c){b=void 0===b?{}:b;b=Pk(b);return Km(a,b,c,function(d){if(null==d)return null;var e={};d.assets&&(e.images=d.assets);d.nextPageToken&&(e.nextPageToken=d.nextPageToken);return e})};w("ee.data.listImages",Nm);var Om=function(a,b){b=new K(b);return b.handle((new vh(b.M)).Ud(a||Gj()))};w("ee.data.listBuckets",Om);var Pm=function(a){a=new K(a);return a.handle((new vh(a.M)).Ud(Gj()).then(Kk))};w("ee.data.getAssetRoots",Pm); -var Qm=function(a,b){var c="projects/"+Hk(a);a="projects/earthengine-legacy"===c?a:void 0;var d=new Rf({type:"Folder"});b=new K(b);b.handle(b.assets().create(c,d,{assetId:a}).then(Jk))};w("ee.data.createAssetHome",Qm); +var Jm=function(a,b){b=new K(b);a=Ik(a);return b.handle(b.assets().get(a,{prettyPrint:!1}).then(Jk))};w("ee.data.getAsset",Jm);w("ee.data.getInfo",Jm); +var Km=function(a,b,c,d){b=void 0===b?{}:b;d=void 0===d?qc:d;var e=Object.assign({},b),g=new K(c),f=(b=Gk.test(a))?new vh(g.M):g.assets();a=b?"projects/"+Hk(a):Ik(a);var l=function(m){if(null!=e.pageSize||!m.nextPageToken)return m;var p=m.assets||[];e.pageToken=m.nextPageToken;m=f.Uc(a,e).then(function(v){v.assets=p.concat(v.assets);return v}).then(l);return c?m:g.handle(m)};return g.handle(f.Uc(a,e).then(l).then(d))},Lm=function(a,b){var c=Qk(a);c.pageSize||(c.pageSize=1E3);return Km(a.id,c,b,function(d){return null== +d?null:Kk(d)})};w("ee.data.getList",Lm);var Mm=function(a,b,c){b=void 0===b?{}:b;return Km(a,b,c)};w("ee.data.listAssets",Mm);var Nm=function(a,b,c){b=void 0===b?{}:b;b=Pk(b);return Km(a,b,c,function(d){if(null==d)return null;var e={};d.assets&&(e.images=d.assets);d.nextPageToken&&(e.nextPageToken=d.nextPageToken);return e})};w("ee.data.listImages",Nm);var Om=function(a,b){b=new K(b);return b.handle((new vh(b.M)).Uc(a||Gj()))};w("ee.data.listBuckets",Om);var Pm=function(a){a=new K(a);return a.handle((new vh(a.M)).Uc(Gj()).then(Kk))}; +w("ee.data.getAssetRoots",Pm);var Qm=function(a,b){var c="projects/"+Hk(a);a="projects/earthengine-legacy"===c?a:void 0;var d=new Rf({type:"Folder"});b=new K(b);b.handle(b.assets().create(c,d,{assetId:a}).then(Jk))};w("ee.data.createAssetHome",Qm); var Rm=function(a,b,c,d,e){if(c)throw Error("Asset overwrite not supported.");if("string"===typeof a)throw Error("Asset cannot be specified as string.");b=a.name||b&&Ik(b);if(!b)throw Error("Either asset name or opt_path must be specified.");c=b.indexOf("/assets/");if(-1===c)throw Error("Asset name must contain /assets/.");a=Object.assign({},a);a.tilestoreEntry&&!a.tilestoreLocation&&(a.tilestoreLocation=a.tilestoreEntry,delete a.tilestoreEntry);a.tilestoreLocation&&(a.tilestoreLocation=new Xf(a.tilestoreLocation)); a.gcsLocation&&!a.cloudStorageLocation&&(a.cloudStorageLocation=a.gcsLocation,delete a.gcsLocation);a.cloudStorageLocation&&(a.cloudStorageLocation=new Ff(a.cloudStorageLocation));d&&!a.properties&&(a.properties=Object.assign({},d));d=n(["title","description"]);for(var g=d.next();!g.done;g=d.next())if(g=g.value,a[g]){var f={};a.properties=Object.assign((f[g]=a[g],f),a.properties||{});delete a[g]}a=new Rf(a);d=b.slice(0,c);b=b.slice(c+8);a.id=null;a.name=null;a:switch(c=a.type,c){case "ImageCollection":c= "IMAGE_COLLECTION";break a;case "Folder":c="FOLDER"}a.type=c;e=new K(e);return e.handle(e.assets().create(d,a,{assetId:b}).then(Jk))};w("ee.data.createAsset",Rm);var Sm=function(a,b,c){return Rm({type:"Folder"},a,b,void 0,c)};w("ee.data.createFolder",Sm);var Tm=function(a,b,c){a=Ik(a);b=Ik(b);b=new $g({Qb:b});c=new K(c);c.handle(c.assets().move(a,b).then(Jk))};w("ee.data.renameAsset",Tm); @@ -568,14 +569,14 @@ var an=function(){this.default=this.tag=""};an.prototype.get=function(){return t an.prototype.validate=function(a){if(null==a||""===a)return"";a=String(a);if(!/^([a-z0-9]|[a-z0-9][-_a-z0-9]{0,61}[a-z0-9])$/g.test(a))throw Error('Invalid tag, "'+a+'". Tags must be 1-63 characters, beginning and ending with a lowercase alphanumeric character ([a-z0-9]) with dashes (-), underscores (_), and lowercase alphanumerics between.');return a};var Wl=function(){return Pl(an).get()};w("ee.data.getWorkloadTag",Wl);var bn=function(a){Pl(an).set(a)};w("ee.data.setWorkloadTag",bn); var cn=function(a){var b=Pl(an);b.default=b.validate(a);Pl(an).set(a)};w("ee.data.setDefaultWorkloadTag",cn);var dn=function(a){a&&(a=Pl(an),a.default=a.validate(""));Pl(an).reset()};w("ee.data.resetWorkloadTag",dn);var wm={Hl:"CANCEL",Kl:"UPDATE"};var O=function(a,b,c){if(!(this instanceof O))return en(O,arguments);if(c&&(a||b))throw Error('When "opt_varName" is specified, "func" and "args" must be null.');if(a&&!b)throw Error('When "func" is specified, "args" must not be null.');this.I=a;this.args=b;this.U=c||null;this.zl=null};x(O,nk);w("ee.ComputedObject",O);O.prototype.Eh=function(){return this.zl};O.prototype.evaluate=function(a){if(!a||"function"!==typeof a)throw Error("evaluate() requires a callback function.");bm(this,a)}; O.prototype.evaluate=O.prototype.evaluate;O.prototype.V=function(a){return bm(this,a)};O.prototype.getInfo=O.prototype.V;O.prototype.encode=function(a){if(null===this.I&&null===this.args)return{type:"ArgumentRef",value:this.U};var b={},c;for(c in this.args)void 0!==this.args[c]&&(b[c]=a(this.args[c]));b={type:"Invocation",arguments:b};a=a(this.I);b["string"===typeof a?"functionName":"function"]=a;return b}; -O.prototype.na=function(a){if(null===this.I&&null===this.args){a=this.U||a.Qi;if(!a)throw Error("A mapped function's arguments cannot be used in client-side operations");return new rf({Mb:a})}var b={},c;for(c in this.args)void 0!==this.args[c]&&(b[c]=qk(wl(a,this.args[c])));return"string"===typeof this.I?tk(String(this.I),b):this.I.Ed(a,b)};O.prototype.va=function(a){return(void 0===a?0:a)?kl(this):ul(this)};O.prototype.serialize=O.prototype.va; +O.prototype.na=function(a){if(null===this.I&&null===this.args){a=this.U||a.Qi;if(!a)throw Error("A mapped function's arguments cannot be used in client-side operations");return new rf({Mb:a})}var b={},c;for(c in this.args)void 0!==this.args[c]&&(b[c]=qk(wl(a,this.args[c])));return"string"===typeof this.I?tk(String(this.I),b):this.I.Fd(a,b)};O.prototype.va=function(a){return(void 0===a?0:a)?kl(this):ul(this)};O.prototype.serialize=O.prototype.va; O.prototype.toString=function(){return"ee."+this.name()+"("+ml(this)+")"};w("ee.ComputedObject.prototype.toString",O.prototype.toString);O.prototype.name=function(){return"ComputedObject"};O.prototype.Gg=function(a,b){var c=Array.from(arguments);c[0]=this;a.apply(r,c);return this};O.prototype.aside=O.prototype.Gg; var fn=function(a,b){if(b instanceof a.constructor)return b;var c=function(){};c.prototype=a.constructor.prototype;a=new c;a.I=b.I;a.args=b.args;a.U=b.U;return a},en=function(a,b){function c(){return a.apply(this,b)}c.prototype=a.prototype;return new c};var gn={},hn=function(a,b){if(b==a)return!0;switch(a){case "Element":return"Element"==b||"Image"==b||"Feature"==b||"Collection"==b||"ImageCollection"==b||"FeatureCollection"==b;case "FeatureCollection":case "Collection":return"Collection"==b||"ImageCollection"==b||"FeatureCollection"==b;case "Object":return!0;default:return!1}},jn=function(a){return"number"===typeof a||a instanceof O&&"Number"==a.name()},kn=function(a){return"string"===typeof a||a instanceof O&&"String"==a.name()},ln=function(a){return t(a)&& "function"!==typeof a?(a=Object.getPrototypeOf(a),null!==a&&null===Object.getPrototypeOf(a)):!1},mn=function(a,b,c){c=void 0===c?!1:c;return 1===a.length&&ln(a[0])&&(a=b.args,c&&(a=a.slice(1)),a.length)?!(1===a.length||a[1].optional)||"Dictionary"!==a[0].type:!1};var nn=function(){if(!(this instanceof nn))return new nn};x(nn,nk);w("ee.Function",nn);var on=qc;nn.prototype.call=function(a){return this.apply(pn(this,Array.prototype.slice.call(arguments,0)))};nn.prototype.call=nn.prototype.call;nn.prototype.apply=function(a){a=new O(this,qn(this,a));return on(a,this.Z().returns)};nn.prototype.apply=nn.prototype.apply; var rn=function(a,b,c){var d=void 0!==b,e=a.Z();if(mn(c,e,d)){if(c=Rb(c[0]),d){d=e.args[0].name;if(d in c)throw Error("Named args for "+e.name+" can't contain keyword "+d);c[d]=b}}else c=pn(a,d?[b].concat(c):c);return a.apply(c)},qn=function(a,b){for(var c=a.Z().args,d={},e={},g=0;g/,"");for(var l=0;l/,"");return new P(f,g)}),a&&a())};a?Vl(c):c(Vl())}},An=function(a,b,c,d){wn();var e=d||"";Fb(un,function(g,f){var l=f.split(".");if(2==l.length&&l[0]==b){l=e+l[1];var m=g.Z();vn[f]=!0;var p=!1;m.args.length&&(f=m.args[0].type,p="Object"!=f&&hn(f,c)); f=p?a.prototype:a;l in f&&!f[l].signature||(f[l]=function(v){return rn(g,p?this:void 0,Array.prototype.slice.call(arguments,0))},f[l].toString=u(g.toString,g,l,p),f[l].signature=m)}})},Bn=function(a){var b=function(c){for(var d in c)"function"===typeof c[d]&&c[d].signature&&delete c[d]};b(a);b(a.prototype||{})};var R=function(a,b){var c=Cn(a),d=Wa(c,function(m){return m.replace(/^opt_/,"")});a=(a=r.EXPORTED_FN_INFO?r.EXPORTED_FN_INFO[a.toString()].name.split(".").pop()+"()":null)?" to function "+a:"";var e={},g=b[0],f=t(g)&&"function"!==typeof g&&!Array.isArray(g)&&"Object"===Object.getPrototypeOf(g).constructor.name;if(1d.length)throw Error("Received too many arguments"+a+". Expected at most "+d.length+" but got "+b.length+".");for(g=0;g"))+"_";for(e=0;e< d.length;e++){var g=d[e],f=c+e;b[g].U=f;a.args[g].name=f}return a};var up=function(a,b){if(!(this instanceof up))return en(up,arguments);if(a instanceof up)return a;vp();var c=R(up,arguments);a=c.date;c=c.tz;var d=new P("Date"),e={},g=null;if(kn(a)){if(e.value=a,c)if(kn(c))e.timeZone=c;else throw Error("Invalid argument specified for ee.Date(..., opt_tz): "+c);}else if(jn(a))e.value=a;else if(za(a))e.value=Math.floor(a.getTime());else if(a instanceof O)a.I&&"Date"==a.I.Z().returns?(d=a.I,e=a.args,g=a.U):e.value=a;else throw Error("Invalid argument specified for ee.Date(): "+ a);O.call(this,d,e,g)};x(up,O);w("ee.Date",up);var wp=!1,vp=function(){wp||(An(up,"Date","Date"),wp=!0)};up.prototype.name=function(){return"Date"};w("ee.Deserializer",function(){});var yp=function(a){return xp(JSON.parse(a))};w("ee.Deserializer.fromJSON",yp);var xp=function(a){if("result"in a&&"values"in a)return zp(a);var b={};if(t(a)&&"CompoundValue"===a.type){for(var c=a.scope,d=0;da.y||a.y>=d)return c.c var lq=function(a,b){a.jb.Xa(b,"status-changed",function(){switch(b.getStatus()){case "loaded":var c=b.fl,d=(new Date).getTime();$p(this.ie,b.zoom).tileLatencies.push(d-c);this.dispatchEvent(new nq(this.Nc()));break;case "throttled":$p(this.ie,b.zoom).throttleCount++;this.dispatchEvent(new oq(b.Ca));break;case "failed":$p(this.ie,b.zoom).errorCount++;this.dispatchEvent(new pq(b.Ca,b.Tk));break;case "aborted":this.dispatchEvent(new qq(this.Nc()))}})}; jq.prototype.J=function(){D.prototype.J.call(this);this.zb.forEach(Ha);this.zb.clear();this.zb=null;Ha(this.jb);this.ug=this.jb=null};var kq=function(a,b){return Za(a.zb.aa(),function(c){return c.getStatus()==b})};w("ee.layers.AbstractOverlay",jq);jq.prototype.removeTileCallback=jq.prototype.bg;jq.prototype.addTileCallback=jq.prototype.Oe;var nq=function(){z.call(this,"tile-load")};q(nq,z);var mq=function(){z.call(this,"tile-start")};q(mq,z);var oq=function(){z.call(this,"tile-throttle")};q(oq,z); var pq=function(a,b){z.call(this,"tile-fail");this.errorMessage=b};q(pq,z);var qq=function(){z.call(this,"tile-abort")};q(qq,z);var rq=function(a,b,c,d){D.call(this);this.rb=a;this.zoom=b;this.la=c.createElement("div");this.la.id=d;this.jl=5;this.de=function(){};this.ke="new";this.vi=0;this.Ff=!1};q(rq,D);rq.prototype.getDiv=function(){return this.la}; -var tq=function(a){if(!a.Ff&&"loading"==a.getStatus())throw Error("startLoad() can only be invoked once. Use retryLoad() after the first attempt.");sq(a,"loading");a.fl=(new Date).getTime();a.Za=new pj;a.Za.xc="blob";a.Za.Xa("complete",function(){var b=Bj(a.Za),c=a.Za.getStatus();429==c&&sq(a,"throttled");if(ij(c)){var d={};Fb(Cj(a.Za),function(g,f){d[f.toLowerCase()]=g});a.Al=d;a.Ci=b;a.Lc()}else if(b){var e=new hq;e.Xa("loadend",function(){a.gd(e.ca.result)});e.readAsText(b)}else a.gd("Failed to load tile.")}, -!1);a.Za.Vc("ready",Fa(Ha,a.Za));a.Ca&&a.Ca.endsWith("&profiling=1")&&(a.Ca=a.Ca.replace("&profiling=1",""),a.Za.headers.set("X-Earth-Engine-Computation-Profiling","1"));a.Za.send(a.Ca,"GET")};h=rq.prototype;h.Lc=function(){this.de(this);sq(this,"loaded")};h.Hc=function(){Ha(this.Za)}; -h.gd=function(a){var b=function(c){try{if(c=JSON.parse(c),c.error&&c.error.message)return c.error.message}catch(d){}return c};this.vi>=this.jl?(this.Tk=b(a),sq(this,"failed")):(this.Hc(),setTimeout(u(function(){this.Sa||(this.Ff=!0,tq(this),this.Ff=!1)},this),1E3*Math.pow(2,this.vi++)))};h.abort=function(){this.Hc();"aborted"!=this.getStatus()&&"removed"!=this.getStatus()&&sq(this,this.ke in uq?"removed":"aborted")};h.getStatus=function(){return this.ke};var sq=function(a,b){a.ke=b;a.dispatchEvent("status-changed")}; -rq.prototype.J=function(){D.prototype.J.call(this);this.Hc();this.la.remove();this.de=null};var uq={aborted:!0,failed:!0,loaded:!0,removed:!0};var vq=function(){y.call(this)};q(vq,y);var wq=function(a,b){jq.call(this,a,b);this.Tg=new zc;this.rh=new zc};q(wq,jq);wq.prototype.fh=function(a,b,c,d){var e=new xq(a,b,c,d);this.jb.Xa(e,"status-changed",function(){"loaded"==e.getStatus()&&(this.Tg.set(a,new Float32Array(e.yd)),this.rh.set(a,e.la))});return e};wq.prototype.J=function(){jq.prototype.J.call(this);this.rh=this.Tg=null};w("ee.layers.BinaryOverlay",wq);var xq=function(a,b,c,d){rq.call(this,a,b,c,d)};q(xq,rq); -xq.prototype.Lc=function(){var a=new hq;a.Xa("loadend",function(){this.yd=a.ca.result;rq.prototype.Lc.call(this)},void 0,this);a.readAsArrayBuffer(this.Ci)};var yq=function(a){D.call(this);this.Rc={};this.Qc={};this.Tb=new aq(this);this.X=a;this.Ye=!1};x(yq,D);var zq=["load","abort","error"],Aq=function(a,b,c){if(c="string"===typeof c?c:c.src)a.Ye=!1,a.Rc[b]={src:c,eh:null}},Bq=function(a,b){delete a.Rc[b];var c=a.Qc[b];c&&(delete a.Qc[b],a.Tb.se(c,zq,a.di))}; +var tq=function(a){if(!a.Ff&&"loading"==a.getStatus())throw Error("startLoad() can only be invoked once. Use retryLoad() after the first attempt.");sq(a,"loading");a.fl=(new Date).getTime();a.Za=new pj;a.Za.xc="blob";a.Za.Xa("complete",function(){var b=Bj(a.Za),c=a.Za.getStatus();429==c&&sq(a,"throttled");if(ij(c)){var d={};Fb(Cj(a.Za),function(g,f){d[f.toLowerCase()]=g});a.Al=d;a.Ci=b;a.Lc()}else if(b){var e=new hq;e.Xa("loadend",function(){a.hd(e.ca.result)});e.readAsText(b)}else a.hd("Failed to load tile.")}, +!1);a.Za.Wc("ready",Fa(Ha,a.Za));a.Ca&&a.Ca.endsWith("&profiling=1")&&(a.Ca=a.Ca.replace("&profiling=1",""),a.Za.headers.set("X-Earth-Engine-Computation-Profiling","1"));a.Za.send(a.Ca,"GET")};h=rq.prototype;h.Lc=function(){this.de(this);sq(this,"loaded")};h.Hc=function(){Ha(this.Za)}; +h.hd=function(a){var b=function(c){try{if(c=JSON.parse(c),c.error&&c.error.message)return c.error.message}catch(d){}return c};this.vi>=this.jl?(this.Tk=b(a),sq(this,"failed")):(this.Hc(),setTimeout(u(function(){this.Sa||(this.Ff=!0,tq(this),this.Ff=!1)},this),1E3*Math.pow(2,this.vi++)))};h.abort=function(){this.Hc();"aborted"!=this.getStatus()&&"removed"!=this.getStatus()&&sq(this,this.ke in uq?"removed":"aborted")};h.getStatus=function(){return this.ke};var sq=function(a,b){a.ke=b;a.dispatchEvent("status-changed")}; +rq.prototype.J=function(){D.prototype.J.call(this);this.Hc();this.la.remove();this.de=null};var uq={aborted:!0,failed:!0,loaded:!0,removed:!0};var vq=function(){y.call(this)};q(vq,y);var wq=function(a,b){jq.call(this,a,b);this.Tg=new zc;this.rh=new zc};q(wq,jq);wq.prototype.fh=function(a,b,c,d){var e=new xq(a,b,c,d);this.jb.Xa(e,"status-changed",function(){"loaded"==e.getStatus()&&(this.Tg.set(a,new Float32Array(e.zd)),this.rh.set(a,e.la))});return e};wq.prototype.J=function(){jq.prototype.J.call(this);this.rh=this.Tg=null};w("ee.layers.BinaryOverlay",wq);var xq=function(a,b,c,d){rq.call(this,a,b,c,d)};q(xq,rq); +xq.prototype.Lc=function(){var a=new hq;a.Xa("loadend",function(){this.zd=a.ca.result;rq.prototype.Lc.call(this)},void 0,this);a.readAsArrayBuffer(this.Ci)};var yq=function(a){D.call(this);this.Rc={};this.Qc={};this.Tb=new aq(this);this.X=a;this.Ye=!1};x(yq,D);var zq=["load","abort","error"],Aq=function(a,b,c){if(c="string"===typeof c?c:c.src)a.Ye=!1,a.Rc[b]={src:c,eh:null}},Bq=function(a,b){delete a.Rc[b];var c=a.Qc[b];c&&(delete a.Qc[b],a.Tb.se(c,zq,a.di))}; yq.prototype.start=function(){var a=this.Rc;Lb(a).forEach(function(b){var c=a[b];if(c&&(delete a[b],!this.Sa)){if(this.X){var d=this.X;d=(d?new Zh(Yh(d)):Ja||(Ja=new Zh)).Rk("IMG")}else d=new Image;c.eh&&(d.crossOrigin=c.eh);this.Tb.Xa(d,zq,this.di);this.Qc[b]=d;d.id=b;d.src=c.src}},this)}; -yq.prototype.di=function(a){var b=a.currentTarget;if(b){if("readystatechange"==a.type)if("complete"==b.readyState)a.type="load";else return;"undefined"==typeof b.naturalWidth&&("load"==a.type?(b.naturalWidth=b.width,b.naturalHeight=b.height):(b.naturalWidth=0,b.naturalHeight=0));Bq(this,b.id);this.dispatchEvent({type:a.type,target:b});!this.Sa&&Pb(this.Qc)&&Pb(this.Rc)&&!this.Ye&&(this.Ye=!0,this.dispatchEvent("complete"))}};yq.prototype.J=function(){delete this.Rc;delete this.Qc;Ha(this.Tb);yq.L.J.call(this)};var Cq=function(a,b){jq.call(this,a,b)};q(Cq,jq);Cq.prototype.fh=function(a,b,c,d){return new Dq(a,b,c,d)};w("ee.layers.ImageOverlay",Cq);var Dq=function(a,b,c,d){rq.call(this,a,b,c,d);this.de=Eq;this.Ih=this.ga=this.Qd=null;this.Xc=""};q(Dq,rq); -Dq.prototype.Lc=function(){try{var a=zd(this.Ci);this.Xc=wd(a);var b=this.Xc!==yd.toString()?this.Xc:this.Ca}catch(c){b=this.Ca}this.ga=new yq;Aq(this.ga,this.la.id+"-image",b);this.Ih=ac(this.ga,Fq,function(c){"load"==c.type?(this.Qd=c.target,rq.prototype.Lc.call(this)):this.gd()},void 0,this);this.ga.start()};Dq.prototype.Hc=function(){rq.prototype.Hc.call(this);this.ga&&(jc(this.Ih),Ha(this.ga))};Dq.prototype.J=function(){rq.prototype.J.call(this);this.Xc&&URL.revokeObjectURL(this.Xc)}; -var Eq=function(a){a.la.appendChild(a.Qd)},Fq=["load","abort","error"];var Gq=function(a){for(var b=arguments[0],c=1;cthis.Wd)throw Error("[goog.structs.Pool] Min can not be greater than max");this.Ta=new Jq;this.vb=new Ic;this.delay=0;this.Gf=null;this.rd()};x(Kq,y);Kq.prototype.Oc=function(){var a=Date.now();if(!(null!=this.Gf&&a-this.Gfthis.Wd&&0=this.R()){for(var c=this.Aa,d=0;d>1,a[d].getKey()>c.getKey())a[b]=a[d],b=d;else break;a[b]=c};h=Nq.prototype; +yq.prototype.di=function(a){var b=a.currentTarget;if(b){if("readystatechange"==a.type)if("complete"==b.readyState)a.type="load";else return;"undefined"==typeof b.naturalWidth&&("load"==a.type?(b.naturalWidth=b.width,b.naturalHeight=b.height):(b.naturalWidth=0,b.naturalHeight=0));Bq(this,b.id);this.dispatchEvent({type:a.type,target:b});!this.Sa&&Pb(this.Qc)&&Pb(this.Rc)&&!this.Ye&&(this.Ye=!0,this.dispatchEvent("complete"))}};yq.prototype.J=function(){delete this.Rc;delete this.Qc;Ha(this.Tb);yq.L.J.call(this)};var Cq=function(a,b){jq.call(this,a,b)};q(Cq,jq);Cq.prototype.fh=function(a,b,c,d){return new Dq(a,b,c,d)};w("ee.layers.ImageOverlay",Cq);var Dq=function(a,b,c,d){rq.call(this,a,b,c,d);this.de=Eq;this.Ih=this.ga=this.Rd=null;this.Yc=""};q(Dq,rq); +Dq.prototype.Lc=function(){try{var a=zd(this.Ci);this.Yc=wd(a);var b=this.Yc!==yd.toString()?this.Yc:this.Ca}catch(c){b=this.Ca}this.ga=new yq;Aq(this.ga,this.la.id+"-image",b);this.Ih=ac(this.ga,Fq,function(c){"load"==c.type?(this.Rd=c.target,rq.prototype.Lc.call(this)):this.hd()},void 0,this);this.ga.start()};Dq.prototype.Hc=function(){rq.prototype.Hc.call(this);this.ga&&(jc(this.Ih),Ha(this.ga))};Dq.prototype.J=function(){rq.prototype.J.call(this);this.Yc&&URL.revokeObjectURL(this.Yc)}; +var Eq=function(a){a.la.appendChild(a.Rd)},Fq=["load","abort","error"];var Gq=function(a){for(var b=arguments[0],c=1;cthis.Wd)throw Error("[goog.structs.Pool] Min can not be greater than max");this.Ta=new Jq;this.vb=new Ic;this.delay=0;this.Gf=null;this.sd()};x(Kq,y);Kq.prototype.Oc=function(){var a=Date.now();if(!(null!=this.Gf&&a-this.Gfthis.Wd&&0=this.R()){for(var c=this.Aa,d=0;d>1,a[d].getKey()>c.getKey())a[b]=a[d],b=d;else break;a[b]=c};h=Nq.prototype; h.remove=function(){var a=this.Aa,b=a.length,c=a[0];if(!(0>=b)){if(1==b)a.length=0;else{a[0]=a.pop();a=0;b=this.Aa;for(var d=b.length,e=b[a];a>1;){var g=2*a+1,f=2*a+2;g=fe.getKey())break;b[a]=b[g];a=g}b[a]=e}return c.ue}};h.aa=function(){for(var a=this.Aa,b=[],c=a.length,d=0;dthis.kl)return!1;this.Kg++;Bq(this.ga,this.fa);setTimeout(u(this.Bl,this),0);return!0}; -h.Bl=function(){if(!this.kc){var a=u(function(d){this.kc||(Aq(this.ga,this.fa,d),ac(this.ga,Zq,u(this.Yk,this)),this.ga.start())},this),b=this.getUrl();if(re(b).Ba.Pb("profiling")){var c=new pj;c.xc="blob";c.Xa("complete",u(function(){this.mi=c.getResponseHeader("X-Earth-Engine-Computation-Profile")||null;if(200<=c.getStatus()&&300>c.getStatus())try{var d=wd(zd(Bj(c)));var e=d!==yd.toString()}catch(g){}a(e?d:b)},this));c.Vc("ready",u(c.Ra,c));c.send(b,"GET")}else a(b)}};h.Kg=0;h.kc=!1;h.ga=null; -h.Ni=null;h.Ja=null;h.mi=null;var Zq=["load","abort","error"],$q=function(){y.call(this);this.Ea=!1};q($q,y);$q.prototype.setActive=function(a){this.Ea=a};$q.prototype.isActive=function(){return this.Ea};var Vq=function(a,b){Qq.call(this,a,b)};q(Vq,Qq);Vq.prototype.bf=function(){return new $q};Vq.prototype.Cd=function(a){a.Ra()};Vq.prototype.Sf=function(a){return!a.Sa&&!a.isActive()};var ar=function(a,b,c,d,e){Nc.call(this,a,b,c,d,e);this.minZoom=d.minZoom||0;this.maxZoom=d.maxZoom||20;if(!window.google||!window.google.maps)throw Error("Google Maps API hasn't been initialized.");this.tileSize=d.tileSize||new google.maps.Size(256,256);this.name=d.name;this.wg=new Ic;this.Tf=1;this.ua=e||null};q(ar,Nc);h=ar.prototype;h.Oe=function(a){return bc(this,"tileevent",a)};h.bg=function(a){jc(a)}; +h.Bl=function(){if(!this.kc){var a=u(function(d){this.kc||(Aq(this.ga,this.fa,d),ac(this.ga,Zq,u(this.Yk,this)),this.ga.start())},this),b=this.getUrl();if(re(b).Ba.Pb("profiling")){var c=new pj;c.xc="blob";c.Xa("complete",u(function(){this.mi=c.getResponseHeader("X-Earth-Engine-Computation-Profile")||null;if(200<=c.getStatus()&&300>c.getStatus())try{var d=wd(zd(Bj(c)));var e=d!==yd.toString()}catch(g){}a(e?d:b)},this));c.Wc("ready",u(c.Ra,c));c.send(b,"GET")}else a(b)}};h.Kg=0;h.kc=!1;h.ga=null; +h.Ni=null;h.Ja=null;h.mi=null;var Zq=["load","abort","error"],$q=function(){y.call(this);this.Ea=!1};q($q,y);$q.prototype.setActive=function(a){this.Ea=a};$q.prototype.isActive=function(){return this.Ea};var Vq=function(a,b){Qq.call(this,a,b)};q(Vq,Qq);Vq.prototype.bf=function(){return new $q};Vq.prototype.Dd=function(a){a.Ra()};Vq.prototype.Sf=function(a){return!a.Sa&&!a.isActive()};var ar=function(a,b,c,d,e){Nc.call(this,a,b,c,d,e);this.minZoom=d.minZoom||0;this.maxZoom=d.maxZoom||20;if(!window.google||!window.google.maps)throw Error("Google Maps API hasn't been initialized.");this.tileSize=d.tileSize||new google.maps.Size(256,256);this.name=d.name;this.wg=new Ic;this.Tf=1;this.ua=e||null};q(ar,Nc);h=ar.prototype;h.Oe=function(a){return bc(this,"tileevent",a)};h.bg=function(a){jc(a)}; h.getTile=function(a,b,c){if(ba.y||a.y>=1< { + // We currently treat pageSize as a cap on the results, if this param was + // provided we should break fast and not return more than the asked for + // amount. + if (params.pageSize != null || !response.nextPageToken) { + return response; + } + const previousAssets = response.assets || []; + params.pageToken = response.nextPageToken; + const nextResponse = methodRoot.listAssets(parent, params) + .then((response) => { + // Add previous assets to front. + response.assets = previousAssets.concat(response.assets); + return response; + }) + .then(getNextPageIfNeeded); + if (opt_callback) { + // For async, make sure we have only a single chained `call.handle` call. + return nextResponse; + } + // For sync mode, the response data needs to be uplifted from the fake + // Promise object to be returned immediately. + return call.handle(nextResponse); + }; + return call.handle(methodRoot.listAssets(parent, params) + .then(getNextPageIfNeeded) + .then(opt_postProcessing)); }; @@ -1686,9 +1713,15 @@ ee.data.makeListAssetsCall_ = function( * @export */ ee.data.getList = function(params, opt_callback) { + const convertedParams = ee.rpc_convert.getListToListAssets(params); + // Force a single page of results by explicitly specifying a page size. + // This maintains backward compatibility, as well as compatibility with the + // Python implementation. + if (!convertedParams.pageSize) { + convertedParams.pageSize = 1000; + } return ee.data.makeListAssetsCall_( - params['id'], ee.rpc_convert.getListToListAssets(params), opt_callback, - (r) => { + params['id'], convertedParams, opt_callback, (r) => { if (r == null) { return null; } @@ -1709,7 +1742,7 @@ ee.data.getList = function(params, opt_callback) { * * * + * return. If not specified, all results are returned. * * *
pageSize (string) The number of results to - * return. Defaults to 1000.
pageToken (string) The token for the page of @@ -1753,7 +1786,7 @@ ee.data.listAssets = function( * * * + * If not specified, all results are returned. * * *
pageSize (string) The number of results to return. - * Defaults to 1000.
pageToken (string) The token page of results to diff --git a/javascript/src/examples/Demos/Landsat8HarmonicModeling.js b/javascript/src/examples/Demos/Landsat8HarmonicModeling.js index 4f667130f..ded6bf373 100644 --- a/javascript/src/examples/Demos/Landsat8HarmonicModeling.js +++ b/javascript/src/examples/Demos/Landsat8HarmonicModeling.js @@ -69,8 +69,10 @@ var addHarmonics = function(freqs) { }; }; -// Filter to the area of interest, mask clouds, add variables. +// Filter to the desired date range and area of interest, mask clouds, +// and add variables. var harmonicLandsat = landsatCollection + .filterDate('2015-01-01', '2020-01-01') .filterBounds(roi) .map(maskClouds) .map(addNDVI) diff --git a/python/ee/__init__.py b/python/ee/__init__.py index 38c1d2f98..353efc3f2 100644 --- a/python/ee/__init__.py +++ b/python/ee/__init__.py @@ -1,6 +1,6 @@ """The EE Python library.""" -__version__ = '0.1.395' +__version__ = '0.1.396' # Using lowercase function naming to match the JavaScript names. # pylint: disable=g-bad-name diff --git a/python/ee/_cloud_api_utils.py b/python/ee/_cloud_api_utils.py index a38027e4b..986fed564 100644 --- a/python/ee/_cloud_api_utils.py +++ b/python/ee/_cloud_api_utils.py @@ -62,9 +62,19 @@ def request( # pylint: disable=invalid-name del connection_type # Ignored del redirections # Ignored - response = self._session.request( - method, uri, data=body, headers=headers, timeout=self._timeout - ) + try: + # googleapiclient is expecting an httplib2 object, and doesn't include + # requests error in the list of transient errors. Therefore, transient + # requests errors should be converted to kinds that googleapiclient + # consider transient. + response = self._session.request( + method, uri, data=body, headers=headers, timeout=self._timeout + ) + except requests.exceptions.ConnectionError as connection_error: + raise ConnectionError(connection_error) from connection_error + except requests.exceptions.ChunkedEncodingError as encoding_error: + # This is not a one-to-one match, but it's close enough. + raise ConnectionError(encoding_error) from encoding_error headers = dict(response.headers) headers['status'] = response.status_code content = response.content diff --git a/python/ee/batch.py b/python/ee/batch.py index aefb68162..9b2c501f3 100644 --- a/python/ee/batch.py +++ b/python/ee/batch.py @@ -20,6 +20,18 @@ from ee import geometry +def _transform_operation_to_task(operation: Dict[str, Any]) -> Task: + """Converts an operation to a task.""" + status = _cloud_api_utils.convert_operation_to_task(operation) + return Task( + status['id'], + status.get('task_type'), + status.get('state'), + {'description': status.get('description')}, + status.get('name'), + ) + + class Task: """A batch task that can be run on the EE batch processing system.""" @@ -108,6 +120,14 @@ def __init__( self.state = state self.name = name + @property + def operation_name(self) -> Optional[str]: + if self.name: + return self.name + if self.id: + return _cloud_api_utils.convert_task_id_to_operation_name(self.id) + return None + def start(self) -> None: """Starts the task. No-op for started tasks.""" if not self.config: @@ -152,8 +172,9 @@ def status(self) -> Dict[str, Any]: - error_message: Failure reason. Appears only if state is FAILED. May also include other fields. """ - if self.id: - result = data.getTaskStatus(self.id)[0] + if self.operation_name: + operation = data.getOperation(self.operation_name) + result = _cloud_api_utils.convert_operation_to_task(operation) if result['state'] == 'UNKNOWN': result['state'] = Task.State.UNSUBMITTED else: @@ -166,7 +187,7 @@ def active(self) -> bool: def cancel(self) -> None: """Cancels the task.""" - data.cancelTask(self.id) + data.cancelOperation(self.operation_name) @staticmethod def list() -> List[Task]: @@ -178,15 +199,7 @@ def list() -> List[Task]: Returns: A list of Tasks. """ - statuses = data.getTaskList() - tasks = [] - for status in statuses: - tasks.append(Task(status['id'], - status.get('task_type'), - status.get('state'), - {'description': status.get('description')}, - status.get('name'))) - return tasks + return list(map(_transform_operation_to_task, data.listOperations())) def __repr__(self) -> str: """Returns a string representation of the task.""" diff --git a/python/ee/data.py b/python/ee/data.py index f9f0e9fb8..eea21620b 100644 --- a/python/ee/data.py +++ b/python/ee/data.py @@ -556,14 +556,16 @@ def getList(params: Dict[str, Any]) -> Any: return result -def listImages(params: Dict[str, Any]) -> Dict[str, Optional[List[int]]]: +def listImages(params: str|Dict[str, Any]) -> Dict[str, Optional[List[int]]]: """Returns the images in an image collection or folder. Args: - params: An object containing request parameters with the following possible + params: Either a string representing the ID of the image collection to list, + or an object containing request parameters with the following possible values, all but 'parent` are optional: parent - (string) The ID of the image collection to list, required. - pageSize - (string) The number of results to return. Defaults to 1000. + pageSize - (string) The number of results to return. If not specified, all + results are returned. pageToken - (string) The token page of results to return. startTime - (ISO 8601 string): The minimum start time (inclusive). endTime - (ISO 8601 string): The maximum end time (exclusive). @@ -591,14 +593,16 @@ def listImages(params: Dict[str, Any]) -> Dict[str, Optional[List[int]]]: return images -def listAssets(params: Dict[str, Any]) -> Dict[str, List[Any]]: +def listAssets(params: str|Dict[str, Any]) -> Dict[str, List[Any]]: """Returns the assets in a folder. Args: - params: An object containing request parameters with the following possible - values, all but 'parent` are optional: + params: Either a string representing the ID of the collection or folder to + list, or an object containing request parameters with the following + possible values, all but 'parent` are optional: parent - (string) The ID of the collection or folder to list, required. - pageSize - (string) The number of results to return. Defaults to 1000. + pageSize - (string) The number of results to return. If not specified, all + results are returned. pageToken - (string) The token page of results to return. filter - (string) An additional filter query to apply. Example query: '''properties.my_property>=1 AND properties.my_property<2 AND diff --git a/python/ee/daterange.py b/python/ee/daterange.py index 729ad67ee..45edf5b57 100644 --- a/python/ee/daterange.py +++ b/python/ee/daterange.py @@ -87,3 +87,84 @@ def reset(cls) -> None: @staticmethod def name() -> str: return 'DateRange' + + def contains( + self, other: Union[_DateType, _DateRangeType] + ) -> computedobject.ComputedObject: + """Returns true if the given Date or DateRange is within this DateRange. + + Args: + other: The Date or DateRange to check if it is inside the DateRange. + + Returns: + A Boolean ComputedObject. + """ + + return apifunction.ApiFunction.call_(self.name() + '.contains', self, other) + + def end(self) -> ee_date.Date: + """Returns the (exclusive) end of this DateRange.""" + + return apifunction.ApiFunction.call_(self.name() + '.end', self) + + def intersection( + self, other: Union[_DateType, _DateRangeType] + ) -> 'DateRange': + """Returns a DateRange that contains all the timespan of this and other. + + Args: + other: The other DateRange to include in the intersection. + + Raises: + EEException if the result is an empty DateRange. + + Returns: + An ee.DateRange. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.intersection', self, other + ) + + def intersects( + self, other: Union[_DateType, _DateRangeType] + ) -> computedobject.ComputedObject: + """Returns true if the other DateRange has at least one time in common. + + Args: + other: The other DateRange to check against. + + Returns: + A Boolean ComputedObject. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.intersects', self, other + ) + + def isEmpty(self) -> computedobject.ComputedObject: + """Returns true if this DateRange contains no dates, i.e. start >= end.""" + + return apifunction.ApiFunction.call_(self.name() + '.isEmpty', self) + + def isUnbounded(self) -> computedobject.ComputedObject: + """Returns true if this DateRange contains all dates.""" + + return apifunction.ApiFunction.call_(self.name() + '.isUnbounded', self) + + def start(self) -> ee_date.Date: + """Returns the (inclusive) start of this DateRange.""" + + return apifunction.ApiFunction.call_(self.name() + '.start', self) + + def union(self, other: Union[_DateType, _DateRangeType]) -> DateRange: + """Returns a DateRange that contains all points in this and other. + + Args: + other: The DateRange to union with. + + Returns: + An ee.DateRange. + """ + + return apifunction.ApiFunction.call_(self.name() + '.union', self, other) diff --git a/python/ee/deprecation.py b/python/ee/deprecation.py index 00851954b..70fd9ed77 100644 --- a/python/ee/deprecation.py +++ b/python/ee/deprecation.py @@ -177,7 +177,8 @@ def _IssueAssetDeprecationWarning(asset: _DeprecatedAsset) -> None: f'\n\nAttention required for {asset.id}! You are using a deprecated' ' asset.\nTo ensure continued functionality, please update it' ) - if removal_date := asset.removal_date: + removal_date = asset.removal_date + if removal_date: formatted_date = removal_date.strftime('%B %-d, %Y') warning = warning + f' by {formatted_date}.' else: diff --git a/python/ee/ee_array.py b/python/ee/ee_array.py index 93a532740..1a4e68ea1 100644 --- a/python/ee/ee_array.py +++ b/python/ee/ee_array.py @@ -5,13 +5,15 @@ from ee import apifunction from ee import computedobject +# pylint: disable=unused-import from ee import ee_list from ee import ee_string +# pylint: enable=unused-import _ArrayType = Union[ - Any, List[Any], 'Array', ee_list.List, computedobject.ComputedObject + Any, List[Any], 'Array', 'ee_list.List', computedobject.ComputedObject ] -_StringType = Union[str, ee_string.String, computedobject.ComputedObject] +_StringType = Union[str, 'ee_string.String', computedobject.ComputedObject] class Array(computedobject.ComputedObject): diff --git a/python/ee/ee_date.py b/python/ee/ee_date.py index eea6441ad..ea08c7022 100644 --- a/python/ee/ee_date.py +++ b/python/ee/ee_date.py @@ -12,7 +12,7 @@ from ee import ee_types as types from ee import serializer -# TODO: b/291072742 - Have a separate type when datetime.Datetime unavailable. +# TODO(user): - Have a separate type when datetime.Datetime unavailable. _DateType = Union[ datetime.datetime, float, str, 'Date', computedobject.ComputedObject ] diff --git a/python/ee/ee_list.py b/python/ee/ee_list.py index 9a615d3fa..391e9e09a 100644 --- a/python/ee/ee_list.py +++ b/python/ee/ee_list.py @@ -1,4 +1,5 @@ """A wrapper for lists.""" +from __future__ import annotations # List clashes with the class List, so call it ListType from typing import Any, List as ListType, Optional, Tuple, Union @@ -6,11 +7,22 @@ from ee import _utils from ee import apifunction from ee import computedobject +from ee import ee_array from ee import ee_exception +from ee import ee_number +from ee import ee_string +from ee import filter as ee_filter +from ee import geometry +from ee import reducer +_EeAnyType = Union[Any, computedobject.ComputedObject] +# TODO(user): - What will the backend accept for bools? +_EeBoolType = Union[Any, computedobject.ComputedObject] _EeListType = Union[ ListType[Any], Tuple[Any, Any], computedobject.ComputedObject ] +_IntegerType = Union[int, ee_number.Number, computedobject.ComputedObject] +_StringType = Union[str, 'ee_string.String', computedobject.ComputedObject] class List(computedobject.ComputedObject): @@ -49,6 +61,9 @@ def __init__(self, arg: Optional[_EeListType]): raise ee_exception.EEException( 'Invalid argument specified for ee.List(): %s' % arg) + # TODO(user): - Make a staticmethod for repeat. + # TODO(user): - Make a staticmethod for sequence. + @classmethod def initialize(cls) -> None: """Imports API functions to this class.""" @@ -80,3 +95,573 @@ def encode_cloud_value(self, encoder: Optional[Any] = None) -> Any: return {'valueReference': encoder(self._list)} else: return super().encode_cloud_value(encoder) + + def add(self, element: Any) -> 'List': + """Appends the element to the end of list. + + Args: + element: The object to add. + + Returns: + An ee.List with the element added to the end. + """ + + return apifunction.ApiFunction.call_(self.name() + '.add', self, element) + + def cat(self, other: _EeListType) -> 'List': + """Concatenates the contents of other onto list. + + Args: + other: Another list to add to the end of the list. + + Returns: + An ee.List with the elements of the original list followed by the elements + of other. + """ + + return apifunction.ApiFunction.call_(self.name() + '.cat', self, other) + + def contains(self, element: Any) -> computedobject.ComputedObject: + """Returns true if list contains element. + + Args: + element: An object to test for presence in the list. + + Returns: + Returns a Boolean ComputedObject. True if the list has the element. False + if the element is not in the list. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.contains', self, element + ) + + def containsAll(self, other: _EeListType) -> computedobject.ComputedObject: + """Returns true if list contains all of the elements of other. + + The results are independent of the order. + + Args: + other: A list of elements to check for presence in the list. + + Returns: + Boolean ComputedObject. True if the list has all of the elements of other. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.containsAll', self, other + ) + + def distinct(self) -> 'List': + """Returns a copy of list without duplicate elements.""" + + return apifunction.ApiFunction.call_(self.name() + '.distinct', self) + + def equals(self, other: _EeListType) -> computedobject.ComputedObject: + """Returns true if the list contains in order the same elements as other. + + Args: + other: List to compare to. + + Returns: + Boolean ComputedObject. + """ + + return apifunction.ApiFunction.call_(self.name() + '.equals', self, other) + + # pylint: disable-next=redefined-builtin + def filter(self, filter: ee_filter.Filter) -> 'List': + """Filters a list to only the elements that match the given filter. + + To filter list items that aren't images or features, test a property + named 'item', e.g.: ee.Filter.gt('item', 3). + + Args: + filter: The ee.Filter instance to apply. + + Returns: + An ee.List with only the elements that pass the filter. + """ + + return apifunction.ApiFunction.call_(self.name() + '.filter', self, filter) + + def flatten(self) -> 'List': + """Flattens any sublists into a single list.""" + + return apifunction.ApiFunction.call_(self.name() + '.flatten', self) + + def frequency(self, element: Any) -> ee_number.Number: + """Returns the number of elements in list equal to element. + + Args: + element: The value to match against. + + Returns: + Returns the count of elements that match element. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.frequency', self, element + ) + + def get(self, index: _IntegerType) -> computedobject.ComputedObject: + """Returns the element at the specified position in list. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + + Returns: + An ee.ComputedObject. + """ + + return apifunction.ApiFunction.call_(self.name() + '.get', self, index) + + def getArray(self, index: _IntegerType) -> ee_array.Array: + """Returns the array at the specified position in list. + + If the value is not a array, an error will occur. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + + Returns: + An ee.Array. + """ + + return apifunction.ApiFunction.call_(self.name() + '.getArray', self, index) + + def getGeometry(self, index: _IntegerType) -> geometry.Geometry: + """Returns the geometry at the specified position in list. + + If the value is not a geometry, an error will occur. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + + Returns: + An ee.Geometry. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.getGeometry', self, index + ) + + def getNumber(self, index: _IntegerType) -> ee_number.Number: + """Returns the number at the specified position in list. + + If the value is not a number, an error will occur. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + + Returns: + An ee.Number. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.getNumber', self, index + ) + + def getString(self, index: _IntegerType) -> ee_string.String: + """Returns the string at the specified position in list. + + If the value is not a string, an error will occur. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + + Returns: + An ee.String. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.getString', self, index + ) + + def indexOf(self, element: _EeAnyType) -> ee_number.Number: + """Returns the position of the first occurrence of element. + + Returns the position of the first occurrence of target in list, or -1 if + list does not contain the target. + + Args: + element: ComputedObject to search for. + + Returns: + An integer as an ee.Number. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.indexOf', self, element + ) + + def indexOfSublist(self, target: _EeListType) -> ee_number.Number: + """Returns the position of the first occurrence of target. + + Returns the starting position of the first occurrence of target within list, + or -1 if there is no such occurrence. + + Args: + target: An ee.List to search for. + + Returns: + An integer as an ee.Number. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.indexOfSublist', self, target + ) + + def insert(self, index: _IntegerType, element: _EeAnyType) -> 'List': + """Inserts element at the specified position in list. + + A negative index counts backwards from the end of the list. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + element: A ComputedObject to insert. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.insert', self, index, element + ) + + # TODO(user): - Improve the type of `function` + def iterate(self, function: Any, first: _EeAnyType) -> 'List': + """Iterate an algorithm over a list. + + The algorithm is expected to take two objects, the current list item, and + the result from the previous iteration or the value of first for the first + iteration. + + Args: + function: A function to apply to the list. + first: The first item to pass the function. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.iterate', self, function, first + ) + + def join(self, separator: _StringType = '') -> ee_string.String: + """Returns a string with the list elements with the separator between them. + + Returns a string containing the elements of the list joined together with + the specified separator between elements. + + Note: The string form of list elements which are not strings, numbers, or + booleans is currently not well-defined and subject to change. + + Args: + separator: A string to but between elements. + + Returns: + An ee.String. + """ + + return apifunction.ApiFunction.call_(self.name() + '.join', self, separator) + + def lastIndexOfSubList(self, target: _EeListType) -> ee_number.Number: + """Returns the position of that last instance of target in the list. + + Returns the starting position of the last occurrence of target within list, + or -1 if there is no such occurrence. + + Args: + target: A list to search for. + + Returns: + An integer as an ee.Number. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.lastIndexOfSubList', self, target + ) + + def length(self) -> ee_number.Number: + """Returns the number of elements in list.""" + + return apifunction.ApiFunction.call_(self.name() + '.length', self) + + # TODO(user): - Improve the type of `baseAlgorithm`. + # pylint: disable=invalid-name + def map( + self, baseAlgorithm: _EeAnyType, dropNulls: _EeBoolType = False + ) -> 'List': + # pylint: enable=invalid-name + """Map an algorithm over a list. + + The algorithm is expected to take an Object and return an Object. + + Args: + baseAlgorithm: The function to apply. + dropNulls: If true, the mapped algorithm is allowed to return nulls, and + the elements for which it returns nulls will be dropped. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.map', self, baseAlgorithm, dropNulls + ) + + # pylint: disable-next=redefined-outer-name + def reduce(self, reducer: reducer.Reducer) -> computedobject.ComputedObject: + """Apply a reducer to a list. + + If the reducer takes more than 1 input, then each element in the list is + assumed to be a list of inputs. If the reducer returns a single output, it + is returned directly, otherwise returns a dictionary containing the named + reducer outputs. + + Args: + reducer: An ee.Reducer instance. + + Returns: + Return depends on the specific reducer used. + """ + + return apifunction.ApiFunction.call_(self.name() + '.reduce', self, reducer) + + def remove(self, element: _EeAnyType) -> 'List': + """Removes the first occurrence of the specified element from list. + + Args: + element: The item to remove from the list. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_(self.name() + '.remove', self, element) + + def removeAll(self, other: _EeListType) -> 'List': + """Removes from list all of the elements that are contained in other list. + + Args: + other: A list of the elements to remove. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.removeAll', self, other + ) + + def replace(self, oldval: _EeAnyType, newval: _EeAnyType) -> 'List': + """Replaces the first occurrence of oldval in list with newval. + + Args: + oldval: The value to be replaced. + newval: The value to be put in place of oldval. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.replace', self, oldval, newval + ) + + def replaceAll(self, oldval: _EeAnyType, newval: _EeAnyType) -> 'List': + """Replaces all occurrences of oldval in list with newval. + + Args: + oldval: The value to be replaced. + newval: The value to be put in place of oldval.. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.replaceAll', self, oldval, newval + ) + + def reverse(self) -> 'List': + """Reverses the order of the elements in list.""" + + return apifunction.ApiFunction.call_(self.name() + '.reverse', self) + + def rotate(self, distance: _IntegerType) -> 'List': + """Rotates the elements of the list by the specified distance. + + Elements rotated off the end are pushed onto the other end of the list. + + Args: + distance: How many positions to shift all the values. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.rotate', self, distance + ) + + def set(self, index: _IntegerType, element: _EeAnyType) -> 'List': + """Sets the value at the specified position in list. + + Replaces the value at the specified position in list with element. A + negative index counts backwards from the end of the list. + + Args: + index: Offset from where to get the element. A negative index counts + backwards from the end of the list. + element: A ComputedObject to set at the position of index. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.set', self, index, element + ) + + def shuffle(self, seed: Optional[_IntegerType] = None) -> 'List': + """Randomly permute the specified list. + + Note that the permutation order will always be the same for any given seed, + unless the value for seed is false. + + Args: + seed: A long integer to use as a seed for the randomization. If the + boolean value of false is passed, then a completely random and + unreproducible order will be generated. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_(self.name() + '.shuffle', self, seed) + + def size(self) -> ee_number.Number: + """Returns the number of elements in list.""" + + return apifunction.ApiFunction.call_(self.name() + '.size', self) + + def slice( + self, + start: _IntegerType, + end: Optional[_IntegerType] = None, + step: Optional[_IntegerType] = None, + ) -> 'List': + """Returns a range of elements from a list. + + Negative values for start or end count backwards from the end of the list. + Values greater than the size of the list are valid but are truncated to the + size of list. + + For start and end, a negative index counts backwards from the end of the + list. + + Args: + start: Offset from where to get the elements (inclusive). + end: Offset from where to stop getting the elements (exclusive). + step: How many elements to move forward to get the next element. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.slice', self, start, end, step + ) + + def sort(self, keys: Optional[_EeListType] = None) -> 'List': + """Sorts the list into ascending order. + + If the keys argument is provided, then it is sorted first, and the + elements of list are placed in the same order. + + Args: + keys: Optional keys to sort by. If keys is provided, it must have the + same length as list. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_(self.name() + '.sort', self, keys) + + def splice( + self, + start: _IntegerType, + count: _IntegerType, + other: Optional[_EeListType] = None, + ) -> 'List': + """Removes elements from list and replaces with elements from other. + + Args: + start: Offset from where to begin getting elements. A negative index + counts backwards from the end of the list. + count: How many elements to replace. + other: Elements to put in at the splice location. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.splice', self, start, count, other + ) + + def swap(self, pos1: _IntegerType, pos2: _IntegerType) -> 'List': + """Swaps the elements at the specified positions. + + A negative position counts backwards from the end of the list. + + Args: + pos1: Offset of one element. + pos2: Offset of the second element. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_( + self.name() + '.swap', self, pos1, pos2 + ) + + def unzip(self) -> 'List': + """Rearranges a list of lists. + + Transposes a list of lists, extracting the first element of each inner list + into one list, the second elements into another, etc., up to the length of + the shortest inner list. The remaining items are discarded. The result is a + list of lists. + + Returns: + An ee.List. + """ + + return apifunction.ApiFunction.call_(self.name() + '.unzip', self) + + def zip(self, other: _EeListType) -> 'List': + """Pairs the elements of two lists to create a list of two-element lists. + + When the input lists are of different sizes, the final list has the same + size as the shortest one. + + Args: + other: The list to merge into the current list. + + Returns: + An ee.List with two elements that are both ee.Lists. + """ + + return apifunction.ApiFunction.call_(self.name() + '.zip', self, other) diff --git a/python/ee/featurecollection.py b/python/ee/featurecollection.py index a06166c52..4ae8e36d8 100644 --- a/python/ee/featurecollection.py +++ b/python/ee/featurecollection.py @@ -29,6 +29,7 @@ class FeatureCollection(collection.Collection): _HAS_DYNAMIC_ATTRIBUTES = True @_utils.accept_opt_prefix('opt_column') + @deprecation.WarnForDeprecatedAsset('args') def __init__( self, args: Optional[ diff --git a/python/ee/image.py b/python/ee/image.py index 0c7441815..4f0cf81ae 100644 --- a/python/ee/image.py +++ b/python/ee/image.py @@ -29,6 +29,7 @@ class Image(element.Element): # Tell pytype to not complain about dynamic attributes. _HAS_DYNAMIC_ATTRIBUTES = True + @deprecation.WarnForDeprecatedAsset('args') def __init__( self, args: Optional[Any] = None, version: Optional[float] = None ): diff --git a/python/ee/imagecollection.py b/python/ee/imagecollection.py index 3aceffc4e..5b59afcdd 100644 --- a/python/ee/imagecollection.py +++ b/python/ee/imagecollection.py @@ -9,6 +9,7 @@ from ee import collection from ee import computedobject from ee import data +from ee import deprecation from ee import ee_exception from ee import ee_list from ee import ee_types @@ -23,6 +24,7 @@ class ImageCollection(collection.Collection): # Tell pytype to not complain about dynamic attributes. _HAS_DYNAMIC_ATTRIBUTES = True + @deprecation.WarnForDeprecatedAsset('args') def __init__(self, args: Any): """ImageCollection constructor. diff --git a/python/ee/tests/algorithms.json b/python/ee/tests/algorithms.json index 50f2a99fa..ae288606e 100644 --- a/python/ee/tests/algorithms.json +++ b/python/ee/tests/algorithms.json @@ -11200,7 +11200,7 @@ }] }, { "name": "algorithms/List.get", - "description": "Returns the element at the specified position in list. A negative index counts backwards from the end of the list.", + "description": "Returns the element at the specified position in list. A negative index counts backwards from the end of the list.", "returnType": "Object", "arguments": [{ "argumentName": "list", @@ -11211,7 +11211,7 @@ }] }, { "name": "algorithms/List.getArray", - "description": "Returns the array at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a array, an error will occur.", + "description": "Returns the array at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a array, an error will occur.", "returnType": "Array", "arguments": [{ "argumentName": "list", @@ -11222,7 +11222,7 @@ }] }, { "name": "algorithms/List.getGeometry", - "description": "Returns the geometry at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a geometry, an error will occur.", + "description": "Returns the geometry at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a geometry, an error will occur.", "returnType": "Geometry", "arguments": [{ "argumentName": "list", @@ -11233,7 +11233,7 @@ }] }, { "name": "algorithms/List.getNumber", - "description": "Returns the number at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a number, an error will occur.", + "description": "Returns the number at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a number, an error will occur.", "returnType": "Number", "arguments": [{ "argumentName": "list", @@ -11244,7 +11244,7 @@ }] }, { "name": "algorithms/List.getString", - "description": "Returns the string at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a string, an error will occur.", + "description": "Returns the string at the specified position in list. A negative index counts backwards from the end of the list. If the value is not a string, an error will occur.", "returnType": "String", "arguments": [{ "argumentName": "list", @@ -11255,7 +11255,7 @@ }] }, { "name": "algorithms/List.indexOf", - "description": "Returns the position of the first occurrence of target in list, or -1 if list does not contain target.", + "description": "Returns the position of the first occurrence of element in list, or -1 if list does not contain element.", "returnType": "Integer", "arguments": [{ "argumentName": "list", @@ -11291,7 +11291,7 @@ }] }, { "name": "algorithms/List.iterate", - "description": "Iterate an algorithm over a list. The algorithm is expected to take two objects, the current list item, and the result from the previous iteration or the value of first for the first iteration.", + "description": "Iterate an algorithm over a list. The algorithm is expected to take two objects, the current list item, and the result from the previous iteration or the value of first for the first iteration.", "returnType": "Object", "arguments": [{ "argumentName": "list", @@ -11337,7 +11337,7 @@ }] }, { "name": "algorithms/List.map", - "description": "Map an algorithm over a list. The algorithm is expected to take an Object and return an Object.", + "description": "Map an algorithm over a list. The algorithm is expected to take an Object and return an Object.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11354,7 +11354,7 @@ }] }, { "name": "algorithms/List.reduce", - "description": "Apply a reducer to a list. If the reducer takes more than 1 input, then each element in the list is assumed to be a list of inputs. If the reducer returns a single output, it is returned directly, otherwise returns a dictionary containing the named reducer outputs.", + "description": "Apply a reducer to a list. If the reducer takes more than 1 input, then each element in the list is assumed to be a list of inputs. If the reducer returns a single output, it is returned directly, otherwise returns a dictionary containing the named reducer outputs.", "returnType": "Object", "arguments": [{ "argumentName": "list", @@ -11445,7 +11445,7 @@ }] }, { "name": "algorithms/List.sequence", - "description": "Generate a sequence of numbers from start to end (inclusive) in increments of step, or in count equally-spaced increments. If end is not specified it is computed from start + step * count, so at least one of end or count must be specified.", + "description": "Generate a sequence of numbers from start to end (inclusive) in increments of step, or in count equally-spaced increments. If end is not specified it is computed from start + step * count, so at least one of end or count must be specified.", "returnType": "List\u003cNumber\u003e", "arguments": [{ "argumentName": "start", @@ -11468,7 +11468,7 @@ }] }, { "name": "algorithms/List.set", - "description": "Replaces the value at the specified position in list with element. A negative index counts backwards from the end of the list.", + "description": "Replaces the value at the specified position in list with element. A negative index counts backwards from the end of the list.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11482,7 +11482,7 @@ }] }, { "name": "algorithms/List.shuffle", - "description": "Randomly permute the specified list. Note that the permutation order will always be the same for any given seed, unless the value for seed is \u0027false\u0027.", + "description": "Randomly permute the specified list. Note that the permutation order will always be the same for any given seed, unless the value for seed is \u0027false\u0027.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11490,7 +11490,7 @@ }, { "argumentName": "seed", "type": "Object", - "description": "A long integer to use as a seed for the randomization. If the boolean value of \u0027false\u0027 is passed, then a completely random and unreproducible order will be generated.", + "description": "A long integer to use as a seed for the randomization. If the boolean value of \u0027false\u0027 is passed, then a completely random and unreproducible order will be generated.", "optional": true, "defaultValue": null }] @@ -11504,7 +11504,7 @@ }] }, { "name": "algorithms/List.slice", - "description": "Returns a portion of list between the start index, inclusive, and end index, exclusive. Negative values for start or end count backwards from the end of the list. Values greater than the size of the list are legal but are truncated to the size of list.", + "description": "Returns a portion of list between the start index, inclusive, and end index, exclusive. Negative values for start or end count backwards from the end of the list. Values greater than the size of the list are legal but are truncated to the size of list.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11525,7 +11525,7 @@ }] }, { "name": "algorithms/List.sort", - "description": "Sorts the list into ascending order. If the \u0027keys\u0027 argument is provided, then it is sorted first, and the elements of \u0027list\u0027 are placed in the same order.", + "description": "Sorts the list into ascending order. If the \u0027keys\u0027 argument is provided, then it is sorted first, and the elements of \u0027list\u0027 are placed in the same order.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11534,13 +11534,13 @@ }, { "argumentName": "keys", "type": "List\u003cObject\u003e", - "description": "Optional keys to sort by. If \u0027keys\u0027 is provided, it must have the same length as \u0027list\u0027.", + "description": "Optional keys to sort by. If \u0027keys\u0027 is provided, it must have the same length as \u0027list\u0027.", "optional": true, "defaultValue": null }] }, { "name": "algorithms/List.splice", - "description": "Starting at the start index, removes count elements from list and insert the contents of other at that location. If start is negative, it counts backwards from the end of the list.", + "description": "Starting at the start index, removes count elements from list and insert the contents of other at that location. If start is negative, it counts backwards from the end of the list.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11559,7 +11559,7 @@ }] }, { "name": "algorithms/List.swap", - "description": "Swaps the elements at the specified positions. A negative position counts backwards from the end of the list.", + "description": "Swaps the elements at the specified positions. A negative position counts backwards from the end of the list.", "returnType": "List\u003cObject\u003e", "arguments": [{ "argumentName": "list", @@ -11581,7 +11581,7 @@ }] }, { "name": "algorithms/List.zip", - "description": "Pairs the elements of two lists to create a list of two-element lists. When the input lists are of different sizes, the final list has the same size as the shortest one.", + "description": "Pairs the elements of two lists to create a list of two-element lists. When the input lists are of different sizes, the final list has the same size as the shortest one.", "returnType": "List\u003cList\u003cObject\u003e\u003e", "arguments": [{ "argumentName": "list", diff --git a/python/ee/tests/batch_test.py b/python/ee/tests/batch_test.py index 0a6e2c115..cb20a1c22 100644 --- a/python/ee/tests/batch_test.py +++ b/python/ee/tests/batch_test.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Test for the ee.batch module.""" + from typing import Any, Optional import unittest from unittest import mock @@ -10,30 +11,51 @@ from ee import data import unittest -TASK_STATUS_1 = { - 'description': 'FirstTestTask', - 'id': 'TEST1', - 'source_url': 'http://example.org/', - 'state': 'RUNNING', - 'task_type': 'EXPORT_IMAGE', - 'creation_timestamp_ms': 7, - 'start_timestamp_ms': 13, - 'update_timestamp_ms': 42, +RUNNING_OPERATION = { + 'metadata': { + 'createTime': '1970-01-01T00:00:00.00Z', + 'startTime': '1970-01-01T00:00:01.00Z', + 'updateTime': '1970-01-01T00:01:00.00Z', + 'description': 'FirstTestTask', + 'state': 'RUNNING', + 'type': 'EXPORT_IMAGE', + 'destinationUris': ['https://test.com'], + 'attempt': 42, + 'priority': 100, + }, + 'done': False, + 'name': 'projects/test-project/operations/TEST1', +} +SUCCEEDED_OPERATION = { + 'metadata': { + 'createTime': '1970-01-01T00:00:00.00Z', + 'startTime': '1970-01-01T00:00:01.00Z', + 'updateTime': '1970-01-01T00:01:00.00Z', + 'description': 'Ingest image: "an/image"', + 'state': 'SUCCEEDED', + 'type': 'EXPORT_IMAGE', + 'destinationUris': ['https://test.com'], + 'attempt': 42, + 'priority': 100, + }, + 'done': False, + 'name': 'projects/test-project/operations/TEST2', } -TASK_STATUS_2 = { - 'description': 'SecondTestTask', - 'id': 'TEST2', - 'state': 'FAILED', - 'task_type': 'EXPORT_FEATURES', - 'creation_timestamp_ms': 17, - 'start_timestamp_ms': 113, - 'update_timestamp_ms': 142, - 'error_message': 'Explosions.', +UNKNOWN_OPERATION = { + 'metadata': { + 'state': 'UNKNOWN', + }, + 'done': True, + 'name': 'projects/test-project/operations/TEST2', } class TaskTest(unittest.TestCase): + def setUp(self): + super().setUp() + data.setCloudApiUserProject('test-project') + def testStartWithoutConfig(self): task = batch.Task('an id', 'a task type', 'a state') self.assertIsNone((task.config)) @@ -49,29 +71,56 @@ def testStartUnknownTaskType(self): task.start() def testStatusWithId(self): - task = batch.Task('an id', 'a task type', 'a state') - with mock.patch.object(data, 'getTaskStatus', return_value=[TASK_STATUS_1]): + task = batch.Task('test_1', 'a task type', 'a state') + with mock.patch.object( + data, 'getOperation', return_value=RUNNING_OPERATION + ) as m: + self.assertEqual('RUNNING', task.status()['state']) + self.assertEqual( + m.call_args.args[0], 'projects/test-project/operations/test_1' + ) + + def testStatusWithName(self): + task = batch.Task( + None, + 'a task type', + 'a state', + name='projects/test-project/operations/test_1', + ) + with mock.patch.object( + data, 'getOperation', return_value=RUNNING_OPERATION + ) as m: self.assertEqual('RUNNING', task.status()['state']) + self.assertEqual( + m.call_args.args[0], 'projects/test-project/operations/test_1' + ) def testStatusWithIdStateUnknown(self): task = batch.Task('an id', 'a task type', 'a state') with mock.patch.object( - data, 'getTaskStatus', return_value=[{'state': 'UNKNOWN'}] - ): - self.assertEqual({'state': 'UNSUBMITTED'}, task.status()) + data, 'getOperation', return_value=UNKNOWN_OPERATION + ) as m: + self.assertEqual('UNSUBMITTED', task.status()['state']) + self.assertEqual( + m.call_args.args[0], 'projects/test-project/operations/an id' + ) - def testStatusWithoutId(self): + def testStatusWithoutIdOrName(self): task = batch.Task(None, 'a task type', 'a state') - self.assertEqual({'state': 'UNSUBMITTED'}, task.status()) + self.assertEqual('UNSUBMITTED', task.status()['state']) def testActive(self): task = batch.Task('an id', 'a task type', 'a state') - with mock.patch.object(data, 'getTaskStatus', return_value=[TASK_STATUS_1]): + with mock.patch.object( + data, 'getOperation', return_value=RUNNING_OPERATION + ): self.assertTrue(task.active()) def testNotActive(self): task = batch.Task('an id', 'a task type', 'a state') - with mock.patch.object(data, 'getTaskStatus', return_value=[TASK_STATUS_2]): + with mock.patch.object( + data, 'getOperation', return_value=SUCCEEDED_OPERATION + ): self.assertFalse(task.active()) def testReprWithoutConfig(self): @@ -128,6 +177,7 @@ def testExportVideoCannotInit(self): class BatchTestCase(apitestcase.ApiTestCase): """A test case for batch functionality.""" + start_call_params: Optional[Any] update_call_params: Optional[Any] @@ -154,33 +204,35 @@ def testTaskStartCloudApi(self): def testTaskCancelCloudApi(self): mock_cloud_api_resource = mock.MagicMock() - mock_cloud_api_resource.projects().operations().list( - ).execute.return_value = { + mock_cloud_api_resource.projects().operations().list().execute.return_value = { 'operations': [{ 'name': 'projects/earthengine-legacy/operations/TEST1', 'metadata': {}, }] } - mock_cloud_api_resource.projects().operations( - ).list_next.return_value = None + mock_cloud_api_resource.projects().operations().list_next.return_value = ( + None + ) with apitestcase.UsingCloudApi(cloud_api_resource=mock_cloud_api_resource): task = ee.batch.Task.list()[0] task.cancel() - cancel_args = mock_cloud_api_resource.projects().operations( - ).cancel.call_args + cancel_args = ( + mock_cloud_api_resource.projects().operations().cancel.call_args + ) self.assertEqual( - cancel_args[1]['name'], - 'projects/earthengine-legacy/operations/TEST1') + cancel_args[1]['name'], 'projects/earthengine-legacy/operations/TEST1' + ) def testExportImageTrivialRegionCloudApi(self): """Verifies the task created by Export.image() with a trivial region.""" with apitestcase.UsingCloudApi(): region = [0, 0, 1, 0, 1, 1] task = ee.batch.Export.image.toAsset( - ee.Image(42), assetId='users/foo/bar', region=region, scale=1000) + ee.Image(42), assetId='users/foo/bar', region=region, scale=1000 + ) expected_expression = ee.Image(42).clipToBoundsAndScale( - geometry=ee.Geometry.LineString(region), - scale=1000) + geometry=ee.Geometry.LineString(region), scale=1000 + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_IMAGE', task.task_type) @@ -190,13 +242,15 @@ def testExportImageTrivialRegionCloudApi(self): 'expression': expected_expression, 'assetExportOptions': { 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), } }, 'description': 'myExportImageTask', }, - task.config) + task.config, + ) def testExportImageCloudApi(self): """Verifies the task created by Export.image().""" @@ -213,10 +267,11 @@ def testExportImageCloudApi(self): formatOptions={'noData': 1}, ) task = ee.batch.Export.image(ee.Image(1), 'TestDescription', config) - expected_expression = ee.Image(1).reproject( - 'foo', crsTransform=[ - 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 - ]).clip(region) + expected_expression = ( + ee.Image(1) + .reproject('foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + .clip(region) + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_IMAGE', task.task_type) @@ -272,48 +327,45 @@ def testExportImageWithTfRecordCloudApi(self): 'tensorDepths': {'b1': 1, 'b2': 2}, 'sequenceData': True, 'collapseBands': True, - 'maskedThreshold': .5, + 'maskedThreshold': 0.5, }, ) task = ee.batch.Export.image(ee.Image(1), 'TestDescription', config) - expected_expression = ee.Image(1).reproject( - 'foo', crsTransform=[ - 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 - ]).clip(region) + expected_expression = ( + ee.Image(1) + .reproject('foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + .clip(region) + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_IMAGE', task.task_type) self.assertEqual('UNSUBMITTED', task.state) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'TestDescription', - 'fileExportOptions': { - 'fileFormat': 'TF_RECORD_IMAGE', - 'driveDestination': { - 'filenamePrefix': 'TestDescription' - }, - 'tfRecordOptions': { - 'tileDimensions': { - 'width': 256, - 'height': 256 - }, - 'marginDimensions': { - 'width': 32, - 'height': 32, + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'TestDescription', + 'fileExportOptions': { + 'fileFormat': 'TF_RECORD_IMAGE', + 'driveDestination': {'filenamePrefix': 'TestDescription'}, + 'tfRecordOptions': { + 'tileDimensions': {'width': 256, 'height': 256}, + 'marginDimensions': { + 'width': 32, + 'height': 32, + }, + 'compress': True, + 'maxSizeBytes': {'value': '1000000000'}, + 'defaultValue': -999, + 'tensorDepths': {'b1': 1, 'b2': 2}, + 'sequenceData': True, + 'collapseBands': True, + 'maxMaskedRatio': {'value': 0.5}, }, - 'compress': True, - 'maxSizeBytes': {'value': '1000000000'}, - 'defaultValue': -999, - 'tensorDepths': {'b1': 1, 'b2': 2}, - 'sequenceData': True, - 'collapseBands': True, - 'maxMaskedRatio': {'value': 0.5}, }, + 'maxPixels': {'value': '10000000000'}, }, - 'maxPixels': { - 'value': '10000000000' - }, - }, task.config) + task.config, + ) def testExportImageToAssetCloudApi(self): """Verifies the Asset export task created by Export.image.toAsset().""" @@ -321,14 +373,16 @@ def testExportImageToAssetCloudApi(self): config = dict( image=ee.Image(1), assetId='users/foo/bar', - pyramidingPolicy={'B1': 'min'}) + pyramidingPolicy={'B1': 'min'}, + ) expected_expression = ee.Image(1) # Test keyed parameters. task_keyed = ee.batch.Export.image.toAsset( image=config['image'], assetId=config['assetId'], - pyramidingPolicy=config['pyramidingPolicy']) + pyramidingPolicy=config['pyramidingPolicy'], + ) self.assertIsNone(task_keyed.id) self.assertIsNone(task_keyed.name) self.assertEqual('EXPORT_IMAGE', task_keyed.task_type) @@ -339,15 +393,15 @@ def testExportImageToAssetCloudApi(self): 'description': 'myExportImageTask', 'assetExportOptions': { 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), }, - 'pyramidingPolicyOverrides': { - 'B1': 'MIN' - } + 'pyramidingPolicyOverrides': {'B1': 'MIN'}, }, }, - task_keyed.config) + task_keyed.config, + ) task_ordered = ee.batch.Export.image.toAsset( config['image'], @@ -355,7 +409,8 @@ def testExportImageToAssetCloudApi(self): config['assetId'], maxPixels=1000, maxWorkers=100, - shardSize=4) + shardSize=4, + ) self.assertEqual('EXPORT_IMAGE', task_ordered.task_type) self.assertEqual('UNSUBMITTED', task_ordered.state) self.assertEqual( @@ -364,48 +419,17 @@ def testExportImageToAssetCloudApi(self): 'description': 'TestDescription', 'assetExportOptions': { 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), }, - 'tileSize': { - 'value': 4 - } - }, - 'maxPixels': { - 'value': '1000' + 'tileSize': {'value': 4}, }, - 'maxWorkers': { - 'value': 100 - } + 'maxPixels': {'value': '1000'}, + 'maxWorkers': {'value': 100}, }, - task_ordered.config) - - task_overwrite_with_priority = ee.batch.Export.image.toAsset( - image=config['image'], - assetId=config['assetId'], - overwrite=True, - priority=999, + task_ordered.config, ) - self.assertIsNone(task_overwrite_with_priority.id) - self.assertIsNone(task_overwrite_with_priority.name) - self.assertEqual('EXPORT_IMAGE', task_overwrite_with_priority.task_type) - self.assertEqual('UNSUBMITTED', task_overwrite_with_priority.state) - self.assertEqual( - { - 'expression': expected_expression, - 'description': 'myExportImageTask', - 'assetExportOptions': { - 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', - 'overwrite': - True - } - }, - 'priority': { - 'value': 999 - } - }, task_overwrite_with_priority.config) def testExportImageToAssetCloudApi_withTileSize(self): """Verifies the Asset export task created by Export.image.toAsset().""" @@ -413,7 +437,8 @@ def testExportImageToAssetCloudApi_withTileSize(self): config = dict( image=ee.Image(1), assetId='users/foo/bar', - pyramidingPolicy={'B1': 'min'}) + pyramidingPolicy={'B1': 'min'}, + ) expected_expression = ee.Image(1) task_ordered = ee.batch.Export.image.toAsset( @@ -422,7 +447,8 @@ def testExportImageToAssetCloudApi_withTileSize(self): config['assetId'], maxPixels=1000, maxWorkers=100, - tileSize=4) + tileSize=4, + ) self.assertEqual('EXPORT_IMAGE', task_ordered.task_type) self.assertEqual('UNSUBMITTED', task_ordered.state) self.assertEqual( @@ -431,21 +457,17 @@ def testExportImageToAssetCloudApi_withTileSize(self): 'description': 'TestDescription', 'assetExportOptions': { 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), }, - 'tileSize': { - 'value': 4 - } - }, - 'maxPixels': { - 'value': '1000' + 'tileSize': {'value': 4}, }, - 'maxWorkers': { - 'value': 100 - } + 'maxPixels': {'value': '1000'}, + 'maxWorkers': {'value': 100}, }, - task_ordered.config) + task_ordered.config, + ) def testExportImageToCloudStorageCloudApi(self): """Verifies the Cloud Storage export task created by Export.image().""" @@ -454,71 +476,92 @@ def testExportImageToCloudStorageCloudApi(self): config = dict( region=region['coordinates'], maxPixels=10**10, - outputBucket='test-bucket') + outputBucket='test-bucket', + ) task = ee.batch.Export.image.toCloudStorage( - ee.Image(1), 'TestDescription', config['outputBucket'], None, None, - config['region'], None, None, None, config['maxPixels'], None, - [512, 2048], True) + ee.Image(1), + 'TestDescription', + config['outputBucket'], + None, + None, + config['region'], + None, + None, + None, + config['maxPixels'], + None, + [512, 2048], + True, + ) expected_expression = ee.Image(1).clip(region) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_IMAGE', task.task_type) self.assertEqual('UNSUBMITTED', task.state) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'TestDescription', - 'fileExportOptions': { - 'fileFormat': 'GEO_TIFF', - 'cloudStorageDestination': { - 'bucket': 'test-bucket', - 'filenamePrefix': 'TestDescription' - }, - 'geoTiffOptions': { - 'tileDimensions': { - 'width': 512, - 'height': 2048 + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'TestDescription', + 'fileExportOptions': { + 'fileFormat': 'GEO_TIFF', + 'cloudStorageDestination': { + 'bucket': 'test-bucket', + 'filenamePrefix': 'TestDescription', + }, + 'geoTiffOptions': { + 'tileDimensions': {'width': 512, 'height': 2048}, + 'skipEmptyFiles': True, }, - 'skipEmptyFiles': True, }, + 'maxPixels': {'value': '10000000000'}, }, - 'maxPixels': { - 'value': '10000000000' - }, - }, task.config) + task.config, + ) config = dict( region=region['coordinates'], maxPixels=10**10, outputBucket='test-bucket', - priority=999) + priority=999, + ) task_with_priority = ee.batch.Export.image.toCloudStorage( - ee.Image(1), 'TestDescription', config['outputBucket'], None, None, - config['region'], None, None, None, config['maxPixels'], None, - [512, 2048], True, None, None, config['priority']) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'TestDescription', - 'fileExportOptions': { - 'fileFormat': 'GEO_TIFF', - 'cloudStorageDestination': { - 'bucket': 'test-bucket', - 'filenamePrefix': 'TestDescription' - }, - 'geoTiffOptions': { - 'tileDimensions': { - 'width': 512, - 'height': 2048 + ee.Image(1), + 'TestDescription', + config['outputBucket'], + None, + None, + config['region'], + None, + None, + None, + config['maxPixels'], + None, + [512, 2048], + True, + None, + None, + config['priority'], + ) + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'TestDescription', + 'fileExportOptions': { + 'fileFormat': 'GEO_TIFF', + 'cloudStorageDestination': { + 'bucket': 'test-bucket', + 'filenamePrefix': 'TestDescription', + }, + 'geoTiffOptions': { + 'tileDimensions': {'width': 512, 'height': 2048}, + 'skipEmptyFiles': True, }, - 'skipEmptyFiles': True, }, + 'maxPixels': {'value': '10000000000'}, + 'priority': {'value': 999}, }, - 'maxPixels': { - 'value': '10000000000' - }, - 'priority': { - 'value': 999 - } - }, task_with_priority.config) + task_with_priority.config, + ) def testExportImageToGoogleDriveCloudApi(self): """Verifies the Drive destined task created by Export.image.toDrive().""" @@ -531,9 +574,13 @@ def testExportImageToGoogleDriveCloudApi(self): maxPixels=10**10, crs='foo', crsTransform='[9,8,7,6,5,4]', - shardSize=512) - expected_expression = ee.Image(1).reproject( - 'foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]).clip(region) + shardSize=512, + ) + expected_expression = ( + ee.Image(1) + .reproject('foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + .clip(region) + ) self.assertIsNone(drive_task_by_keys.id) self.assertIsNone(drive_task_by_keys.name) self.assertEqual('EXPORT_IMAGE', drive_task_by_keys.task_type) @@ -546,44 +593,47 @@ def testExportImageToGoogleDriveCloudApi(self): 'fileFormat': 'GEO_TIFF', 'driveDestination': { 'folder': 'foo', - 'filenamePrefix': 'myExportImageTask' + 'filenamePrefix': 'myExportImageTask', }, - 'geoTiffOptions': { - 'tileSize': { - 'value': 512 - } - } + 'geoTiffOptions': {'tileSize': {'value': 512}}, }, - 'maxPixels': { - 'value': '10000000000' - }, - }, drive_task_by_keys.config) + 'maxPixels': {'value': '10000000000'}, + }, + drive_task_by_keys.config, + ) drive_task_with_old_keys = ee.batch.Export.image.toDrive( - image=ee.Image(1), region=region['coordinates'], driveFolder='foo', - driveFileNamePrefix='fooExport', maxPixels=10**10, - crs='foo', crs_transform='[9,8,7,6,5,4]') + image=ee.Image(1), + region=region['coordinates'], + driveFolder='foo', + driveFileNamePrefix='fooExport', + maxPixels=10**10, + crs='foo', + crs_transform='[9,8,7,6,5,4]', + ) self.assertIsNone(drive_task_with_old_keys.id) self.assertIsNone(drive_task_by_keys.name) self.assertEqual('EXPORT_IMAGE', drive_task_with_old_keys.task_type) self.assertEqual('UNSUBMITTED', drive_task_with_old_keys.state) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'myExportImageTask', - 'fileExportOptions': { - 'fileFormat': 'GEO_TIFF', - 'driveDestination': { - 'folder': 'foo', - 'filenamePrefix': 'fooExport' - } - }, - 'maxPixels': { - 'value': '10000000000' + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'myExportImageTask', + 'fileExportOptions': { + 'fileFormat': 'GEO_TIFF', + 'driveDestination': { + 'folder': 'foo', + 'filenamePrefix': 'fooExport', + }, + }, + 'maxPixels': {'value': '10000000000'}, }, - }, drive_task_with_old_keys.config) + drive_task_with_old_keys.config, + ) - with self.assertRaisesRegex(ee.EEException, - 'Unknown configuration options.*'): + with self.assertRaisesRegex( + ee.EEException, 'Unknown configuration options.*' + ): ee.batch.Export.image.toDrive(image=ee.Image(1), framesPerSecond=30) drive_task_with_priority = ee.batch.Export.image.toDrive( @@ -594,9 +644,13 @@ def testExportImageToGoogleDriveCloudApi(self): crs='foo', crsTransform='[9,8,7,6,5,4]', shardSize=512, - priority=999) - expected_expression = ee.Image(1).reproject( - 'foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]).clip(region) + priority=999, + ) + expected_expression = ( + ee.Image(1) + .reproject('foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + .clip(region) + ) self.assertEqual( { 'expression': expected_expression, @@ -605,21 +659,15 @@ def testExportImageToGoogleDriveCloudApi(self): 'fileFormat': 'GEO_TIFF', 'driveDestination': { 'folder': 'foo', - 'filenamePrefix': 'myExportImageTask' + 'filenamePrefix': 'myExportImageTask', }, - 'geoTiffOptions': { - 'tileSize': { - 'value': 512 - } - } + 'geoTiffOptions': {'tileSize': {'value': 512}}, }, - 'maxPixels': { - 'value': '10000000000' - }, - 'priority': { - 'value': 999 - } - }, drive_task_with_priority.config) + 'maxPixels': {'value': '10000000000'}, + 'priority': {'value': 999}, + }, + drive_task_with_priority.config, + ) def testExportMapToCloudStorageCloudApi(self): """Verifies the task created by Export.map.toCloudStorage().""" @@ -629,7 +677,8 @@ def testExportMapToCloudStorageCloudApi(self): bucket='test-bucket', maxZoom=7, path='foo/gcs/path', - maxWorkers=100) + maxWorkers=100, + ) # Test keyed parameters. task_keyed = ee.batch.Export.map.toCloudStorage( @@ -638,60 +687,80 @@ def testExportMapToCloudStorageCloudApi(self): maxZoom=config['maxZoom'], path=config['path'], maxWorkers=config['maxWorkers'], - bucketCorsUris=['*']) + bucketCorsUris=['*'], + ) expected_expression = ee.Image(1) self.assertIsNone(task_keyed.id) self.assertIsNone(task_keyed.name) self.assertEqual('EXPORT_TILES', task_keyed.task_type) self.assertEqual('UNSUBMITTED', task_keyed.state) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'myExportMapTask', - 'tileOptions': { - 'endZoom': config['maxZoom'], - }, - 'tileExportOptions': { - 'fileFormat': 'AUTO_JPEG_PNG', - 'cloudStorageDestination': { - 'bucket': config['bucket'], - 'filenamePrefix': config['path'], - 'permissions': 'PUBLIC', - 'bucketCorsUris': ['*'], + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'myExportMapTask', + 'tileOptions': { + 'endZoom': config['maxZoom'], + }, + 'tileExportOptions': { + 'fileFormat': 'AUTO_JPEG_PNG', + 'cloudStorageDestination': { + 'bucket': config['bucket'], + 'filenamePrefix': config['path'], + 'permissions': 'PUBLIC', + 'bucketCorsUris': ['*'], + }, }, + 'maxWorkers': {'value': 100}, }, - 'maxWorkers': {'value': 100} - }, task_keyed.config) + task_keyed.config, + ) - with self.assertRaisesRegex(ee.EEException, - 'Unknown configuration options.*'): + with self.assertRaisesRegex( + ee.EEException, 'Unknown configuration options.*' + ): config_with_bogus_option = config.copy() config_with_bogus_option['framesPerSecond'] = 30 ee.batch.Export.map.toCloudStorage(**config_with_bogus_option) # Test ordered parameters. task_ordered = ee.batch.Export.map.toCloudStorage( - config['image'], 'TestDescription', config['bucket'], 'jpeg', None, - False, None, 30, None, None, None, 'aFakeKey', maxWorkers=100) + config['image'], + 'TestDescription', + config['bucket'], + 'jpeg', + None, + False, + None, + 30, + None, + None, + None, + 'aFakeKey', + maxWorkers=100, + ) self.assertIsNone(task_ordered.id) self.assertIsNone(task_ordered.name) self.assertEqual('EXPORT_TILES', task_ordered.task_type) self.assertEqual('UNSUBMITTED', task_ordered.state) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'TestDescription', - 'tileOptions': { - 'scale': 30, - 'mapsApiKey': 'aFakeKey', - }, - 'tileExportOptions': { - 'fileFormat': 'JPEG', - 'cloudStorageDestination': { - 'bucket': config['bucket'], - 'filenamePrefix': 'TestDescription', + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'TestDescription', + 'tileOptions': { + 'scale': 30, + 'mapsApiKey': 'aFakeKey', }, + 'tileExportOptions': { + 'fileFormat': 'JPEG', + 'cloudStorageDestination': { + 'bucket': config['bucket'], + 'filenamePrefix': 'TestDescription', + }, + }, + 'maxWorkers': {'value': 100}, }, - 'maxWorkers': {'value': 100} - }, task_ordered.config) + task_ordered.config, + ) config = dict( image=ee.Image(1), @@ -699,7 +768,8 @@ def testExportMapToCloudStorageCloudApi(self): maxZoom=7, path='foo/gcs/path', maxWorkers=100, - priority=999) + priority=999, + ) task_with_priority = ee.batch.Export.map.toCloudStorage( image=config['image'], bucket=config['bucket'], @@ -707,28 +777,30 @@ def testExportMapToCloudStorageCloudApi(self): path=config['path'], maxWorkers=config['maxWorkers'], bucketCorsUris=['*'], - priority=config['priority']) + priority=config['priority'], + ) expected_expression = ee.Image(1) - self.assertEqual({ - 'expression': expected_expression, - 'description': 'myExportMapTask', - 'tileOptions': { - 'endZoom': config['maxZoom'], - }, - 'tileExportOptions': { - 'fileFormat': 'AUTO_JPEG_PNG', - 'cloudStorageDestination': { - 'bucket': config['bucket'], - 'filenamePrefix': config['path'], - 'permissions': 'PUBLIC', - 'bucketCorsUris': ['*'], + self.assertEqual( + { + 'expression': expected_expression, + 'description': 'myExportMapTask', + 'tileOptions': { + 'endZoom': config['maxZoom'], + }, + 'tileExportOptions': { + 'fileFormat': 'AUTO_JPEG_PNG', + 'cloudStorageDestination': { + 'bucket': config['bucket'], + 'filenamePrefix': config['path'], + 'permissions': 'PUBLIC', + 'bucketCorsUris': ['*'], + }, }, + 'maxWorkers': {'value': 100}, + 'priority': {'value': 999}, }, - 'maxWorkers': {'value': 100}, - 'priority': { - 'value': 999 - } - }, task_with_priority.config) + task_with_priority.config, + ) def testExportMapToCloudStorageCloudApi_WithV1Parameters(self): """Verifies Export.map.toCloudStorage() tasks with v1 parameters.""" @@ -743,7 +815,8 @@ def testExportMapToCloudStorageCloudApi_WithV1Parameters(self): path='foo/gcs/path', skipEmptyTiles=True, skipEmpty=False, # Takes precedence over skipEmpty. - maxWorkers=100) + maxWorkers=100, + ) # Test keyed parameters. task_keyed = ee.batch.Export.map.toCloudStorage( @@ -756,7 +829,8 @@ def testExportMapToCloudStorageCloudApi_WithV1Parameters(self): path=config['path'], maxWorkers=config['maxWorkers'], skipEmptyTiles=config['skipEmptyTiles'], - skipEmpty=config['skipEmpty']) + skipEmpty=config['skipEmpty'], + ) expected_expression = ee.Image(1) self.assertIsNone(task_keyed.id) self.assertIsNone(task_keyed.name) @@ -779,39 +853,45 @@ def testExportMapToCloudStorageCloudApi_WithV1Parameters(self): 'permissions': 'PUBLIC', }, }, - 'maxWorkers': { - 'value': 100 - } - }, task_keyed.config) + 'maxWorkers': {'value': 100}, + }, + task_keyed.config, + ) def testExportTableCloudApi(self): """Verifies the task created by Export.table().""" with apitestcase.UsingCloudApi(): task = ee.batch.Export.table( - ee.FeatureCollection('drive test FC'), config={'maxWorkers': 100}) + ee.FeatureCollection('drive test FC'), config={'maxWorkers': 100} + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_FEATURES', task.task_type) self.assertEqual('UNSUBMITTED', task.state) - self.assertEqual({ - 'expression': ee.FeatureCollection('drive test FC'), - 'description': 'myExportTableTask', - 'fileExportOptions': { - 'fileFormat': 'CSV', - 'driveDestination': { - 'filenamePrefix': 'myExportTableTask', - } + self.assertEqual( + { + 'expression': ee.FeatureCollection('drive test FC'), + 'description': 'myExportTableTask', + 'fileExportOptions': { + 'fileFormat': 'CSV', + 'driveDestination': { + 'filenamePrefix': 'myExportTableTask', + }, + }, + 'maxWorkers': {'value': 100}, }, - 'maxWorkers': {'value': 100}, - }, task.config) + task.config, + ) def testExportTableCloudApiBogusParameter(self): """Verifies that bogus parameters are rejected.""" with apitestcase.UsingCloudApi(): - with self.assertRaisesRegex(ee.EEException, - 'Unknown configuration options.*'): + with self.assertRaisesRegex( + ee.EEException, 'Unknown configuration options.*' + ): ee.batch.Export.table.toDrive( - ee.FeatureCollection('drive test FC'), framesPerSecond=30) + ee.FeatureCollection('drive test FC'), framesPerSecond=30 + ) def testExportTableSelectorsCloudApi(self): """Verifies that table export accepts a list or tuple of selectors.""" @@ -819,18 +899,21 @@ def testExportTableSelectorsCloudApi(self): task = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), selectors=['ab', 'bb', 'c'], - outputBucket='foo') + outputBucket='foo', + ) self.assertEqual(['ab', 'bb', 'c'], task.config['selectors']) task = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), selectors=('x', 'y'), - outputBucket='foo') + outputBucket='foo', + ) self.assertEqual(['x', 'y'], task.config['selectors']) # Single string should work too. task = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), selectors='ab,cd,ef', - outputBucket='foo') + outputBucket='foo', + ) self.assertEqual(['ab', 'cd', 'ef'], task.config['selectors']) def testExportTableToCloudStorageCloudApi(self): @@ -839,7 +922,8 @@ def testExportTableToCloudStorageCloudApi(self): task = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), outputBucket='test-bucket', - maxVertices=1e6) + maxVertices=1e6, + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_FEATURES', task.task_type) @@ -855,10 +939,10 @@ def testExportTableToCloudStorageCloudApi(self): 'filenamePrefix': 'myExportTableTask', }, }, - 'maxVertices': { - 'value': int(1e6) - }, - }, task.config) + 'maxVertices': {'value': int(1e6)}, + }, + task.config, + ) task_with_priority = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), @@ -897,11 +981,9 @@ def testExportTableToGoogleDriveCloudApi(self): 'fileFormat': test_format, 'driveDestination': { 'filenamePrefix': test_file_name_prefix, - } + }, }, - 'maxVertices': { - 'value': 0 - } + 'maxVertices': {'value': 0}, } # Ordered parameters @@ -911,7 +993,8 @@ def testExportTableToGoogleDriveCloudApi(self): None, test_file_name_prefix, test_format, - maxVertices=0) + maxVertices=0, + ) self.assertIsNone(task_ordered.id) self.assertIsNone(task_ordered.name) self.assertEqual('EXPORT_FEATURES', task_ordered.task_type) @@ -922,7 +1005,8 @@ def testExportTableToGoogleDriveCloudApi(self): expected_config['description'] = 'myExportTableTask' expected_config['fileExportOptions']['fileFormat'] = 'CSV' expected_config['fileExportOptions']['driveDestination'][ - 'folder'] = 'fooFolder' + 'folder' + ] = 'fooFolder' # Test that deprecated parameters (driveFolder and driveFileNamePrefix) # still work. @@ -930,7 +1014,8 @@ def testExportTableToGoogleDriveCloudApi(self): collection=test_collection, driveFolder='fooFolder', driveFileNamePrefix='fooDriveFileNamePrefix', - maxVertices=0) + maxVertices=0, + ) self.assertEqual('EXPORT_FEATURES', task_old_keys.task_type) self.assertEqual('UNSUBMITTED', task_old_keys.state) self.assertEqual(expected_config, task_old_keys.config) @@ -940,7 +1025,8 @@ def testExportTableToGoogleDriveCloudApi(self): collection=test_collection, folder='fooFolder', fileNamePrefix='fooDriveFileNamePrefix', - maxVertices=0) + maxVertices=0, + ) self.assertEqual('EXPORT_FEATURES', task_new_keys.task_type) self.assertEqual('UNSUBMITTED', task_new_keys.state) self.assertEqual(expected_config, task_new_keys.config) @@ -974,7 +1060,8 @@ def testExportTableToAssetCloudApi(self): task = ee.batch.Export.table.toAsset( collection=ee.FeatureCollection('foo'), description='foo', - assetId='users/foo/bar') + assetId='users/foo/bar', + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_FEATURES', task.task_type) @@ -985,20 +1072,14 @@ def testExportTableToAssetCloudApi(self): 'description': 'foo', 'assetExportOptions': { 'earthEngineDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), } - } + }, }, - task.config) - - task = ee.batch.Export.table.toAsset( - collection=ee.FeatureCollection('foo'), - description='foo', - assetId='users/foo/bar', - overwrite=True) - self.assertTrue(task.config['assetExportOptions'] - ['earthEngineDestination']['overwrite']) + task.config, + ) task_with_priority = ee.batch.Export.table.toAsset( collection=ee.FeatureCollection('foo'), description='foo', @@ -1014,7 +1095,6 @@ def testExportTableToAssetCloudApi(self): 'name': ( 'projects/earthengine-legacy/assets/users/foo/bar' ), - 'overwrite': False, } }, 'priority': {'value': 999}, @@ -1028,7 +1108,8 @@ def testExportTableWithFileFormatCloudApi(self): task = ee.batch.Export.table.toCloudStorage( collection=ee.FeatureCollection('foo'), outputBucket='test-bucket', - fileFormat='tfRecord') + fileFormat='tfRecord', + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_FEATURES', task.task_type) @@ -1042,9 +1123,11 @@ def testExportTableWithFileFormatCloudApi(self): 'cloudStorageDestination': { 'bucket': 'test-bucket', 'filenamePrefix': 'myExportTableTask', - } - } - }, task.config) + }, + }, + }, + task.config, + ) def testExportTableToFeatureViewCloudApi(self): """Verifies the export task created by Export.table.toFeatureView().""" @@ -1055,8 +1138,9 @@ def testExportTableToFeatureViewCloudApi(self): assetId='users/foo/bar', ingestionTimeParameters={ 'maxFeaturesPerTile': 10, - 'zOrderRanking': [] - }) + 'zOrderRanking': [], + }, + ) self.assertIsNone(task.id) self.assertIsNone(task.name) self.assertEqual('EXPORT_FEATURES', task.task_type) @@ -1067,16 +1151,17 @@ def testExportTableToFeatureViewCloudApi(self): 'description': 'foo', 'featureViewExportOptions': { 'featureViewDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), }, 'ingestionTimeParameters': { - 'thinningOptions': { - 'maxFeaturesPerTile': 10 - }, - } - } - }, task.config) + 'thinningOptions': {'maxFeaturesPerTile': 10}, + }, + }, + }, + task.config, + ) task_with_priority = ee.batch.Export.table.toFeatureView( collection=ee.FeatureCollection('foo'), @@ -1113,7 +1198,8 @@ def testExportTableToFeatureViewEmptyParamsCloudApi(self): task = ee.batch.Export.table.toFeatureView( collection=ee.FeatureCollection('foo'), description='foo', - assetId='users/foo/bar') + assetId='users/foo/bar', + ) with self.subTest(name='TaskIdAndName'): self.assertIsNone(task.id) self.assertIsNone(task.name) @@ -1128,12 +1214,15 @@ def testExportTableToFeatureViewEmptyParamsCloudApi(self): 'description': 'foo', 'featureViewExportOptions': { 'featureViewDestination': { - 'name': - 'projects/earthengine-legacy/assets/users/foo/bar', + 'name': ( + 'projects/earthengine-legacy/assets/users/foo/bar' + ), }, - 'ingestionTimeParameters': {} - } - }, task.config) + 'ingestionTimeParameters': {}, + }, + }, + task.config, + ) def testExportTableToFeatureViewAllIngestionParams(self): """Verifies the task ingestion params created by toFeatureView().""" @@ -1145,59 +1234,63 @@ def testExportTableToFeatureViewAllIngestionParams(self): 'maxFeaturesPerTile': 10, 'thinningStrategy': 'GLOBALLY_CONSISTENT', 'thinningRanking': 'my-attribute ASC, other-attr DESC', - 'zOrderRanking': ['.minZoomLevel DESC', '.geometryType ASC'] - }) + 'zOrderRanking': ['.minZoomLevel DESC', '.geometryType ASC'], + }, + ) expected_ingestion_params = { 'rankingOptions': { 'thinningRankingRule': { - 'rankByOneThingRule': [{ - 'direction': 'ASCENDING', - 'rankByAttributeRule': { - 'attributeName': 'my-attribute' - } - }, { - 'direction': 'DESCENDING', - 'rankByAttributeRule': { - 'attributeName': 'other-attr' - } - }] + 'rankByOneThingRule': [ + { + 'direction': 'ASCENDING', + 'rankByAttributeRule': { + 'attributeName': 'my-attribute' + }, + }, + { + 'direction': 'DESCENDING', + 'rankByAttributeRule': {'attributeName': 'other-attr'}, + }, + ] }, 'zOrderRankingRule': { - 'rankByOneThingRule': [{ - 'direction': 'DESCENDING', - 'rankByMinZoomLevelRule': {} - }, { - 'direction': 'ASCENDING', - 'rankByGeometryTypeRule': {} - }] - } + 'rankByOneThingRule': [ + {'direction': 'DESCENDING', 'rankByMinZoomLevelRule': {}}, + {'direction': 'ASCENDING', 'rankByGeometryTypeRule': {}}, + ] + }, }, 'thinningOptions': { 'maxFeaturesPerTile': 10, - 'thinningStrategy': 'GLOBALLY_CONSISTENT' - } + 'thinningStrategy': 'GLOBALLY_CONSISTENT', + }, } self.assertEqual( expected_ingestion_params, - task.config['featureViewExportOptions']['ingestionTimeParameters']) + task.config['featureViewExportOptions']['ingestionTimeParameters'], + ) def testExportTableToFeatureViewBadRankByOneThingRule(self): """Verifies a bad RankByOneThingRule throws an exception.""" - with self.assertRaisesRegex(ee.EEException, - 'Ranking rule format is invalid.*'): + with self.assertRaisesRegex( + ee.EEException, 'Ranking rule format is invalid.*' + ): ee.batch.Export.table.toFeatureView( collection=ee.FeatureCollection('foo'), assetId='users/foo/bar', - ingestionTimeParameters={'thinningRanking': 'my-attribute BAD_DIR'}) + ingestionTimeParameters={'thinningRanking': 'my-attribute BAD_DIR'}, + ) def testExportTableToFeatureViewBadRankingRule(self): """Verifies a bad RankingRule throws an exception.""" - with self.assertRaisesRegex(ee.EEException, - 'Unable to build ranking rule from rules.*'): + with self.assertRaisesRegex( + ee.EEException, 'Unable to build ranking rule from rules.*' + ): ee.batch.Export.table.toFeatureView( collection=ee.FeatureCollection('foo'), assetId='users/foo/bar', - ingestionTimeParameters={'thinningRanking': {'key': 'val'}}) + ingestionTimeParameters={'thinningRanking': {'key': 'val'}}, + ) def testExportTableToFeatureViewBadIngestionTimeParams(self): """Verifies a bad set of ingestion time params throws an exception.""" @@ -1211,7 +1304,8 @@ def testExportTableToFeatureViewBadIngestionTimeParams(self): ee.batch.Export.table.toFeatureView( collection=ee.FeatureCollection('foo'), assetId='users/foo/bar', - ingestionTimeParameters={'badThinningKey': {'key': 'val'}}) + ingestionTimeParameters={'badThinningKey': {'key': 'val'}}, + ) def testExportTableToBigQueryRequiredParams(self): """Verifies the export task created by Export.table.toBigQuery().""" @@ -1313,7 +1407,8 @@ def testExportVideoCloudApi(self): framesPerSecond=30, maxFrames=10000, maxPixels=10000000, - maxWorkers=100) + maxWorkers=100, + ) collection = ee.ImageCollection([ee.Image(1), ee.Image(2)]) task = ee.batch.Export.video(collection, 'TestVideoName', config) self.assertIsNone(task.id) @@ -1324,7 +1419,8 @@ def testExportVideoCloudApi(self): def expected_preparation_function(img): img = img.setDefaultProjection( - crs='SR-ORG:6627', crsTransform=[1, 0, 0, 0, -1, 0]) + crs='SR-ORG:6627', crsTransform=[1, 0, 0, 0, -1, 0] + ) img = img.clipToBoundsAndScale(geometry=region, maxDimension=16) return img @@ -1333,24 +1429,26 @@ def expected_preparation_function(img): # serialised forms instead. self.assertEqual( expected_collection.serialize(for_cloud_api=True), - task.config.pop('expression').serialize(for_cloud_api=True)) - self.assertEqual({ - 'description': 'TestVideoName', - 'videoOptions': { - 'framesPerSecond': 30, - 'maxFrames': 10000, - 'maxPixelsPerFrame': { - 'value': '10000000' - } - }, - 'fileExportOptions': { - 'fileFormat': 'MP4', - 'driveDestination': { - 'filenamePrefix': 'TestVideoName', - } + task.config.pop('expression').serialize(for_cloud_api=True), + ) + self.assertEqual( + { + 'description': 'TestVideoName', + 'videoOptions': { + 'framesPerSecond': 30, + 'maxFrames': 10000, + 'maxPixelsPerFrame': {'value': '10000000'}, + }, + 'fileExportOptions': { + 'fileFormat': 'MP4', + 'driveDestination': { + 'filenamePrefix': 'TestVideoName', + }, + }, + 'maxWorkers': {'value': 100}, }, - 'maxWorkers': {'value': 100} - }, task.config) + task.config, + ) config['outputBucket'] = 'test-bucket' gcs_task = ee.batch.Export.video(collection, 'TestVideoName', config) @@ -1358,41 +1456,47 @@ def expected_preparation_function(img): self.assertEqual('UNSUBMITTED', gcs_task.state) self.assertEqual( expected_collection.serialize(for_cloud_api=True), - gcs_task.config.pop('expression').serialize(for_cloud_api=True)) - self.assertEqual({ - 'description': 'TestVideoName', - 'videoOptions': { - 'framesPerSecond': 30, - 'maxFrames': 10000, - 'maxPixelsPerFrame': { - 'value': '10000000' - } - }, - 'fileExportOptions': { - 'fileFormat': 'MP4', - 'cloudStorageDestination': { - 'bucket': 'test-bucket', - 'filenamePrefix': 'TestVideoName', - } + gcs_task.config.pop('expression').serialize(for_cloud_api=True), + ) + self.assertEqual( + { + 'description': 'TestVideoName', + 'videoOptions': { + 'framesPerSecond': 30, + 'maxFrames': 10000, + 'maxPixelsPerFrame': {'value': '10000000'}, + }, + 'fileExportOptions': { + 'fileFormat': 'MP4', + 'cloudStorageDestination': { + 'bucket': 'test-bucket', + 'filenamePrefix': 'TestVideoName', + }, + }, + 'maxWorkers': {'value': 100}, }, - 'maxWorkers': {'value': 100} - }, gcs_task.config) + gcs_task.config, + ) - with self.assertRaisesRegex(ee.EEException, - 'Unknown configuration options.*'): + with self.assertRaisesRegex( + ee.EEException, 'Unknown configuration options.*' + ): config_with_bogus_option = config.copy() config_with_bogus_option['flamesPerSleestak'] = 30 - ee.batch.Export.video(collection, 'TestVideoName', - config_with_bogus_option) + ee.batch.Export.video( + collection, 'TestVideoName', config_with_bogus_option + ) def testExportVideoToCloudStorageCloudApi(self): """Verifies the task created by Export.video.toCloudStorage().""" with apitestcase.UsingCloudApi(): region = ee.Geometry.Rectangle(1, 2, 3, 4) collection = ee.ImageCollection([ee.Image(1), ee.Image(2)]) + def expected_preparation_function(img): img = img.reproject( - crs='foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + crs='foo', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0] + ) img = img.clipToBoundsAndScale(geometry=region, maxDimension=16) return img @@ -1404,8 +1508,8 @@ def expected_preparation_function(img): 'cloudStorageDestination': { 'bucket': 'test-bucket', 'filenamePrefix': 'TestVideoName', - } - } + }, + }, } # Test keyed parameters. @@ -1416,25 +1520,37 @@ def expected_preparation_function(img): dimensions=16, region=region['coordinates'], crsTransform='[9,8,7,6,5,4]', - crs='foo') + crs='foo', + ) self.assertIsNone(task_keyed.id) self.assertIsNone(task_keyed.name) self.assertEqual('EXPORT_VIDEO', task_keyed.task_type) self.assertEqual('UNSUBMITTED', task_keyed.state) self.assertEqual( expected_collection.serialize(for_cloud_api=True), - task_keyed.config.pop('expression').serialize(for_cloud_api=True)) + task_keyed.config.pop('expression').serialize(for_cloud_api=True), + ) self.assertEqual(expected_config, task_keyed.config) # Test ordered parameters. task_ordered = ee.batch.Export.video.toCloudStorage( - collection, 'TestVideoName', 'test-bucket', None, None, 16, - region['coordinates'], None, 'foo', '[9,8,7,6,5,4]') + collection, + 'TestVideoName', + 'test-bucket', + None, + None, + 16, + region['coordinates'], + None, + 'foo', + '[9,8,7,6,5,4]', + ) self.assertEqual('EXPORT_VIDEO', task_ordered.task_type) self.assertEqual('UNSUBMITTED', task_ordered.state) self.assertEqual( expected_collection.serialize(for_cloud_api=True), - task_ordered.config.pop('expression').serialize(for_cloud_api=True)) + task_ordered.config.pop('expression').serialize(for_cloud_api=True), + ) self.assertEqual(expected_config, task_ordered.config) expected_config_with_priority = { @@ -1471,9 +1587,11 @@ def testExportVideoToDriveCloudApi(self): with apitestcase.UsingCloudApi(): region = ee.Geometry.Rectangle(1, 2, 3, 4) collection = ee.ImageCollection([ee.Image(1), ee.Image(2)]) + def expected_preparation_function(img): img = img.reproject( - crs='SR-ORG:6627', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0]) + crs='SR-ORG:6627', crsTransform=[9.0, 8.0, 7.0, 6.0, 5.0, 4.0] + ) img = img.clipToBoundsAndScale(geometry=region, maxDimension=16) return img @@ -1485,8 +1603,8 @@ def expected_preparation_function(img): 'driveDestination': { 'folder': 'test-folder', 'filenamePrefix': 'TestVideoName', - } - } + }, + }, } # Test keyed parameters. @@ -1496,25 +1614,37 @@ def expected_preparation_function(img): folder='test-folder', dimensions=16, crsTransform='[9,8,7,6,5,4]', - region=region['coordinates']) + region=region['coordinates'], + ) self.assertIsNone(task_keyed.id) self.assertIsNone(task_keyed.name) self.assertEqual('EXPORT_VIDEO', task_keyed.task_type) self.assertEqual('UNSUBMITTED', task_keyed.state) self.assertEqual( expected_collection.serialize(for_cloud_api=True), - task_keyed.config.pop('expression').serialize(for_cloud_api=True)) + task_keyed.config.pop('expression').serialize(for_cloud_api=True), + ) self.assertEqual(expected_config, task_keyed.config) # Test ordered parameters. task_ordered = ee.batch.Export.video.toDrive( - collection, 'TestVideoName', 'test-folder', None, None, 16, - region['coordinates'], None, 'SR-ORG:6627', '[9,8,7,6,5,4]') + collection, + 'TestVideoName', + 'test-folder', + None, + None, + 16, + region['coordinates'], + None, + 'SR-ORG:6627', + '[9,8,7,6,5,4]', + ) self.assertEqual('EXPORT_VIDEO', task_ordered.task_type) self.assertEqual('UNSUBMITTED', task_ordered.state) self.assertEqual( expected_collection.serialize(for_cloud_api=True), - task_ordered.config.pop('expression').serialize(for_cloud_api=True)) + task_ordered.config.pop('expression').serialize(for_cloud_api=True), + ) self.assertEqual(expected_config, task_ordered.config) expected_config_with_priority = { diff --git a/python/ee/tests/data_test.py b/python/ee/tests/data_test.py index 83afd4ee4..ec8de2744 100644 --- a/python/ee/tests/data_test.py +++ b/python/ee/tests/data_test.py @@ -157,11 +157,54 @@ def testListAssets(self): cloud_api_resource.projects().assets().listAssets( ).execute.return_value = mock_result cloud_api_resource.projects().assets().listAssets_next.return_value = None - actual_result = ee.data.listAssets({'p': 'q'}) + actual_result = ee.data.listAssets('path/to/folder') cloud_api_resource.projects().assets().listAssets( ).execute.assert_called_once() self.assertEqual(mock_result, actual_result) + def testListAssetsWithPageSize(self): + mock_http = mock.MagicMock(httplib2.Http) + ok_resp = httplib2.Response({'status': 200}) + page = ( + b'{"assets": [{"path": "id1", "type": "type1"}], "nextPageToken": "t1"}' + ) + mock_http.request.side_effect = [(ok_resp, page)] + with apitestcase.UsingCloudApi(mock_http=mock_http): + actual_result = ee.data.listAssets( + {'parent': 'path/to/folder', 'pageSize': 3} + ) + expected_result = { + 'assets': [{'path': 'id1', 'type': 'type1'}], + 'nextPageToken': 't1', + } + self.assertEqual(expected_result, actual_result) + + def testListAssetsMultiplePages(self): + mock_http = mock.MagicMock(httplib2.Http) + ok_resp = httplib2.Response({'status': 200}) + page1 = ( + b'{"assets": [{"path": "id1", "type": "type1"}], "nextPageToken": "t1"}' + ) + page2 = ( + b'{"assets": [{"path": "id2", "type": "type2"}], "nextPageToken": "t2"}' + ) + page3 = b'{"assets": [{"path": "id3", "type": "type3"}]}' + mock_http.request.side_effect = [ + (ok_resp, page1), + (ok_resp, page2), + (ok_resp, page3), + ] + with apitestcase.UsingCloudApi(mock_http=mock_http): + actual_result = ee.data.listAssets('path/to/folder') + expected_result = { + 'assets': [ + {'path': 'id1', 'type': 'type1'}, + {'path': 'id2', 'type': 'type2'}, + {'path': 'id3', 'type': 'type3'}, + ] + } + self.assertEqual(expected_result, actual_result) + def testListImages(self): cloud_api_resource = mock.MagicMock() with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource): @@ -169,7 +212,7 @@ def testListImages(self): cloud_api_resource.projects().assets().listAssets( ).execute.return_value = mock_result cloud_api_resource.projects().assets().listAssets_next.return_value = None - actual_result = ee.data.listImages({'p': 'q'}) + actual_result = ee.data.listImages('path/to/folder') cloud_api_resource.projects().assets().listAssets( ).execute.assert_called_once() self.assertEqual({'images': [{ @@ -177,6 +220,49 @@ def testListImages(self): 'type': 'type1' }]}, actual_result) + def testListImagesWithPageSize(self): + mock_http = mock.MagicMock(httplib2.Http) + ok_resp = httplib2.Response({'status': 200}) + page = ( + b'{"assets": [{"path": "id1", "type": "type1"}], "nextPageToken": "t1"}' + ) + mock_http.request.side_effect = [(ok_resp, page)] + with apitestcase.UsingCloudApi(mock_http=mock_http): + actual_result = ee.data.listImages( + {'parent': 'path/to/folder', 'pageSize': 3} + ) + expected_result = { + 'images': [{'path': 'id1', 'type': 'type1'}], + 'nextPageToken': 't1', + } + self.assertEqual(expected_result, actual_result) + + def testListImagesMultiplePages(self): + mock_http = mock.MagicMock(httplib2.Http) + ok_resp = httplib2.Response({'status': 200}) + page1 = ( + b'{"assets": [{"path": "id1", "type": "type1"}], "nextPageToken": "t1"}' + ) + page2 = ( + b'{"assets": [{"path": "id2", "type": "type2"}], "nextPageToken": "t2"}' + ) + page3 = b'{"assets": [{"path": "id3", "type": "type3"}]}' + mock_http.request.side_effect = [ + (ok_resp, page1), + (ok_resp, page2), + (ok_resp, page3), + ] + with apitestcase.UsingCloudApi(mock_http=mock_http): + actual_result = ee.data.listImages('path/to/folder') + expected_result = { + 'images': [ + {'path': 'id1', 'type': 'type1'}, + {'path': 'id2', 'type': 'type2'}, + {'path': 'id3', 'type': 'type3'}, + ] + } + self.assertEqual(expected_result, actual_result) + def testListBuckets(self): cloud_api_resource = mock.MagicMock() with apitestcase.UsingCloudApi(cloud_api_resource=cloud_api_resource): diff --git a/python/ee/tests/daterange_test.py b/python/ee/tests/daterange_test.py index 30381d888..1c4568803 100644 --- a/python/ee/tests/daterange_test.py +++ b/python/ee/tests/daterange_test.py @@ -5,6 +5,7 @@ """ import json +from typing import Any, Dict import ee from ee import apitestcase @@ -19,6 +20,24 @@ DATERANGE = 'DateRange' +def make_expression_graph( + function_invocation_value: Dict[str, Any] +) -> Dict[str, Any]: + return { + 'result': '0', + 'values': {'0': {'functionInvocationValue': function_invocation_value}}, + } + + +def daterange_function_expr(value: int) -> Dict[str, Any]: + return { + 'functionInvocationValue': { + 'functionName': 'DateRange', + 'arguments': {'start': {'constantValue': value}}, + } + } + + class DateRangeTest(apitestcase.ApiTestCase): def test_init_all(self): @@ -103,7 +122,6 @@ def test_init_ee_dates(self): end = ee.Date('2017-06-24T07:00:00') daterange = ee.DateRange(start, end) result = json.loads(daterange.serialize()) - print(result) expect = { 'result': '0', 'values': { @@ -141,6 +159,118 @@ def test_no_args(self): with self.assertRaisesRegex(TypeError, message): ee.DateRange() # pytype:disable=missing-parameter + def test_contains(self): + expect = make_expression_graph({ + 'functionName': 'DateRange.contains', + 'arguments': { + 'dateRange': daterange_function_expr(1), + 'other': {'constantValue': 2}, + }, + }) + expression = ee.DateRange(1).contains(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.DateRange(1).contains(other=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_end(self): + expect = make_expression_graph({ + 'functionName': 'DateRange.end', + 'arguments': {'dateRange': daterange_function_expr(1)}, + }) + expression = ee.DateRange(1).end() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_intersection(self): + expect = make_expression_graph({ + 'functionName': 'DateRange.intersection', + 'arguments': { + 'dateRange': daterange_function_expr(1), + 'other': daterange_function_expr(2), + }, + }) + expression = ee.DateRange(1).intersection(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.DateRange(1).intersection(other=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_intersects(self): + expect = make_expression_graph({ + 'arguments': { + 'dateRange': daterange_function_expr(1), + 'other': daterange_function_expr(2), + }, + 'functionName': 'DateRange.intersects', + }) + expression = ee.DateRange(1).intersects(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.DateRange(1).intersects(other=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_isEmpty(self): + expect = make_expression_graph({ + 'arguments': { + 'dateRange': daterange_function_expr(1), + }, + 'functionName': 'DateRange.isEmpty', + }) + expression = ee.DateRange(1).isEmpty() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_isUnbounded(self): + expect = make_expression_graph({ + 'arguments': { + 'dateRange': daterange_function_expr(1), + }, + 'functionName': 'DateRange.isUnbounded', + }) + expression = ee.DateRange(1).isUnbounded() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_start(self): + expect = make_expression_graph({ + 'functionName': 'DateRange.start', + 'arguments': { + 'dateRange': { + 'functionInvocationValue': { + 'functionName': 'DateRange', + 'arguments': {'start': {'constantValue': 1}}, + } + }, + }, + }) + expression = ee.DateRange(1).start() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_union(self): + expect = make_expression_graph({ + 'arguments': { + 'dateRange': daterange_function_expr(1), + 'other': daterange_function_expr(2), + }, + 'functionName': 'DateRange.union', + }) + + expression = ee.DateRange(1).union(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.DateRange(1).union(other=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + if __name__ == '__main__': unittest.main() diff --git a/python/ee/tests/deprecation_test.py b/python/ee/tests/deprecation_test.py index ad175c3ba..2c77249dc 100644 --- a/python/ee/tests/deprecation_test.py +++ b/python/ee/tests/deprecation_test.py @@ -1,2 +1,177 @@ #!/usr/bin/env python3 """Tests for the ee.deprecation module.""" + +import contextlib +from typing import Any, Dict +import warnings + +from absl.testing import parameterized + +from ee import apitestcase +from ee import deprecation +from ee import ee_string +from ee import image +from ee import imagecollection +import unittest + + +_STAC_JSON = { + 'stac_version': '1.0.0', + 'type': 'Catalog', + 'id': 'GEE_catalog', + 'title': 'Google Earth Engine Catalog (Flat Version)', + 'description': 'A description.', + 'links': [ + { + 'href': 'https://example.test/catalog/catalog.json', + 'rel': 'root', + 'type': 'application/json', + }, + { + 'href': 'https://example.test/catalog/catalog.json', + 'rel': 'self', + 'type': 'application/json', + }, + { + 'href': 'https://example.test/date_and_learn_more.json', + 'title': 'date_and_learn_more', + 'deprecated': True, + 'gee:replacement_id': 'replacement_id', + 'gee:removal_date': '2024-07-01T00:00:00Z', + 'gee:learn_more_url': 'learn_more_url', + }, + { + 'href': 'https://example.test/date_only.json', + 'title': 'date_only', + 'deprecated': True, + 'gee:replacement_id': 'replacement_id', + 'gee:removal_date': '2024-07-01T00:00:00Z', + }, + { + 'href': 'https://example.test/learn_more_url_only.json', + 'title': 'learn_more_url_only', + 'deprecated': True, + 'gee:replacement_id': 'replacement_id', + 'gee:learn_more_url': 'learn_more_url', + }, + { + 'href': 'https://example.test/deprecated_asset.json', + 'title': 'deprecated_asset', + 'deprecated': True, + 'gee:replacement_id': 'replacement_id', + }, + { + 'href': 'https://example.test/non_deprecated_asset.json', + 'title': 'non_deprecated_asset', + }, + ], +} + + +_EXPECTED_WARNINGS = { + 'deprecated_asset': ( + r'Attention required for deprecated_asset! You are using a deprecated' + r' asset.\nTo ensure continued functionality, please update it.' + ), + 'date_and_learn_more': ( + r'Attention required for date_and_learn_more! You are using a' + r' deprecated asset.\nTo ensure continued functionality, please update' + r' it by July 1, 2024.\nLearn more: learn_more_url' + ), + 'date_only': ( + r'Attention required for date_only! You are using a deprecated asset.\n' + r'To ensure continued functionality, please update it by July 1, 2024.' + ), + 'learn_more_url_only': ( + r'Attention required for learn_more_url_only! You are using a' + r' deprecated asset.\nTo ensure continued functionality, please update' + r' it.\nLearn more: learn_more_url' + ), +} + + +class FakeClass: + + @deprecation.WarnForDeprecatedAsset('arg1') + def __init__(self, arg1=None, arg2=None): + pass + + @deprecation.WarnForDeprecatedAsset('arg2') + def some_function(self, arg1=None, arg2=None): + pass + + +class DeprecationTest(apitestcase.ApiTestCase, parameterized.TestCase): + + @contextlib.contextmanager + def assertDoesNotWarn(self): + """Asserts that no warnings are thrown.""" + with warnings.catch_warnings(): + warnings.simplefilter('error') + yield + + # Overridden from apitestcase.ApiTestCase. + def _MockFetchDataCatalogStac(self) -> Dict[str, Any]: + return _STAC_JSON + + def test_no_warnings_thrown(self): + with self.assertDoesNotWarn(): + FakeClass('valid-asset') + + def test_no_warnings_thrown_second_arg(self): + with self.assertDoesNotWarn(): + FakeClass().some_function('some-value', 'valid-asset') + + @parameterized.named_parameters( + ('deprecated_asset', 'deprecated_asset'), + ('date_and_learn_more', 'date_and_learn_more'), + ('date_only', 'date_only'), + ('learn_more_url_only', 'learn_more_url_only'), + ) + def test_warning_thrown_args_init(self, asset_id: str): + with self.assertWarnsRegex( + DeprecationWarning, _EXPECTED_WARNINGS[asset_id] + ): + FakeClass(asset_id, 'some-value') + + def test_warning_thrown_args_instance_method(self): + asset = 'deprecated_asset' + with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]): + FakeClass().some_function('some-value', asset) + + def test_warning_thrown_kwargs_init(self): + asset = 'deprecated_asset' + with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]): + FakeClass(arg1=asset) + + def test_warning_thrown_kwargs_instance_method(self): + asset = 'deprecated_asset' + with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]): + FakeClass().some_function(arg2=asset) + + def test_same_warning_not_thrown(self): + # Verifies the same warning message is not thrown twice. + asset = 'deprecated_asset' + with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]): + FakeClass(arg1=asset) + with self.assertDoesNotWarn(): + FakeClass(arg1=asset) + + # Verifies that a different warning message is thrown. + asset = 'date_only' + with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]): + FakeClass(arg1=asset) + + def test_ee_object_warning_not_thrown(self): + with self.assertDoesNotWarn(): + FakeClass(arg1=ee_string.String('non_deprecated_asset')) + with self.assertDoesNotWarn(): + FakeClass( + imagecollection.ImageCollection([image.Image(0), image.Image(1)]) + ) + with self.assertDoesNotWarn(): + FakeClass(None) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/ee/tests/ee_list_test.py b/python/ee/tests/ee_list_test.py index 53be6f1e2..12ce0b2ab 100644 --- a/python/ee/tests/ee_list_test.py +++ b/python/ee/tests/ee_list_test.py @@ -1,11 +1,22 @@ #!/usr/bin/env python3 """Test for the ee.list module.""" +import json +from typing import Any, Dict import ee from ee import apitestcase import unittest +def make_expression_graph( + function_invocation_value: Dict[str, Any] +) -> Dict[str, Any]: + return { + 'result': '0', + 'values': {'0': {'functionInvocationValue': function_invocation_value}}, + } + + class ListTest(apitestcase.ApiTestCase): def testList(self): @@ -16,10 +27,15 @@ def testList(self): computed = ee.List([1, 2, 3]).slice(0) # pylint: disable=no-member self.assertIsInstance(computed, ee.List) self.assertEqual(ee.ApiFunction.lookup('List.slice'), computed.func) - self.assertEqual({ - 'list': ee.List([1, 2, 3]), - 'start': ee.Number(0) - }, computed.args) + self.assertEqual( + { + 'list': ee.List([1, 2, 3]), + 'start': ee.Number(0), + 'end': None, + 'step': None, + }, + computed.args, + ) def testMapping(self): lst = ee.List(['foo', 'bar']) @@ -57,6 +73,588 @@ def testInternals(self): self.assertNotEqual(a.__hash__(), b.__hash__()) self.assertEqual(a.__hash__(), c.__hash__()) + def test_add(self): + expect = make_expression_graph({ + 'arguments': { + 'element': {'constantValue': 'b'}, + 'list': {'constantValue': ['a']}, + }, + 'functionName': 'List.add', + }) + expression = ee.List(['a']).add('b') + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List(['a']).add(element='b') + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_cat(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'other': {'constantValue': [2]}, + }, + 'functionName': 'List.cat', + }) + expression = ee.List([1]).cat([2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).cat(other=[2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_contains(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'element': {'constantValue': 2}, + }, + 'functionName': 'List.contains', + }) + expression = ee.List([1]).contains(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).contains(element=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_containsAll(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1, 2]}, + 'other': {'constantValue': [3, 4]}, + }, + 'functionName': 'List.containsAll', + }) + expression = ee.List([1, 2]).containsAll([3, 4]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1, 2]).containsAll(other=[3, 4]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_distinct(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.distinct', + }) + expression = ee.List([1]).distinct() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).distinct() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_equals(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'other': {'constantValue': [2]}, + }, + 'functionName': 'List.equals', + }) + expression = ee.List([1]).equals([2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).equals(other=[2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_filter(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'filter': { + 'functionInvocationValue': { + 'functionName': 'Filter.greaterThan', + 'arguments': { + 'leftField': {'constantValue': 'item'}, + 'rightValue': {'constantValue': 3}, + }, + } + }, + }, + 'functionName': 'List.filter', + }) + a_filter = ee.Filter.gt('item', 3) + expression = ee.List([1]).filter(a_filter) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).filter(filter=a_filter) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_flatten(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.flatten', + }) + expression = ee.List([1]).flatten() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_frequency(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'element': {'constantValue': 2}, + }, + 'functionName': 'List.frequency', + }) + expression = ee.List([1]).frequency(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).frequency(element=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_get(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + }, + 'functionName': 'List.get', + }) + expression = ee.List([1]).get(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).get(index=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_getArray(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + }, + 'functionName': 'List.getArray', + }) + expression = ee.List([1]).getArray(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).getArray(index=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_getGeometry(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + }, + 'functionName': 'List.getGeometry', + }) + expression = ee.List([1]).getGeometry(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).getGeometry(index=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_getNumber(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + }, + 'functionName': 'List.getNumber', + }) + expression = ee.List([1]).getNumber(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).getNumber(index=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_get_string(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + }, + 'functionName': 'List.getString', + }) + expression = ee.List([1]).getString(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).getString(index=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_index_of(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'element': {'constantValue': 2}, + }, + 'functionName': 'List.indexOf', + }) + expression = ee.List([1]).indexOf(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).indexOf(element=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_index_of_sublist(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'target': {'constantValue': [2, 3]}, + }, + 'functionName': 'List.indexOfSublist', + }) + expression = ee.List([1]).indexOfSublist([2, 3]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).indexOfSublist(target=[2, 3]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_insert(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + 'element': {'constantValue': 3}, + }, + 'functionName': 'List.insert', + }) + expression = ee.List([1]).insert(2, 3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).insert(index=2, element=3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + # TODO(user): - test_iterate + + def test_join(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'separator': {'constantValue': 'a'}, + }, + 'functionName': 'List.join', + }) + expression = ee.List([1]).join('a') + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).join(separator='a') + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_last_index_of_sublist(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'target': {'constantValue': [2, 3]}, + }, + 'functionName': 'List.lastIndexOfSubList', + }) + expression = ee.List([1]).lastIndexOfSubList([2, 3]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).lastIndexOfSubList(target=[2, 3]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_length(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.length', + }) + expression = ee.List([1]).length() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + # TODO(user): - test_map + + def test_reduce(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'reducer': { + 'functionInvocationValue': { + 'functionName': 'Reducer.count', + 'arguments': {}, + } + }, + }, + 'functionName': 'List.reduce', + }) + reducer = ee.Reducer.count() + expression = ee.List([1]).reduce(reducer) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).reduce(reducer=reducer) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_remove(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'element': {'constantValue': 2}, + }, + 'functionName': 'List.remove', + }) + expression = ee.List([1]).remove(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).remove(element=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_remove_all(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'other': {'constantValue': [1, 2]}, + }, + 'functionName': 'List.removeAll', + }) + expression = ee.List([1]).removeAll([1, 2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).removeAll(other=[1, 2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + # TODO(user): test_repeat constructor. + + def test_replace(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'oldval': {'constantValue': 2}, + 'newval': {'constantValue': 3}, + }, + 'functionName': 'List.replace', + }) + expression = ee.List([1]).replace(2, 3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).replace(oldval=2, newval=3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_replaceAll(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'oldval': {'constantValue': 2}, + 'newval': {'constantValue': 3}, + }, + 'functionName': 'List.replaceAll', + }) + expression = ee.List([1]).replaceAll(2, 3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).replaceAll(oldval=2, newval=3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_reverse(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.reverse', + }) + expression = ee.List([1]).reverse() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_rotate(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'distance': {'constantValue': 2}, + }, + 'functionName': 'List.rotate', + }) + expression = ee.List([1]).rotate(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).rotate(distance=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + # TODO(user): - test_sequence + + def test_set(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'index': {'constantValue': 2}, + 'element': {'constantValue': 3}, + }, + 'functionName': 'List.set', + }) + expression = ee.List([1]).set(2, 3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).set(index=2, element=3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_shuffle(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'seed': {'constantValue': 2}, + }, + 'functionName': 'List.shuffle', + }) + expression = ee.List([1]).shuffle(2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).shuffle(seed=2) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_size(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.size', + }) + expression = ee.List([1]).size() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_slice(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'start': {'constantValue': 2}, + 'end': {'constantValue': 3}, + 'step': {'constantValue': 4}, + }, + 'functionName': 'List.slice', + }) + expression = ee.List([1]).slice(2, 3, 4) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).slice(start=2, end=3, step=4) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_sort(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'keys': {'constantValue': [2]}, + }, + 'functionName': 'List.sort', + }) + expression = ee.List([1]).sort([2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).sort(keys=[2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_splice(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'start': {'constantValue': 2}, + 'count': {'constantValue': 3}, + 'other': {'constantValue': [4]}, + }, + 'functionName': 'List.splice', + }) + expression = ee.List([1]).splice(2, 3, [4]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).splice(start=2, count=3, other=[4]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_swap(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'pos1': {'constantValue': 2}, + 'pos2': {'constantValue': 3}, + }, + 'functionName': 'List.swap', + }) + expression = ee.List([1]).swap(2, 3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).swap(pos1=2, pos2=3) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_unzip(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + }, + 'functionName': 'List.unzip', + }) + expression = ee.List([1]).unzip() + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + def test_zip(self): + expect = make_expression_graph({ + 'arguments': { + 'list': {'constantValue': [1]}, + 'other': {'constantValue': [2]}, + }, + 'functionName': 'List.zip', + }) + expression = ee.List([1]).zip([2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + + expression = ee.List([1]).zip(other=[2]) + result = json.loads(expression.serialize()) + self.assertEqual(expect, result) + if __name__ == '__main__': unittest.main() diff --git a/python/pyproject.toml b/python/pyproject.toml index dc5e12593..ecf8be0f0 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "earthengine-api" -version = "0.1.395" +version = "0.1.396" description = "Earth Engine Python API" requires-python = ">=3.7" keywords = [