From a3f2c903a2689ba027334b8181136199c70ba2da Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Fri, 26 Jun 2020 07:22:44 -0500 Subject: [PATCH 01/15] JOSS Dr. Jeffrey W Hollister comments --- NAMESPACE | 2 +- NEWS.md | 2 +- R/AAAMapclass.R | 12 ++++ R/Map.R | 1 + R/ee_Initialize.R | 67 ++++++++++++------ R/ee_install.R | 1 - R/print.R | 2 +- man/Map.Rd | 1 + man/mapview-class.Rd | 17 +++++ man/print.ee.computedobject.ComputedObject.Rd | 6 +- paper/paper.bib | 14 +--- paper/paper.md | 5 +- paper/paper.pdf | Bin 1126166 -> 1127108 bytes tests/testthat/test-map.R | 2 + 14 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 man/mapview-class.Rd diff --git a/NAMESPACE b/NAMESPACE index 2691fa53..0f828e28 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -75,12 +75,12 @@ export(eedate_to_rdate) export(gcs_to_ee_image) export(gcs_to_ee_table) export(local_to_gcs) -export(print.ee.computedobject.ComputedObject) export(raster_as_ee) export(rdate_to_eedate) export(sf_as_ee) export(stars_as_ee) exportClasses(EarthEngineMap) +exportClasses(mapview) importFrom(cli,cat_line) importFrom(cli,rule) importFrom(cli,symbol) diff --git a/NEWS.md b/NEWS.md index e2cd0e6b..6c9ae5a3 100755 --- a/NEWS.md +++ b/NEWS.md @@ -18,7 +18,7 @@ vignette: > - Users can mix mapview and EarthEnginemap objects in the same pipeline (see Examples in `Map$addLayer`). - Add `ee_as_mapview`, a function to convert `EarthEnginemap` objects to `mapview` objects. - add a new logical argument called 'strict' to **ee_manage_delete**. If TRUE, the existence of the asset will be evaluate before to perform the task. -- Fix a bug en ee_Initialize, now users without an Earth Engine Assets home root will see a message error. +- Fix a bug en ee_Initialize, now users without an Earth Engine Assets home root will see a message. - Fix a minor bug when ee_Initialize change of user, now before to change of user the GCS and GD credentials will be deleted. - ee_check completely renovated. - New message display. diff --git a/R/AAAMapclass.R b/R/AAAMapclass.R index ad9f4418..5f54a0f7 100644 --- a/R/AAAMapclass.R +++ b/R/AAAMapclass.R @@ -9,6 +9,18 @@ setClass('EarthEngineMap', map = 'ANY')) NULL +#' Class mapview +#' +#' @slot object the spatial object +#' @slot map the leaflet map object +#' +#' @exportClass mapview +setClass('mapview', + slots = c(object = 'list', + map = 'ANY')) +NULL + + if ( !isGeneric('EarthEngineMap') ) { setGeneric('EarthEngineMap', function(x, ...) standardGeneric('EarthEngineMap')) diff --git a/R/Map.R b/R/Map.R index 69aaacc0..30ede6db 100644 --- a/R/Map.R +++ b/R/Map.R @@ -128,6 +128,7 @@ #' m4 #' #' # Case 5: mapview + EarthEnginemap +#' library(sf) #' nc <- st_read(system.file("shp/arequipa.shp", package="rgee")) #' mapview(nc) + m2 #' m2 + mapview(nc) diff --git a/R/ee_Initialize.R b/R/ee_Initialize.R index 4229feed..b258cce4 100644 --- a/R/ee_Initialize.R +++ b/R/ee_Initialize.R @@ -58,17 +58,21 @@ ee_Initialize <- function(email = NULL, quiet = FALSE) { # Message for new user init_rgee_message <- Sys.getenv("EARTHENGINE_INIT_MESSAGE", unset = NA) - if (is.na(init_rgee_message) && !py_module_available("ee")) { + if (is.na(init_rgee_message)) { text <- paste( crayon::bold("Welcome to the Earth Engine client library for R!"), "----------------------------------------------------------------", - "It seems it is your first time using rgee. Before start coding is ", + "It seems it is your first time using rgee. First off, keep in mind that", + sprintf("Google Earth Engine is %s, check the", + bold("only available to registered users")), + sprintf("official website %s to get more information.", + bold("https://earthengine.google.com/")), + "Before start coding is necessary to set up a Python environment. Run", sprintf( - "necessary to set up a Python environment. Run %s", - crayon::bold("rgee::ee_install()") + "%s to set up automatically, after that, restart the R", + bold("rgee::ee_install()") ), - "to set up automatically, after that, restart the R session to see", - "changes. See more than 250+ examples of rgee at", + "session to see changes. See more than 250+ examples of rgee at", crayon::bold("https://csaybar.github.io/rgee-examples/"), "", sep = "\n" @@ -222,23 +226,26 @@ ee_Initialize <- function(email = NULL, } # Root folder exist? - ee_user <- tryCatch( - expr = ee_remove_project_chr(ee$data$getAssetRoots()[[1]]$id), - error = function(e) stop( - "Earth Engine Assets home root folder does not ", - "exist for the current user. ", - sprintf( - "Run %s to attempt to create it. ", - bold("ee$data$createAssetHome(users/PUT_YOUR_NAME_HERE)") - ), - sprintf( - "Take into consideration that once created %s", - bold("you will not be able to change the folder name again. ") - ), - "If the root folder was created successfully ", - sprintf("execute again %s.", bold("ee_Initialize()")) + ee_user_assetroot <- ee$data$getAssetRoots()[[1]] + # if ee_asset_home (list) length is zero + if (length(ee_user_assetroot) == 0) { + root_text <- paste( + "Earth Engine Assets home root folder does not exist for the current user.", + "Please enter your desired root folder name below. Take into consideration", + sprintf("that once created %s Alternatively,", + bold("you will not be able to change the folder name again. ")), + sprintf("press ESC to interrupt and run: %s", + bold("ee$data$createAssetHome(\"users/PUT_YOUR_NAME_HERE\")")), + sprintf("to attempt to create it. After that execute again %s.", + bold("ee_Initialize()")), + sep = "\n" ) - ) + message(root_text) + ee_createAssetHome() + ee_user_assetroot <- ee$data$getAssetRoots()[[1]] + } + + ee_user <- ee_remove_project_chr(ee_user_assetroot$id) options(rgee.ee_user = ee_user) ee_sessioninfo( @@ -665,3 +672,19 @@ create_table <- function(user, wsc, quiet = FALSE) { ee_get_assethome <- function() { options('rgee.ee_user')[[1]] } + + +#' Wrapper to create a EE Assets home +#' @noRd +ee_createAssetHome <- function() { + x <- readline("Please insert the desired name of your root folder : users/") + tryCatch( + expr = ee$data$createAssetHome(sprintf("users/", x)), + error = function(x) { + message( + strsplit(x$message,"\n")[[1]][1] + ) + ee_createAssetHome() + } + ) +} diff --git a/R/ee_install.R b/R/ee_install.R index 5fdc6634..9db285d9 100755 --- a/R/ee_install.R +++ b/R/ee_install.R @@ -404,4 +404,3 @@ ee_install_upgrade <- function(version = NULL) { rstudioapi::restartSession(), cat("Restart R session to see changes.\n")) } - diff --git a/R/print.R b/R/print.R index 83f77b6c..5f2f42bc 100644 --- a/R/print.R +++ b/R/print.R @@ -3,7 +3,7 @@ #' @param type Character. What to show about the x object?. Three options are #' supported: "json", "simply", "ee_print". By default "simply". #' @param ... ignored -#' @export +#' @method print ee.computedobject.ComputedObject print.ee.computedobject.ComputedObject <- function(x, type = getOption("rgee.print.option"), ...) { if (type == "json") { diff --git a/man/Map.Rd b/man/Map.Rd index e9a7f176..928ca18d 100644 --- a/man/Map.Rd +++ b/man/Map.Rd @@ -139,6 +139,7 @@ m4 <- Map$addLayer( m4 # Case 5: mapview + EarthEnginemap +library(sf) nc <- st_read(system.file("shp/arequipa.shp", package="rgee")) mapview(nc) + m2 m2 + mapview(nc) diff --git a/man/mapview-class.Rd b/man/mapview-class.Rd new file mode 100644 index 00000000..83dd4654 --- /dev/null +++ b/man/mapview-class.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AAAMapclass.R +\docType{class} +\name{mapview-class} +\alias{mapview-class} +\title{Class mapview} +\description{ +Class mapview +} +\section{Slots}{ + +\describe{ +\item{\code{object}}{the spatial object} + +\item{\code{map}}{the leaflet map object} +}} + diff --git a/man/print.ee.computedobject.ComputedObject.Rd b/man/print.ee.computedobject.ComputedObject.Rd index 1286ac7e..dc6307c2 100644 --- a/man/print.ee.computedobject.ComputedObject.Rd +++ b/man/print.ee.computedobject.ComputedObject.Rd @@ -4,11 +4,7 @@ \alias{print.ee.computedobject.ComputedObject} \title{print Earth Engine object} \usage{ -print.ee.computedobject.ComputedObject( - x, - type = getOption("rgee.print.option"), - ... -) +\method{print}{ee.computedobject.ComputedObject}(x, type = getOption("rgee.print.option"), ...) } \arguments{ \item{x}{Earth Engine spatial object.} diff --git a/paper/paper.bib b/paper/paper.bib index 8476d5ea..fb1d59fe 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -5,6 +5,7 @@ @article{gorelick2017google volume={202}, pages={18--27}, year={2017}, + doi={10.1016/j.rse.2017.06.031}, publisher={Elsevier} } @@ -24,21 +25,10 @@ @article{wickham2011ggplot2 number={2}, pages={180--185}, year={2011}, + doi={10.1016/j.rse.2017.06.031}, publisher={Wiley Online Library} } - -@article{abatzoglou2018terraclimate, - title={TerraClimate, a high-resolution global dataset of monthly climate and climatic water balance from 1958--2015}, - author={Abatzoglou, John T and Dobrowski, Solomon Z and Parks, Sean A and Hegewisch, Katherine C}, - journal={Scientific data}, - volume={5}, - pages={170191}, - year={2018}, - publisher={Nature Publishing Group} -} - - @Manual{reticulate, author = {JJ Allaire, Kevin Ushey, Yuan Tang and Dirk Eddelbuettel}, title = {reticulate: R Interface to Python}, diff --git a/paper/paper.md b/paper/paper.md index 57dd299a..2eeb5961 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -136,7 +136,8 @@ Map$addLayer( rgee can extract values from `ee.Image` and `ee.ImageCollection` objects at a certain location based on `ee.Geometry`, `ee.Feature`, `ee.FeatureCollection` and `sf` objects. If the geometry is a polygon, users can summarize the values using built-in Earth Engine reducer functions. The code below explains how to extract the average areal rainfall from North Carolina counties using the [TerraClimate](https://developers.google.com/earth-engine/datasets/catalog/IDAHO_EPSCOR_TERRACLIMATE) dataset. ```r -library(tidyverse) +library(ggplot2) +library(tidyr) library(rgee) library(sf) @@ -198,7 +199,7 @@ ee_print(l8) # Availability -rgee is an open-source software package made available under the Apache 2.0 license. It can be installed through CRAN using: install.packages("rgee"). rgee can also be installed from its GitHub repository using the remotes package: remotes::install_github("r-spatial/rgee"). A series of examples for using rgee are available at [https://r-spatial.github.io/rgee](https://r-spatial.github.io/rgee). +rgee is an open-source software package made available under the Apache 2.0 license. It can be installed through GitHub repository using the remotes package: remotes::install_github("r-spatial/rgee"). A series of examples for using rgee are available at [https://r-spatial.github.io/rgee](https://r-spatial.github.io/rgee). # Acknowledgments diff --git a/paper/paper.pdf b/paper/paper.pdf index 2d74227713eab25fff10d9ea2311de15096337a7..ed0aaf4a75321fe2351048cfa6e224cb84382b52 100644 GIT binary patch delta 68315 zcmZs>gKy_w(6Ifj-EM80Tf19p+iq>!Hb1p(+um|(+t}Lf)^_{+?l*aoH+k-pTsbp; zz~nk-W>UAr1`owhC>6vc7+IJ&;3;PphgRSdzG0;)P{PpyF?bVB`+sh_^$SFiT~dZf zVXsrxO_p;#=(09%|ImfWTESD1!tr5LP)d1s>gUAO&LznI`qUvc>cj&^(8T|l-{FXqnapb-%-^pJAab< z14dWB#PePgutx&p{1Adz-|e@6){SdfCsPocaod=??fA4pIul?BFYiJ_&Fh&HU2$5l zwKcE(e7gRevdydx4zFU6xrr#G*<+nhKhqhq%$y_NmM7c;6Tt5D%92CF+hD!d-eDUPvQ2;@vuj-gm*4FbK_@!ik-D#*=TqQfkU32iy5G9PC z5NdV~&mw_LW$W1Hgq`}dsGBj!yJTzos}+P7uOLLa0Hu%c{It33 zLXeihVj{s?KE6eHonawE-# z`~R~1ViGFU80&r$V(qxL$gNL5m|*D?F_aE~JRF*aK&LNBKYo<%-wUcL`(>!#+NH&< zp0|Xasd%yAVvs;IB()aU(3St5>4qn(p;jx_3zCM#mX-!UaBmQE>`Wzn@l%a&!x_hm z|Hef4nQe77@Vq!&bv*F_kq*o>4#gX~rIxr^R0L0}?d>oW+GLs;C*t!~N)~yrHzEVb zMbmVaBTpsU*;-9SSrRx!|FPhb+GNeYF38iEvDVlD9%0Xm$uS9t-7Ge$W(86h; zGtG{0+AT6OP0+l*L(ctYF{v|c-ffmIW;b8+VeLoNt0BTew`M5h=%Fsc{In<0C@7vT z?!AHvm^Ujt4?<@hE-sd9u18mF0I*?&crh!2$y8zw%On0J^WD5L>-69MCR}uN$ z-oJL$S8E`v-oLXw7!U19__YhWqQQmy=qOT%y9me@vqQ)|dW@*-Zxad|aIop07ujAg zc!1C$zU?daNX0-8^F7`--`mxpzUvW_27NG{OjmNl$X}f5RcasFV^VTQgoG)0-pi{g zG__C@WcEo8E$Yw7lM|9lM34dCdu3kPo$^o~Jq2~8@hDiI99 z-X6rf93+Dz;X&`RjkuQ9#xTm{joJ=vX<{{z+%p=B)r>ftRAzWy^ra7sHHM3gBp4y; zFvp2v+785ipav&$m^jJA(eOXHUCFYN^u0j-Uq>4ryA!xxUNEo8bQ+*x2D;sCAz*M5 z#W-C0gdM>pbCngGqHl&uW+tT^3dIEL7&B_K!y+=~V%|5h=$zJ7X!dRz(eUZq10nj< zqFvWkB>KR?yK*9AnGSJ(I68OC@;ngC!|L?fg?qtB94de1<-^s^i5LTgBoIgaw+)XsE+b})unL@!Ry zaoVo*~)$*kRYG1YC?O@Eo2+iO8WYYOlx`F-7eTiK2FR-;*H@+;(efIC&Yxy#o~Yvx&4zHyOxqvP>yG*K%e>CB%w1E|5f1EI2yj} z4 zvkv?V@KQS5+ZtaU^SB|7_in!%r58tw${yl(&FQlPNf0q7J}Z&aY6|PXsHZE#oIX~9 zi<%zhwGzLn)e|PBD@m>SEnKjh7|~c`5LkSkEiGu~%aM`}R-X*g6~wO)uW8nC`{b;e z_f4lq;%iQ_B;5T)yHgEh$B^h~S)R%|^#}4y#TbrYrFuu8LVwbO7Xw;vnmw_bX2$$as; zs{$)V*N||gH3iL!?xgNZI+#MN!t-MdrpEEaR8Sgl;SUF{!#?wuN$WaxG}B^+*?=^H zs}~mWbIqF*s;Dcl%Uc~Vb)7@63a@@-K6xHyuTySN~Ry0B1%-k|GEMAZ{K47js>)HanM?N*UY>j_KDqyzjznL?ofo~rJ2Rlx`u zxas0bqXfp}1NJP)<}XujXfgZ6MR>srFt8nSE2{l#h?Su^EUd6t-N|HLFi=O&%e9by zCS*clA0Rt_V6V-#<%&;}pp064)~IB|{VkVF#WFtn%<*2S41Cy>N6-WeN)`==&sh>H9`2 zW3%kh(LWXhM%wJOZsH4Az^JW@*VV4U_vAMlTF<7_|_t)n|3j zKnzPgAbV-6oC%gA%@I$@+A0*RJ}4rAUC zh=A%YLFCWtI&Upz2Zm4iY5$^!xqk~QfK_kzR4Y(~rVtZV%{$abSX#G{34i+c`dtga z*IR$&Na@t?QKJ%PxO2AZl1;h#l4nfSZ;1TYr|=NEwzqEB8OFZGr_|%N7l8^M@pp0l za$J2h63BYywWI+7;d-paS2qI}0*gnYcZ5_Td)h{?E&O~IoDtyK+FN;VbFG)ys?ZMy ztN2?PpSzcjkL&VMm=3IIWs-AGmgi%po?|^f`_PXVYQ8x~YQOy`CEqE1+Dr&+Cx5pP z+-U)iUh_jZe-iz*J8$3uf``*eRZsTRQ@F{3rOde!XQ-1QF%2)eo7ZE;vR_{`;;NhM znaoTL$bZ3X%;i7Bo7$N;yEvH|+Wt>uZ)63}%0t3T@;?DTKm2!DQ#*4P3lbI{cJ?&I z|Gqhm5=cKG0dU^CwVs#$bq-f|--BA{Vv}$ZwcJD+iqe}i!&(r(ke>>zMf1jlv3)Jv zB{%s*2MHPCuq7-2*_ee#?jFl4t|40S3u~Fb$_A=*j3Vxq7>O>3v9Kk?5*eb;EofLW zVbYI(_=T4tG6ns&plshRpf{^pGReMiq-FgcB67mfgJ(aTp$08;0D!c@g(``@GuUa0AjFJ1tjx%|A% ztLw+t8B`(}lzOdl;sx zSy^Bb9)=W5x&22%JDpG1fLuR1_y;V)IdCj}4;y(Cxc~+lg(4d8+szIf4RdL{G~^mN zc;M7|=$vT@>?V*=kX!mZJa@_&XrEAr3P+d&6nke{ zB{ja*%huP^_BEXOrpm`1{iTb zee?@%R5uBZ!*b~Bof?|&A~CqXE9}$<%x1Rv2z=j` zT&#NG_iT`*>cgujA-uim@PkSmYf0?$Ld_cU`+U1gy`Ez4(WmFxq!D3(1G1S&X`uDz zVMlJ6>b#2h@gt2ga=|0}{Tb`0tojga*pxkJyYK%bcFS@ipdxpbp2P4F)*=nOtUOd) z(NQyat8UHN_t@GHO&pz4!lVe;GIH&-xDoEb7!con-4o&kE(HH}CE5<1aNr-WsuA&` zZ>@N7P-Kd+B!*&H{Yf*s4Sc^vw05<vNb z`#ga>{UBMAG>~uP5nqagvpDa^ ziNVM}wV{fqXUb#?nU-YK`}QXA^In?xb9NT0rlc@!T(;DwiQP>UW8{CHkN&KGBX^@WXRPqd=p#^ z6mAMAy8=)O=qr)mzbb#gZgWUQ+D9PO@HB5@89#DHoE7ntD%m7zghso(yJ!R()P|JpHT<4&LiU4cR?B4#|R zW}z|ANW-aOigDnFB8Tnlnp85mER$7bRH&GVl^9W>BNAZsOU(+~8E@gr0gnz6(Z$UC z0ABAk90T+jz#~!Tx`K9&80JbbDKfLF`QPsewxGRverS+MBG}``bQx=kZ^&a~u+av% z-^zjqXV%f$e&U!E4t{?PhaC+{txtgYMY*o*>1m6xEcm{%UN1eO=o35s_~fsi-JbE(CkLnG}NpeffghbCPzKy*6zK$<_wvjC~9bH zHUdKvHv+3o0pxfVXiIh*1e{2C=3Hv}UKS~7qQ`Y>dHI+X6_wWJ{%S|OSUKZR@GLu( zK~~v~fbynQ`@i(6Z>05VaR`cXk(2Q^lao1l=}%%-+q*-Ho$KtyU1D=`KNHfwH|P72 zdQeU-1H4QwKk28kt0^GogZT!#0>KlyZ5K--7M7#9GLA!TCPVnLVC*~Q&g}fW(uVvEup)E zI4sCvJa%Ra*&u-C%%dQiny3V6Q_(egtlb-oDAt}oJPa|xJgHUfczo4HV0p%icfAnd zMF>UYw+e8~wOB^e+P)%+C)6ZL+?FZ`H&v&Ha8uV@hVdrjam){^pVpeLT0z3i@y(b} z0x8x}HD?^EFyyMj0l0{%OVPvNu4WKXrpqM;~2rzMSgeb${bD+V%tXn`dy@cvtU+s4t8krD-rghi9Kn6v<-;^T3qVSZ zLEoW&)V1Q<@DF(5*-2?dp>i=y-25Yuefu`V*}L-li!4h_iw%VUHnfNEw@buv5qIX> zl+YLKpMCr9>gLW~|JL$uv=SdTBr>Z;XbC!ggif&#vyjhP2L!g?6Zbqc)&DPQWc$Br zY@{U^F~ef#`oH=A@4?EMCQk`R4``ms?@M5OP3s#rcPNo17&66;RJEXm2vs~h-1Re?0P=Eb#Xy^qoa7INqU@0?|gp@m*f^-+y^Jm zgW6)|d3}S0lMS|i!WZp0bdKFfW(z#=IX<8(*z$C{r+;T~KHW~__ zC#K>Dm^0kG*-uQwRn*p3S1vZo7a4V2D9;Y)2qnbRdQn?_%i2}kayRJdmjA8P%k-Ui&3$((eL#1lG56EtkMklpCt za!r#hxaeGnPWv4+T5$TC8XSZKs3`YVo}l3rrl{Alkq4%~4K}y;Yw+pW-OfII#73G6 ztxb=3rgBqtZ<{DN=Ht9ii)Y0wozN+wcWH9}GlopH$bqD`Gr}^U0HCy|&KaVNFBn=9 z7|}`l1DhgD+&RfUA;D9&IFm(I6;rxg^eW3)H4uk`!u=I+D#yjIk8Xi`o zlZ8mSye5U zzy+mra<-TDr5KKafVt8J9x@!L8R!~_p=VsGX!0@5vuM{ImrF@#d6VODvcQw%r!^$1 z=A?55x((&Fk;UAZy)x-kqK*<=NVo%whb;xo%@~q~jGRDnqNT){M6ts;>=NlQ zgWN>sJWNGi$AJz_j5GoKH0HCa&d$2fQ%E)GGO~oTIYf~aKyJ8kk$0f--@p{4SWW#F9itR@HfSAQF?x8a{)yHHK%8Qzt5v5S~q@y)PWlft(IQ$t9 ziX}sY#Deu|yM9+xy}@awQHtSIE03c1W4o6(gI}#RoQK5(vu>Gy!{wWvsS<1+j+1H> z%{DtNb=tvpK=c?B%Tq0I)6*hKn_e7?YbF+{D_*n#fnNU1rtDth`oAz7{b@>NzXYR| zqV>g);T>fNRBP<(=F6k=jb977;K;#yZF*>BMKPGan}kM~%VyKmVA4nS$|=%0$tiYU zJo@}|*QqiJD;3ZF&QY1F%T7k2rOLJ-z6R|?`qxUpfsf>85YGUfGh)e=SBes1BJrzL z5xDD5pji!%=ghZ-+SOEuV#<3`17z&p(0to$E)oliVzu9G`Bb6JjfY%g(Y}-+#7a?Y z(e7j&v-@FRJ1q~qx~zX49-S{djO7EqzFv=?{{~L;zvQAS$mJe6;BMfg+NLSi-^}Rm zgzHd1C?5d5_=v)!tDha2aS%sAIP2tN3)K*iet&+Ye#EyG5RW7z8eL237XSUv>C~t- zPf#!#)NFcFc0&0n)%S125%Ja6{nIq3_2<=lkdu@fbT8GOY_n39^BphMcX-1voR);8@i>2#MF*Iy>@d|WEi*DQz z2=AP2E}}?pj=$8W-!PvRnx(qsPPAa;{$AI4;@^KvduT5Q8NJ^M|HOfIK~{&d?D=#u zrFyh7A9yHemtKqLkj~tFr)m>_f4#EeU6T2)TD&4jVWQA>sYQ&opn$;%}o3zcmQ<(cT~}{ zu7=PnU>_~8gKOtojgA)Pn^`e%Yvtdllu)|fg)Mz`3j}lhPWX(`Ec||#uS4G&4clN2 zwhDHB#I5@gkq|KWQf5e?XnNwCpNbvh_xw`82v;5QcCX1I-B_m#bQ^X;h;KdOyD(J! zF*bkud7Zo8=eOVo=DE}~?5!Wdn~thkjWQVYqjlPM{S`)BUPLKV-ZYz(scnB!+vhE) z2motPtGN4L9LJtSO28b%9vQ|S3HBf0{{itIkpBVoAJG2+)5;zR3zdb#!T!JakDK%V zYuL~Mn$GK--~68Hb`Bz}1dOH$_)FNm`FTJRBR1e&yT{#2^j)Zf-+C=YL_ zQHxAE>ceVVfp~TU^O@k*fN{Kj(B%JY;K$U0dF$Ea^{_YzQ?B{s-v7pi>=A#!aCBWn zDbogz^%r+sl}?xk0jhfsU@;yz~MN) z_6+h;U3@WzbUkOn@w~FM2x25#ryjf)=jQ`#QBAYEX-TksGru%A9vpoAg1O9Hy?c#; zb;1j~O^&Qb|A{q~M~kwkQk(XmJ4gbaPtO@#wKUoKcB5PVgblfTg-la2{Ds>WigDv* z`)?8a5VR;{)0(yD`t>apOcRAIVdIh|48IO54vohj*EV2^gND8!%CkSjZkcddl)ZUi zw_!ZF?Sp5D5j>a_;yrgbpt@+M{*q1teN@;_wyQuM;fbJU)Z6Wtu~F%bgBmdcTV4U2 zNh>i{!xe7_&6*Lj=-i@8-APg9w`R$ZM0m0K#vp5#2u}Nm&1Fl-{{AvPo?& z7lJ|Gd@MP$x5v8qbkOHe^F@VNnr)GVSULie&G$ZQsoln~_`Y zZL_g32vJ9kB(b?Rzf8W53;wj6M73#7iwm^ zfyA-_iwf`KW%=6LI*`>J#b7IFO&*!TDF~tOspS7rf=1D?1~HtURFko10Ls%FeF=cJbjvY$L@QM(3G{N-0s#& zH&{!k%5Xa=ra}EZUP`OLeal@OvYmovuO3b(jPm!>K9NIV)3{qzWhUL0S7$VpX?okL zIgH`1heAuogQd7XVmZp}be)?DGTLjqgrglPlsg3RO8i(!2z7ljmZ!LG zDHq;edhAW!hCpI|uuYtD{wPWHM)s+Kzj`Zx`VC_V;Y`lZ`(q2>D;IyZN~Ns}qco`> z{!o!HVQH^&=89oN<`#bSgt+lWWKsBI19;1DFR1*xLxcfM$-f5ERvT`7Bkb~2(Q(RL z%dsMHg+k(7m)pxd#ZKhG8d`X2qxjVjSatBLYOFEYQR7;#D#G=90pL}vcbEK$+7OUT zU~;*^5LGqp(h383j|5Zt^bg!fZM4P3U6PVtIrSq|clqcJyqJye*?Z%0!IBAL!S!Y( zfn(g{wRG4ZZ8=KSb%)22SWNGYWtnexKi{mr?@irtcX$xpMe+2k?3>**4fJ(6pZ0FOOxorIAxN zo3wdrKcdk3=EdX?5buPzH@2~ex_T{1TS|#-d^RN0bJq9;E3F+;JaFkTWnAId5&b;f z>{}fMvBO8zry-j$H!==?Apzj!7Z)`sAp|)z{L=w=7tM2Vi_se+mI*j7GhX%=2K>Gd z@Er`VE$o1lvHg{yIzigWmG(3mNnJf_>LKlBn^ZYbsT0p77EiN(%$l3H072|fKrdXv zFFExrFX7GV#fUQG>)EUfsRV=DyJyV#m#>Ujy(g4vYQq`Hhqg-1f)M z5bwNppdcxcd7v#K%t!9kakACPXq#s7gc$|xln{} z6$~ojw+@`rHHFMjW_*9xG_tOtWU&G`lJ0`)Cw@RA7>Sl|Lpdw$g7x zOM@PN_*lP}a5>2a%pK@tTrD}1WOzZn!XQz*+Hk_B`=w{a_xfY1?d=YSToB>os13OZ zlL&A`CY7ON3{l;{XlEmRo?WzGz~&mq21U_{;Xbl6URomUVcwu!X`@B5 zW8p1INQ-*p4kem&8W3GC`cMez=af->-jfmq9+`fgW#`!f<0~_m_LiJEk~uVwt*H1k zH1GVcq-(8uc@d`HTH6EX+PSK1!XsR(#mwv;8luPk@0XW>_rW*DEmF*Lj#N}Orpry& zt+q~{Hwq6P_8PL(wC(~k*Ciux0 zXbj!D=l``e2h0E1I;?{M?myuF1K~dqTRRw#Bv5cT|F=#q4?EZY%u{H967|jh1dRqf z>Gz#(KyWLIK*ITXUJf-Aj#y?}nl|TTZOK8WlE9fqsz}dPT-EjHGSDbHB~IKE+4x84 z8VLHm6>zUWIyyLG#nIPAQcMIcG_OPI^JadjRtQc*cH+X2jnWe#Xha5%WWc6&uoX{` zK^?J5$><%jp19QiczXfrtUT)Fi-qLrp3_PwT*Sb(h_TEYxim3Vu5A)wqFfaygi()m z22JZVpnBM91h+Vc@l>d zP0`Q27@~{fUFK^&9=%5lG(dq1kwW;b{z}%k$L##K=-f(sHq9;2w^FwpC5o(yo92i$ z+OPvv9WM%GI|3PFtj>r@HGGvqlJ8Pg9u(7d?yP^TNJspB@H1}_y_^+|eK5(3{rJr- zjIBx*?>QXRKr*nMJ~eyF{Bt3B?2b8+)!2QlGNmE1{H&!Y=F6`xQ?-u4Pf87KN;rx* z=yu1A7J}51?rsoDwm3zZJQ3uTr-@zQf9?T)7fh>Mr2y2!DC6Uo1`6!gP^ZF*5oREd zq2vc1gar|9EZVYbB~eb^H&uo%xe-f9_qm`yT+A{OrWyElw2;ON?h5|(T`4XBd z58azkyB8pnxmDS|M6aF+ayX2-v84RtUwIzgx?W4jsU6Qq*^#a9lfOBeWft@siukSL zc~RsV9J7Z);;E008C{4g!)&X_BW?kitMXENtss~3?)bY``;6=I<$J(|rL*z*`fk8u z&KBQp3s=iUE_LNRmhZ_$4b|MQsK_X*=$5T!wI?8WC_)-}bZA$j{?55>Vd3Pj#AaH3b!|};#YPs^x3>=`1f0nV8Q?du4jG{8g~uJ z=ll#KBQo|pvDb8^(h{2}<_5R8*1Q=rUC7Z0TuUO62V)NRQgNvCVL60h5KoBKC~Z*e z0yBVI$iADxPeD?=#wv^c2&+_W>G%dG(zbdEJwp;$cjo{v-sh~qg9(=wLh%<{8zwrv zqR(!9+EB=#RJEG$AExgTG=mqSBu1YMBiw3@6_Mqe-ARq5#g}MV@?WGI-ZFAmYp*qz z*Rd0MBC+_Ksd2UwuO1?+SB(}vc7&}aJsW_-MUSfVU}%bnq>|UcVZ9Sx5%EJx#e^D+ z3`%&vuU`4`u|VI=VrxrXrVPoc_CBfckKI!dnG3TKg5e#bW7b*orAy=D$MJ*-1J#YR zSiDsEho*>^{y8m#D^l$+!wV6y{7V1%#;szKPOL=;3rL7l!GWvkC zK=o)U42PkNL={v=7Ko={H6 z3R8_Iylw`6WNa(DoTnnD$!Q%qRp}22SDKsCw|}#@vZ80%VY4*~aKa3&NNsJ&%3Lz# zQZL-nI@|AosvRa=?O+@QM;&i$43xl%y;qlad0xa}gYD8F1gXnJ@LfSGMfbbSWdwYr zm}2w{FChI`b!U0eBQ`e1A^E|Iip#E-SHxvoR)AfAPt2mnYDPSq?ja`CcbV)8VqDe! z`y9ID`N(&N^Qs4RtbR1pa-;NQw#+HkU6ThRwz7o4^{ezDT3rQgDl#2gn*(@8kIO-u zJ=ETFst9*l<^?~ATROt*FWm`CRg8ufE$p{Q>5W$y`eBHw%4K5OQQ@oEt(LR-YTL+y z^eDb8>GY^vgO?uBethCrA@Pa7`KbJ?!yZ2N+QwBzxSN?s=tb~j*cHHI-T3u%Ne5{Dkghm@xHRlIG^J*r4ZL`y;wjZyM->60englIl47 z`*Rs3s71|K3*)yB*tjKGPkCmlfrx809rq8jwah~WQ5bNTLx&Fn;!v*PSdfx^{X~aV-|HIEGzn=0RUnZ= zOrpQWQyfyTqJ=V$QoaLQKTkup5o46Ko9{9{)oQF<+>a{djtEYViW!QM*LGZ#S~Bja7_A>p$ep2SS&}H2Xj~j@|96jq^?w#AI9bxZIU{30u`+XTv!xkaq0j(r#x5qh z%N!fOdEMquRYqED*WHFVdH=gkSAPAMH5&Vzp80w7%xv2)mh(3AKdm`mI7?vOpzw%W z`H|y8(zAV|nasGf62gJVqv}+Oa0%d#(24=?ukv9i) z50v3r*czE3QG6kyHvM*OJ3tk$2Xg{h)k!Od%Cb#nY=YwAokm9ym-PiPMG8*6FhSZ3jz3N4J-?ITUyX(`Oj1V0<&n(^2XWKF+bK%E#TkP1A98a<5}4twfI0^&=D)|6BZAv<1_j4 zUHbson@#Zkc6REQ6AU*f;HxUSqI*E_w~~{S5{TZH%~~?Qxx0C{tf;8FC<#L7ig@!F z-$Ly5yT6csep1)RHs1V~Wy4n;>ld7E?~${qRw_~ME7KRyZT!0Q3;FV^i0jLHHI#D+ z*$g=r`D+{&5+_R&{HxIYxx?IS>FcEU#dJ_S7&E23^D0KK*PikRj3IyL=vFZuu?Yz=MqjV^8goam|Q znCP#^tx;esRc&=`avF|?>7Mv9k8b5_DFZ|Z<^swm1f5t}{CsQO5%hVfD*dYZvP6B_tpBW%;~~#vnl7J32Xglk_idAN)<;&C%Kz zAN1CTPEdbDpA%VuCSy|3{% z`I49a+Em;gpPat{IzF?a=&rwOvEt%;)B8>UjTt=Q>22UK2a^Oya$kSffYTwu2f>#a zds`#8_l;CQWgL(kzfJ1MJC0l3-bWCUn;Cz%Z;{u+)6#$-Bml|Eex>{8#XNHj=52mO z0oX!S6jY>z6!I@$^GC#jhSs9^u#b*{YFUt0L{BA9EYIE)n&-0g^&LroP{1GSd7Dgt(0}Q* zN9;%*fMHwyN)@b$W#arA$iWmkyWc(qRzXddLT|w8*OSxXXY9M6PU+j#!`F!Bb6oX_ zg}pJD3J2S3C(1htwxzG6H=DX#yFA%E@J~?x>qYJjz$E#+!hI#40|)1pjxCf*B*-e!I+-oDp&=y3;!^Y?z-$S$+`M-`|%fuOP)O4epjCScL-$|{ZnY| z(WQV$?IgYM+U$pX5qWn}W!R(Atume)a#8IG0 zZGTx&q7c`RKMHAp)Y49vJF7Mm`PEJ9<9&VZXFjeuw~4kIjPcUR@_k3_k)hXK*YhG1 zO=~Cg0GSNZjE`YpH{+%FYhb<0qE-v|ccUex@37tK4;&+wXm?LqtQ)zS_d7Z!jsX^MTjmy87DXPyj9%7E}ixZJZAv;)A1CfW~6R?$) z+`lnFk7iKj4v2y#uZG7%IZ0c_NNaF&+y{0gF$q~$V1(ti>(EQ{ zwcTQ=e7E{=}Gp zU9I9giujyAiv~3fSt2^-OZS}*MgJ>SMDGg=#<$1CV1YFR6eCm>`%fM)^~s&c-Lk0F zshig*;CD~*vWM8W^Q68P3+s{H)%@Xa9@e(}n5j~ZyN(FHY-jj63LT_S23r?Z#WMnJ zpDNY0K3>W&93cjUd~Sn$mrve1xyW~oio#U&o!g5;kF7by$mzJKf<(4euy#8->5|e} zxObyI)Ly;T)6Z64kZ#P3?xyXIdeU6U&rFFQu=o-J5_L>64*OQqX_3_Xf}xZ zMUVSNcLu()+G?CdJJc_HbZ1x=v0YCF_|awp&XfPr{i@&%JI-lzY)CE-3!H|q&`gKo z3^xugO@%+N>piUp8taK&*l2@1S|sMm%E!F(bSkMX38{DFefs;X?6!=!OWWX1bXOyd zSgbNfuFz#FOpAs*g(l)2yd4xXJLCGRnv&YucwD%1z;&ycZd8kF1-lM`FD}*W2KBt6xFj}f{dVgj9Aq^Yj zn>7LnlaCkH$T-HQ+yMmBV%*yKuwPrJ^jB7YKEiPS@}8HvX}f8=y#hJn52#xr??Y7~ zU#a)`>z$~P3^PYBp+FBqf1^%<=Mih5iK@GY@#(;2S;AZY5~b-kw}(KN>VReBlSg6U zFJ7L!qGk3bXUnUMs&E-Mm5FGXfc`-^>797WL|HQ(FwexjGzY0WO15bYp*MRFp{-yC zE#1g0Vj=v^)2Pxe2AdByX*mv5B76i{Y#K5Av_xn<(aXGG0CX{_TC>k_?5qgzJe!t; zW6S#*;n8Dv8y9!RIL|(IN2GMoh-jCj&J2WmSY3)*h+r+H_!RZpP`i z95l|e*lA%`(oeIxl@&L;%1#c(#zKt#?y^}L5(MrD7Oj0X^H#%s|5x^qd9T0a=Ir$s z3{2=e#^fVbwbZO(N_Jb*HI6aBul*eB%tVUA*@In^6~sqbUhieXMkZ&nFuIvXEt_`o zAASd>Z{dhZU;f(s%8+IXP#uIm!7hl**>dR$)*4R+pQuepxGBSV{wlx! z9Oi%@nyr9t7ph{ivW33qm-s{-7Z@sAUl;pCy-;!49@3PYjyrAt=>s_eKUdruTS&JZey)4avsY#( zRJqmAEfV^Bl&{77;9_zH=mrlY^~_kg{KU#RbXC*cg1oR`a>QU4KT_#G$w?)d-XUT* zWW9~A%-U55FkY#m-9uU>B2}}ahD2q>M|=iZl_*Pv_aX@#YtlE8bA?4Otn8(@F8**R z*PxtarWs$waWl}_PD8|Bh9U?;ZOm#iqHpmB#_9erp_7sNwRUd?Ovd^?9x+LU#S=>q zH&tG((Wm*NaVu)Xy(!n*SYG5(Dz?V&lQ_#M-JtiAg1fNNsznMZF`PBn6|oY?F~N`* zu?JZB8r3{E9v*Rcz{7Z-Jfr8aP|Ksd%&U4IJSU7^9}F%Dm!foQAhL`6(27KAIiYdc z^WP(1OQUNq66U4?_z%YnyN^!hFKzrxLfPm`5;!$xNg>a{5o3h)LMxn43b;HRXrM1f z;$t@Iag5%tFX}eyjbJ&xR~9h@Cx%G_uUa*X#}f(zEM&00P-s=_&TIy`&EOvPRX;j= z#PxG@)6@BC--vQ-OZh0Nju=+S)d;ik`os~UXqNC~j}{Yvc(e~`d5d~p=m&SI=Emjg z!dOW5;Jm4*Uw?lQprKtZAo$?GRnsq8T_gQO$XcO_@ntx3cKK@A4ZXJWcuv={K#fX6 zif&`N9!Ls83SqQFQxi1ngORlS<_K*+h`kvw#?DYeAa8p#Sr$i$^Jj1M43B=~M8wOG zE)Ek{Xp9rvt;-nGMhC7x+GU*a&{V;tQGP1NR<%wH;P|@e{q+HBS&q(y7blz!Y9>l2 z5@Z`zKi(TKPmV z%W|qm_Hyu75JQE^-USXBbM;h|D06}1foX;I1Gf4?SA2nl!1>{TQl ziBFUZ!AXD+k#2MHJ(+P@EPWp&yRZ(VSk#fQCvvMBzG3DW5i)qKo~+GQW^xX`1SqSM zWWUT>NgH$E)g}LS@OIR+3}lle;UWIa1=8FGOEb@{#4|1f@B+FyFr+tD?3UQDy9I8S{zbVS)E#Y+ZGdlmt&q8IlpB`^=>?7k3D@C$y$19zO zAZYHdYG_~u$!1RBb=j6^%Y=FfEl@2mQOZZ*8<#6&tALD)h4}4=Pokp;V^9?zPYE?y zH!$=zN9pZvh~aTFEE7_%@thFCZLfdpv3UFxCj+QxI9HK+#_OtV$}1Plg+kZ0AMzvo zcB2V;B5)70N_e#{x2v;JiI7FaLBQdCdyh{?-Mwdv90W}DgUSRJ9!&HDbNZ|S)?GdY1 z?8`Ek8o#r>TAP?PyYJkfo0HS?!?ZeVHqS^0?6o;<9S7s}ne^yPsGG2M^ht^1q$QT$5WZ zcB|qbujO4FXo}khoM*ltlGU0(&__ z+EEy%vYNi@1XLE+nNe3`-*xo~O3f@^IKHOq%zMpV2r)0>thVkUJs!g`-sC}?)XczUuhhr`Wn@kyb}zA4>nDHCcD>@oLb%oL3?gf za_a?MGjY{qOt*BKQxa~ov5TCZLJHb3ZrY?$ion{TZSxWYJ4!XD2T>h_2_S@w&$6oM zr~OGA&BV+vKPehez_QZcpD}!;4YqK}>^tgLymAhv;B140Xyty!@+xTZMz{Xf+X5T{Zee&KMQ-NVN0*^;FZB{ zp;sX<7*B1`;i$BK_&YZ~MnrY#XZZq;HbHtZcSh3(XldMzyn=bMM)}odx*)!7<8S(3 zWE@bsVl*mZ8q^yqY`A$&@9M#`K&W;fXmvn&1_OHnFsHV_FZN1)l>&Aq(1MfgWoM03 zkV=tFatIlX1tNO5SUXM50($lvlJLit8#6@IG>~j2N^aMGqqAmOSs2VnJ$(CMN!~6- z>)uj>!IdTWZeBSNljW8vHA{e5u@}N2&fi{atA*iP*%H@K&U#~!Ha~oJ`U8dn*46M= z<+|lgMYc}6rX$XwfR0KUSEHN#AC-gMX1^^&v~fK43giX}eksn;oXx+&#|uKi^~Lr4 zK`3&UXm06$@tk-X^d<%nBj~a=w3RdRnJ_ku(3X?2KoM=lMFjTZWNT~xvHqcA5i6iU z0qpjaZEuTik=)Y$pyX|R+o|i!7W8eC$Mrf&!L8WAD#v@ugY(VeF*zm7?zdiWdyy!W*pdqUIeu+!;p;nxKC&7B}|Xo?t|P4_L;WfpFn zAv2NB(@!@9`?F}-+~q{GYq*ZoVLeWNk=ky5)-j$K8{nv*r>BpJbW<Y z$lSqh7p73XG{++k&C*>}TC)c!CMueet%Q!pjT91%U|Rs1hrWw&XfN>M*}nGT1p!@> z^B)-kx$1#!;l}+iVm{z%v6Os^1fE!64%Aqs^`>s2`!p4`n*-Ipw{bZEWX4=l19i)ZlmYDwV8 zSse*`e#03;!ClQbPZeP6OQauP!}h8mLetlAwFXT`jNLmtdaag{NQ6fm!Hg$3V>Yof zpa4+N8Qfs0QK{D_^T=e^tX1%g{aR^%sF|XO>^f=kz)4@7`~g4a%qg5SbL>rn$+-?I z!qB!Pv&JivwVrhPMpZHg$5&TpP{!#JA$p{`*IQwz{ZXt;_&_*8>?=bTY-D?FsS_Ri zAoUVDD%$We%sWRUTAyLhD|lP7je73B=Qb^`vei<@qNrGPNl}Iir@rc?f>3gQ@gp*I z`e&-A@sv0oHTh)m-B5_x&OCQZs48H78a#5yGdsMCgDk-PJuG#MJ~Nt&f)-vkC_Ajc zpe}^Ngd^FIZ2M|TP|U@E;G&o8yL910$~Q;^9g@y4+PSh{qhy`zSRk7_TQcYIitp!ouSCh5Y#f6lRs$Y=Hk56CR|#5)f$?)?G08Wv-wLXJPV z*5y6f+fGuHoc$V1Z(B8<_=Y3UVn?xS#!%DGlg}*10ukViIuFCdpGxkaqIMpoDHM{f z%uxr5v(;^pdnM_bq>|b(8G33eA$mc{*&dC3LM5^x>#x;>Ht~ca=7N}ivY6X2s zn~bV`Xx@=WBN1dqhCs;TpuMZI(H8bKS7DbEuK>6ChHHhqiZl19%rw)w9EFDKXeLW% zpB!_fK~pt)4z@`;T$Cd@XxU@S;04VD3620<*!gp;rDsqkhnpSvn6Gu{UEj6dQyX%Kz2V;;8{Z6C zrEL-Kxxz5@&R?aYxSOV@X^5B&yWV`u`eDXIdrY(kUb21Led|6Xy(PZb8t@rHjx%|Q z6`z7Fvsk}ne1S6&4ax4>Dx+OKD@1vI%oB%=e>RV!G$wYs5qaEym1e8^V;H|Mzb%k* z;=#d+-dc4CCVZzc5mpS!B^l!4ox~I3qZ@Nc)2Q3AR@4(P;54cmlgbTK*&E01hP^B? z;v=zx|7sVH;^N1O$1N7nfPj+NkNK+7^1b|Vvz4bwF*9eTl2aNHg`Kr#d98OK8Hqt z6+){>84od31oah=J#)9p66nglnybZY?~IzN5sr-utS;6_EZlaYIx*4m`nqY`UZ{I?Bh3+A#vNwh@J3|mUocjm{N30IO|`3UIklM(hfp#owURIw7fN_IJ;9uCj)jfc5C>_%+(VHpp}!;`N+Itqb`Z{ zv&vEV?2=SF)Y=uNg%1Tf1(;ydLNK5x6)ChPlFk$0corv%c~Ri5*0kIwV}cCoKVArL zCV!N3HI~|j@Xc5+WHnF@gClo<$sivzV=|Z19q1x|x}AB)>x_Mk3wCZc$8-E8ZdGfQz660L1W`tCEB=vc-(GOMuzeM`8tk&e_a zOfU9-RLzH9qw3~yGA&qTItKgsa(rZR-{J|+D}D)hILnMv#3Y#pAp4guULH}xT#>Wq ztxx!sClqlNx)>vf%x9rZkhOM-*gEhv7g|43e!1LDuqJT*;t_o1k7 z{;jrqK>aH2w<|h7vgw@78iY|F;8e>V7+`^aZsfJ_0K8y7^f%xv9?yd13C?K9574h; z4<3ir9sUXD0@Q2*o~&Dgx#m7h=P5leUlZ4=r>j>bS6v;g5wuOF$_gqI#`7fOF=B$t z<=y?J%_Cp|En1}*DC&MxY2b@d=<2Reks_LW!l{m#dU*QOM#q6IzTUby}^=??A#n>mBRi1@N_xwf4945NH> zTPrqoRQuMo97Z)50F_x-UHXrxF^?9MXui#BHI;)4R>Naq_>Kb%Zb2={V0K)8vDxU{ zkhAvbLSnIVn@c_b)m4S zuU}uG(r%VQZ&cKl%#ewMmB0PGHVi#!pMib6{_>#;#(}Bu0rAn0-Y z$TpZz8L%Lri{ zHtyWYRu=sOF^HLL)&4AoC{!|O5=?_r_}pAs*_4ch+5&!ns_~6scX9H62MBFhxegiY zE_#EK$MlOy_u(vrxUc~tRSvFA6T+zHkC4WRf#$|ie-Z)t4Sh4=YM`C8)V!Xx;@ z*sJ=gg$G>HrmV0z8mljQT);`o_(N12H6vH2Y{pjC6H|zm(FLO}O@Mz~u~G%)?3){V~!p1uI{=#7$Hz z^Q@hW$f`nHQ;WkGyo`(%yoeAvB(}{S&K+aWjF=(tBq@nmXpFOeNU}wybNCy+Z?=D& zQwIXIkZ)>GRa@l?D6om7(C0_!j4A%eS`;`gl|u({;SZOYm#h-EK?@nEK(C$WE-^)z$RFJcOoz#kaF{ zaM@^Hv14|t^NbpQRZJ({ub>jlTR0aW59A0=24zT_=5y`okBIakh3cerRS+J1#1=>k zEqysw->)M=qoP^6{i*GiG1_X;_w1l@9hC~|358j@a!tH5Tl|4bEs1F<$}P!a1zn@X zluVH53{1VyAvXoXT#o{NV(UcTxNxZJ2hnvy6U($+&b|(R&n}vdmCf>>`y6<)ygkou ztcNhAiq?zCb9wpGwA^ZvT?X~i(}yk@1kz^R^ie7jhg;kgl&Bc}WZ=sSG=xQQv@%I( zI5~9-!Jrn&#e6vn)A9@1a2#EHloox`(dyY7emwemAEEyH)IExT{7IIR1s>lM?&kr{ zdD&%3khIBvLBDj6!b*j`HsqcVJ|9OWl8z|b+;kx$B>2?6>IIlF=AEm~*Xi-pA>x&hTa|4Ek#l6I| zh|U6@Ta^m05Ru&|R_QHlmmYzwb0m6yMmCxpxIIJ7T`$6yU>>3nXp201Cmy<=-HJDK zC2uRW(t*%j^pKR8mw>&vZVJ@Uu23u0=#{-pI}=Nn)19dpjO}u}#W6|3C`-9j;gt3b zxn{V389(Xul1VMwFVVwE0%Ah4BhnisZ>7zCPgbLs2_vIL<@b+eHhE!`x-CG?*%UG_ z!88QF?zdUSpE3nR{pZ=R6GagP{qJz_Rdhr5WxBV1(3B!~)G`?MwNO@zcKr4a(Jg&w z0VpCuAc!1u&2%D1FOi8N?gU9x!EB*YW=wd0QxpZUhF(P17-i%x3{&4J7dEngJq`@b z*Hg1OISJvUC0DpB2{rAjHRyrUllPO_Q_3)c5}T*^BKzb38p15rJiXL3G6FL8B^Vbt z&3?!`BM>OWpARv>ud^->ED0@XHX-dpmrw6bgOxa*cKRpg-%HeK#PyjU*wE#*U*GI?%NBWB$>oBEBY{W5_bYWxRDpg?`dCS$)jlyRY!*yn$Iab|L zlO>NiLQ}KRSxE({o-+|oF=yr!;7SNwxDQoH{CFpVw-eZ$o7b>!$>ML1wR$jja0+${ zsJzTpEKjE}-Kufy7NZ04m>Ms}T&4|w1H24phs*e>QJ@&JmVu?oa`~*&uZ@S^`-e(O zHa_LllI$AYv&dh&qFqGE_DG}7l&h)($00w+@NJkbh%g-UFGz$}Vt~|YNxlQHr+Mh7 zYbpv2%5y2AnPh1ox`D#?{;|vo-sYIzTIfcBV7oG97&}M0)#Sbc)X6t0`^xPPm#Bw?YY&{~L=s{8T{VFuLHbvqk^1SVZvDK=?jd3mxrsw;cQmHg(TmSsv_7-5h=b ziW8k-c2zay+O!Es(o$m#ru>oC&Y`C$xM@#4dfn^ZTmoutuUj_n$4gtlsB_;kSY*JQ z89BB+(=zf9+vIaFwK974tLrifD-ar6uvqm+B*tp}SCwn|xNZ1o=3G+X; z`An#cgq^Hq1V_S(hMCSKL?aEg7v%21ThaQCXXoRcQ&c&h2gEG|H7)rXT9Ec|?w$~5 z%jd}(OphzyD4!dDh58}-i1@jN%lt-R1i`aJN1h$ z4yZJ99Dgq4_*CNSHHXa~A(EeDOjYTgoxmuEa^t^$7As=i`mtw`l|#N>L27=XjfJ|& zjkc|9nnKZXAc63q+i9qPV;pA=z+;Ye!xgG;%ShwN7MwbKB@Q;4S)rgyf(*V{yW)xJ zt7BH;dvyB>${fNwjO4yFzQXxQv3VPuWdp) znZud8T?{Q+1&G-12p8qE@7ZG=wX^DjJas*@>t}tsi1V3WPb?UVCmR<=e>E99Ng;$X zJ(W9z(w*A2sr?_nz_A(&sxgKJcmSu-Mm`XKCr#8eEJk;z3%))$A&{IeC+q1DS$GAQ zuN}j~_P1*_2p|>9u;hVtF?*Bol%^B+Q{eu%hw;HC9WjK=$t27-dU&z4uy# z%HBq__8Mm^|3p2H$Z;}FoAC%fk)Shwu?bW-M6#v`@qCwJmR2Uyd=d|-Sup4`lwyI* z@V;*qOKdmk3rOciwljHX)3zz*xL=f2JzP+MJ83uLjz`&;z2V~R#i?GAvINyd27w9` z`!GqIh4TiVj7_?hVW;#TRl%*bSYNEx)7Rh5Z^KY3TAZ_1ozAojGSX;p^0-oeS06SF zG#Q!`76|Fih!qBY;ptSI75wQ?T#+-Fley}VmwhZm3n{bD{^tWbO5i9K4TOl>^?c+4 zSJf#dnz!H-JSsziSv&BIoB;N>Vn4DE?++J~P~B=;KTZmU9NKH2z>|~x@6sNg#`R)Y zgpG)y?mJh_<5G{xG`}kuT}*U;S+I>~>!I71t~qoo0eM-s5opD;_)x;YT$x6gq!p(9 zt^hz8x+k&OWNf?%T2a-$nG?uMufIWwD=9o}=dODm9V5K&WsmXZJagHenKDARF@0?8 zQq3MN=S~Oox0mz-a_P&ct!ti)>qQ6*b_|0r9Isiuf+PzR5=IJEB4oVJ%V&dOg zQe)+Q+BxwYfalPhdt*U=mZEyrV=k5l{P7IbFtb-Q)lY0E^b<98_M`ufA0i_+#xtGLa7>OcH(T%W%Y-1|D;HLdDB{t%f|zB=wJDME(^c|$`10u{@$)>l%dbtyzwPEJ@g~RDe?g+B z;ChuPb#O6em$4cLP}1*X5T>s>)>so-V1f@V%vdmxtZ8p_Etoo7JB0045 zlI8Lb%NIo2FDeAlJCU`U7!awurNRWLY(ZMD0kSnKMc&*Xy-aK>^sR^C%)ZnpCL0nf zWWgR5LC&c2ybF_rh>{TCi2OL7TjW-VY0BlzQXGtta-k7pa4`^yW` z@YgrmE5#nxY+;%{$>!E`walRwObRH4xvk6a2t)ETJt?UJ-(e0H5lcuh(xb70m4^kw zlLo@{Rsn$ky$UkcpzgiyJMFxo!Khv35i~P@K`sHWt{y3qGnBI;ITOUSc-RR`)}EJ1 z3hljkB5lcmkK9vla0zxTL4`Vcugym{JFL_atXeS&kM-nmfCj1Yn0VpcK@BIHGxro|)Y+SM0mvV-|=Bz6~2e%drPU<+!?i|HuF6+H;~q-T6Jj zLRY3t+&p9KjeZD>a=t;3s}-*>OA#-BrDXGS448=RHR)316F58~rpQi@?{unN*4gd1 z_srLCkvY2U*59b_9wtwrU5J#?-{Dq3iDhP17>?;u~&*rq0mGH)eEnk1pR2WH|1){YbPX}7aU>B74KSPf}GJXM< zWI*bKmE4^RaR2VaW}77fFlp75E*PMQV3<-zqp;=091LXqnZ(|J~$Wr1q*b4HE@Gh1E5FKr>g7kSo_OA8D*8N0Yi;CA`=nMPN33}ALwnaG{5;ZBSYCC-$TH5=_<~dTS5S7P=@O5lQIdst1 zesh2RJnbl?Ov-?`a0|*L8LSc3VmxY!FyH8<)r~tSVng)Hh1dz<4cg&p+*`E;l$>K7M7Oj?0>roj}tr2!T7rHGXOeQl!yoL&G|KCEE}Fz)`ydh&-W5GRAB#nNuDh0 zUzJUbY9wM4@e%*s$u3k%WQo)1vgaKcRYD zScCl`#Qah=ZeL%#mFHAEs=;>~w!pFvZ6zavj(w5{F>jbtWx*H)&%w{eAB97j$e2(I zP)k4d$zI?%$t!MuX}Q*%J==AO*T$xQ2F7Zv*R zbF6k~dtM`CA(ce`RI%#Bl;gG8g8`luO!+PqO%iOl?|&lIJGhi)b(CA2{&WcQROKNQ zK4h-T_5fk`OJSohXp4iJ2YP=onvvc>fY3vN66hX|Z4}dg64pf`%@e<gIn4R4TuNf+WVzS?6@tSH~EL3CTRPq3;j23fO{KxMigPO%0Lk}r1 zQlgm>6|;}`Wj>h8a>Y=nRrr`9IQvzFPDEK&;)X+E7uko3KY62m=tWKk=K29pe>X~{zsZAs>D}VZQGkUtgFu)OHE^0|lj;i)geCAR z2=&vgB+{(II0kRRCTLG^oZ=kjJ=YrsMK^UDr&S7E-)oseGz{ZN+|XV}tL$`U6ainq zT*745mJPO70p-|WT)05$q`8>iWzdVrD@x6dA{}Ud<~m;;c-bKQWuxx>;Cy|N?aPWY zns()zb#Zwe$Zb3%6_bE8?>WCS|1PYk{Q(DrOY-22WrhnmF^~}YmbFtXHhd);4Zj24 zrd_%ro$Jn+#^%_t5Z~&4Xv_R+ZZ}in^U%mosjfm>?~J+kHu3lrX>v1x?qTy2W9gLri?X>dFW)fT&*f;dA*4-5 zV>b+1h>E$_e7*`h!YXmp{h0b#P0*wM0Jt-^RART&E^H6 zDgnUx81@{+5b+@G0-h?~J6Lra`AbJQA$ZxHArdg>+ z&yhYcUj`VJTd{YFIdYUVS*Nk16sL+QeLQ0-;sYRM!3*qksIpM<2%?-QaOgi*` zMbV5+cBzsiuo||$YhKB_%5-sI8Mz+np>1X;S;c{n;1uIR+#HbCG}5{np%MAYQcgni zz-f+od{{x>e@h2@5z&J@&K0{&am*Eq#s^p9%M_xf7-W$hC%?1hYDnewj_@_ITXmG{ z9$72YliO*zOw{J1dQ-#HZx2?Y!vENRonw+gsG8gpiguaSzbEj88m4SzFyq5AY{dIu zpH>cgmM?lMzv8Uht$t^~R8#S-M8f-nBVQ9k3xwCti4|cElt}`|$?@}oM!TNDzmUUQ zxEKTtiXk7yJF6cg@59fEFm}*VKVjQ#uNbolEZS2i6U+F2FxMaw5E+Q!`znk>#kzIj`UeZsgVqm2CWVC-!9^x# zt=Owe^oTOO-p+>B9i-D=CAfXLCQ;`%`{GMtc>a8c!95v`nlT;)l7$KtGNVdd*-6f= zJW9RG5ztXb`c!51T77BTl}Pt9A^zYzCjl*v#QeH(jCyE25mJ_d$nTne!@-SA-UENphssZrcR3g9+Dv7gyTWb~(8=j<$^1%1aGCyU%APL;9Ml`c~xo`3u~t+@$sq zu}LR``Gt(Of*TCItx}Buy!;uXVVnMMbRXvv zWSqLreXZqffvN9*-oub#v|@A(QgBXU6n&ZO^~4N+0u+}lV&)so8>;p}xp!^~u;1j5 zA-Jz!tl2?gqpoJagPs^|R!v9(lv(UteuE=cRu3P8&7D zfy7juEmVj0F$)rxzLg#La*;ZND^KdS{R7g1kLqUlVy@(W5bACI%;XO}8;uOZOw3cK zO+Ppxao+e_&&K^Mf`;0HMwHc?8b^`+>JG23HYWCOs9<}B2MZcAMcfG(H2E3(E%%d} zo2lWhAw9%luTvK%cBk)Lv;AVI-R|{+<-7|lQl90Z)O8^8lp*5%eI@YyyAu_2;+KI7 zQ5X$XutSl5d92H6j^OdxV^3I9@4@wW=CR?ZIXlvvzb(eTYn7vaV8dEFFvj2p5g#iy zmMV=9(ZXkR)8d}%>iro`_Vh!r9iwTAKltzt?_DI+=dw7oQZoV@arg*Elm$Qe|Q(l>^G47c#ABL8yp8i-}(&`+v4Y#15l zz|Er$acbm#X)e3Y{*l##etd{ta@8k)`J0j0@$Jc@f z_~u*B9lwS~Nb-OYRr(4(ru>#tlpH2Z3nRq%XH{t?;&qgbebu?HFgjNIvaO|EC2S31 zL#NDtcM$)f1rMP_jN|&_J~(2?c=!*3dOS-fvq-FSbgr8rL`Ud6`YN3ytEhm1(^XBX zWC*Vmd8FOCYK407+TS4Cq0ak{22nq1VS?#q#zE_9w~9~W#Q&s!1>-sKDEKW~wJGYUH0-<_IYghHNB1R& zaEs+@L~Stpesp^fDJ#x=5rMov>>SvGR?mKsuZNk}^A%k#Y#TkCv$bG%V7**_ z>U3J}6q)Q4dD*GQ9ylzB=|2>6wa7NhxEXdYnc!F89SN%A=q>l_+3|cdE+5!@cTuU4 zgn8r`w?|*8!zhIpLFSB6G-0IOt};VqD0?TfHqt$(?TBp3U*7XzFDDh@c*j!`2M8lV znBsC*{OA!Z?yi(hM1G&!Cz{=C-0~)Wn8k1iV$TR&ff%(v)3ivPHoPGcnq!i4Q`mpC ze^vMIr4^$+?me`*J_T=cJ#1rl8g$vItqC<22=1J;DJZ3v|Mp{*v12|uo9wJ9b!2ur zZz}H##?!tfG!B{{Oj8_T_>Kw?E`P3FvRGfMJy?i zrrQg}xBK6=VzH`|u#~?Ukm#H!@4shPca%zS)uY=i5+U4d4%9*?Bsgb5Y}V+g&>e+c z*)9aIJZS8%`=fZiW4y046HA@}49y5jPG9?g%YlV6i( zx9$#5I_FSaKdx;xb|q+0*Ty%pg1hzFuox_7?gh5ZkpwAGD#OURw2jnqIJCivnt7Mv z;cWs@swlQ=%=~y&MiX+h&r|^Px4iJ$UL=G)m0C5o2>|NVehDIuk~)#H_jy;Ibn{`~ z@1%t|Y%mvT`QYKE{~7jwiQFGLiUHyJB|Qj|Lvddr>y`l_eRuA-|3u_Zw0TKnIF_yd zXLKL<@=bc)KEuA%c@hvD^VB{V_yV;iGxu-B?zc>xMhMN$?a%|p9``hWq|2Zg)Wn8t zf!ImCjoQ~!RZ>J$7JZsV9?Z63C7>WU&+-D_*6#%Bgj!-j|0XORZj4MnEf7$4dth7f zRY2V*jqp!=qAjUp1Zh?|D~FfSr8^9ZzXj6$_0yX2B>U8}( zL!o%cZ%5-hObV@`%=Cb@XioBDph5rOocXjbs6JB z&KltiRh0|7N=RUV{S_t>~#=f0}hs$#PVEisTi(^ zfR&(iD9AK2lHqumKCOXnDk?z=e83^2whbMH1g4YTS|}k4qjj<_dLDFtT(m-L3s^!L zcK7ta$8$Psk~_Mu9&GiGK>(}Bb~Nwnp_{8*B%W|n zd<90}mNS{_GLiIuprBn>Azt|@l!f*%8k8)8*nfzs{$!!7AhZAWD$15wr~#`01}mC(nTehG5$osGd8=!uvV9Z zG6)pJ`kAf7KfEdisaduu?{q|lJo91F_ar8F#A45;*WLvN+*4EY(K^;<%c%IpZYSf0 zXqM6hI6w{#cw-OM1)+4lv_NRR(>11CPWkJ`;tb4vvBNd<_v zvtYJlL<|!#`FGJOTzld?w(a3;CE(fX2P2xnAB`8F6{$RE!wS_aS2!<2Qv+14y0G`? zLnjJffTNOm#roZUA7ZVl7e%ht8P>-gqDi_$;Ly|daz$aRxY4_e$dio$_tIF_;I`{( zSGP`Mqfw5>W_|~{Ua)K`1RvOzsSbt{->B?KD34XlHgoJA+!SN)STpFMc{DJAC)mm0 z`IE%UG)aS=wguo8eIx}c+At$bL z<(`&`o%A%b{y;?PtFndFR>E?*uX+NpBI?h_nx_aUz(5j1qbFh zNQ3enQBrtKaR1v9f=E~10`%fSDR#yzuU&`2y9+zW63C5v((@Bcm>2Y4((miJB}Ml? zTmu$;a3p4bgDW!J16}&|0pjas72?bh{Gj0oV!N#;pOYhKyS%9z)in>rW1<0ix8tc4 z7~bTAnXI~MawIZLi2g=|x8{`;dVC{hxGY8yC=Yt|X->%Cb)V4Z|7+MiVo@rCqau8Tbi4$SG zPyb1OpCfuJ!4}bzqr7`=#!0!7I+8Ry{oF~=_{pui}Ibfc;3(@|GBTc$W*zE)& zWOl5*&Hnt0d(uku9aR}^_WLg`@We2Oq!G*;RDBGg{YAHGB8SjiWnZF~;BygpGLWF^ z2slUy&t)$jU@m_l6wdqdv~K|cEXhtUvkV@8L=lmWi6$|=2qlr|mXnKEL{Qn_2+hzS zEwQuC$s;c>c$t5bTiAH(qn>9IU$NKwcpi!ht`v0+iK(|!bN!Zw^Ei?=2EyHI1N4gv zSKE)@ieQMZ?_bf^_%=RW3WWb*&#AY#-7gD`f^{wNpZH%oyULHL1qAK|A1Kb@T3`5o zad1gv^%_d=jaKWuq0NTkhu$PZ`0oMIRoq-f!jIYZZdhPEzwz{`Ot33w$?xN$lMF;l zcao%HO4s?_m)Z%CTnp?DzWcAYc{EHN>ywn5@B$#59CpOUMwMo0%Ve{7sf(rYh6j~h zQ`ZMpuhmQq^Nc-V2N-Ci&O8S3nQS|MZn`nuJ(JZilC@Fjxdas03q*6=qz_p}*(PoW z%l3g5PwMkB!JD%?n^Zgkx}oWb;l0{#TM|-d4mBf+KYXW;ueLK|k`5xN4%G#6w3$@c z9zsF2N7Now9J15(T))W6&)4~{wku1+Xi_fqy8`UYM5W$8AOLda&K9&G{&1FmffnoE z|9I=!e?alG$H#&pum~N_;A2^JQ~AohzLEdSSm|9WD8uZ1Wd-DObQIy~0@Xeww;j~+ z%o9i~Nc|L6yFJ{2FZnmFQl|L4hN?JSyQ($gysr0Rb94r3NMAc03?V6=IH@_7pzC@w zSM2F=I{%WZ65)?dd2mw}Z?M~cGGB4wAAc>5GnOEwCw6xUHKw7H`XQs-CEnx&slm%7 zwa#q#`IDKJ4;TovL>SBO9!3C!C4W-KX6hUphCI?GY$Gp;bH>C%jT0Y{Qz2rPjnTL@ zgb(T}9f=b&MH=x}S26@UcSQ%cU8K~Uf6Y?-ucqvog7r|!qG3kZx8fyfJci{`Xw?tn39w( zTzHRb;YHb>LKQlR`?l_m61r^M-#;@J0{#!dIPLH7c&MGuxlqPD>)sW^CzMIfcO5wl zX9_cM-<41SLqFgRlTwfDn#}+>lbFIgu1VaO@rVnr?J1?BTFQ8c+d_t8ki5)l5MzH zxfpW!9P9k`*NE0mB=r~VGGH+Ls&6u4+~3OSJDwhSWefyh#TKYN@_^5(cTVy7vFAyD%>m0vhF@(#7c(JBbo7GVDU<#-MYsLvm z)zHTy!V?(jo2^b1*~_Zmj`=#1HkJl-e`fdj{XEVT0x9UF#~z=pMXFaAqI zF-lkhA2z3D{4S)uXrNANwY-(G$WY1m` z9OjdA8w^RG%Z%1~Zg6v?@VPN(J3sop(&rrBMBZVLYAnn8>lH03l*!}sB)%I&{oPrD zdL=Bl9Rob_y3lkAib;m<`^`Xof8+WkMQ3^|a z`0c}^w3PFJo5BtQ*9Doo#chKqlI!W>yK;hUML8)e9+Z^Ax2JRmNegMm0BvUYwbj0JAU4zsQVgubsnp%(aRiPm`W z*HdFrJou3iFL5SbtFWXx^k;gofvS8s!*LCN1au9;Qi8r2nls^>9dPv?Ew=8f)AnJB z0p8U?G>_taK$g3v8(LHWh=g=0$(thPj(af-D>}uCOlXnXaB~(F$g9LZ6;i}=_r*{p zl(;`x2iCXhlz>p^C~_4h9K(|#M01or)mW~%Sb?X{Bye`R8FhLc%bmFo*jlnky{ZI% z5hZ6DGca$udaE-qdJM^E{5vsx@LvbN)3*)m#0C`+zjud7*BnD`CV36zm|o@tD-_$b z_~mtVCu4_iejXZzJPRN|EclK`?$2Q{zV z$(2-@gv~%EKnak&3lkeND?dO^ULIr*Vq;}e0b01)8an~lnORvmk*KLfoq)zJRv>#Z zV;3MlfXBrWplIp>P6bD>vhpEO10;d=Kqs)%41Zwa36KZ67^`_Y0NDU^#(x1Nkh2Su ziLo=-4YaqgvIo+GQ$#@yo=#R4mM*_*a4<3buJk)wgc%@XY-$5?ceb$t7~7ixWSHfd z0SX{@u+a)Y2eJp404f{DfErK>psp&eq6&~yQB+q_rDp~gR&{l706G0%FQTey z>VJ}q05M?&HE{q?gApL9uB!I?uNu%E+`k1QKtT=c|IGsq{GBc@t|qLett8II@_P*c zHh>$@$=T{R?f>*f1MUp)r!{b@xf96lF9HCarHhLLKMRYyyF0UmtFsF;$jO4)!S=8I z)GV!>0q!6t8vyvz31|!aOBh#sGq9X4mVdy%3;f;`fSdqRK&-!&DbU^-_&Z4g^taOv zEG0MzYgwe5n?wFTE~kHJ{?ob$2)r^qTW?=u_y657V|!O;uYYOt-^(@y z**kw*IlDOjT@eT{x3UHP?%w(Lo>|%dWs?_Hkd_cvRb!F^51u`fJP53gJ+q64%U{vI z>j{g=fk%;t7r@TW4qycjskptFD9FwZ+_f{(Z~4Tmz&g2roIF|npSiZN2f5pO|G#!~ zD|<8Z-^!c0I)tL&EQV{Gb?|43jjM8H^A7*$=DN#6+8#*TwDNeHtUDpFoVh; z1ZGhEgE#>!YJU)Twd(&MJ}{s0KZp}t#Tfh?aI$i?`4a+e!sHJEFU-UVJfuJuTcEki zA3MiC_P@vd9|yQ=(|-^bxR@!(7CaIEaB}{(*xCK*f{hitG_!vo2Us98knQi?{u2T2 z0{EvWI0E=zq};##j;_YG|4M%Wug&~V3bwe)nb1ls>A0&KJT7X)`|{Vxbs-{xNsEY6>fxxp&f{yrXm{9q~V{y;Wx zz#l3ua4mcAvHF)EECT3Hci?2uf4RXca`@v07jrNMUpoJ_tDJ2AXZ?TgJ>>v%IRKsh zx(5FV<^IcN1^U;{f#q>@1%dAslYeal8`%0U4cWj7{aGW|Zv=GvmxNs4aAzxzKRjS= zXIo=u%YT&sQ~zm%3*57dr4#U9dkbEhi#zCF3Een`PcpLYzlJvw?N?SaQg$< z!Bsr|1;I3)f4Tz4dI5i({^t1ahnUjuJN&QfiS^G@?f+hG{=%v*P9Phgrj;4^#qM7r z^2Xqkm>$a*ggt*m%hNrOF0c34@iE2&!Yd4OMnz$`UuEDN`g<{S-li9+X&v`@XSDyS z9wVKP+|=Pj%Ha?!t^bgxaz<2Yl0{OX8=Y5-1L<58Wo2uVF>NHbTxST)GUi=Hrkc`1 zv#Q*pLSl5MN{Xf<>TdvX)EfLA;Z)C>+>WubKfwCfhC_dES}^=8*BgDhdX?T(Lx0Y! zu>s}$-rO*2pT25tPSMwrV8Z-*VSM5d7oJ%*-2%}VCd%;1rGSRfbh=)pr+J0E;D?Wr z677{_#)LjW#Lj!U4#us4A1e^-xZ!C`j=NAXHwifv(2VnqkCyVZZ+|gQB#=WdAXN$J zrIPhJ9oc{5U9PVXH_H37veY1Ks4MLhy)>m zhxX;bf=)(s_)z+8Z3w88dd|1@{ek4bE4K?jaS{LZ_|@V?Be;fQ_KO$DohtC>cK0Y- zMd9anLmG!#_>Kg2k0lMYS_&_rWj1;8ZshR%kj;P7drBNJ3GQV9ff~5)OLVTyP|ZX| zHoNZFu$<$g3Mw@628Ge1abQ|Xoz}F}$Et0=MI`44RZg#Ne-}CwC=Cri-H>?Z{9Lki zJ&9E{HZM=C9aE{_*md+Vj+p}s_~C%C7aAw06et&`0l`5`Q1 z`w|Luu)GP*K2oQRmXH{uz+q1OE=sMH7;%5~D#Zb6y<=jSuUYCsU)%H?!8gfm5NM9C zv}A{#U2IpDaovhS_Q}!by_%@5;y16ULIDGgvgMgba@u*V*^jExcs{086h-muP&1cn zpJR)VE`2b_56v5~n&qC$c$HPE<5I1Alcs2f2s8aJce0FnhM&Vl-zrR_GC^Da#I5k zq4{?Cc=V0RGspyKo{ns&HkJ$2CMPV5B@#!DrHG5g)_-bAsGL47m6D?@Yp3OOia(r+ zT#R4JQ1`DQWx!PYdI;K~WJ3@ypk8W>EOoYY@8blO^O)xh@taSTZbPo49ehFMr$sQs7ATm>!g1YV)Y8;}NZdNPapWt{ z|@@2|-hjeu>iR)bWs{j91F+_z^&rQ=;cILO-d3 z!v#JL8Z>pD(K@RuN<$!W(m;5?{TMFxL1JB~+{5PH>xP`c;!Q+$_UbU+a<{qrIHE2E zHsN{b=rd_7(Wx#~w|xSg10&Z9;mjG@BXpk#UwYMt!{~mD<2H z%&|he$#_QEv$)`8Jtcf&;%$x|2p-ihAisck=fR^Xu|wr4<|yF14kdCV61VOmoceWcwQmz`~G$F?P^aE|-h669z z8-tS(r*=M#^NmcBsk)(d9KPHR*F5)}FI)F%hBYnT<-W*nwAp{Wb4igf$^qf(#3W<+ zx3gKI00g#e`t!AK-jAQ!`;SQ16=k}0ygZ%PQ2DLsM;+|wNAD%_;Z+FD>dUT6F0drk za^5HnxxmjpmR-uc>~SVuE9%iD6dW5Qgp8nGYh+tJndvvrAhn@~O+a=$pZOr>0Y!U-JsO zqa>$42MDUk%-Gco!p80go4M}YCds^f#zNg^kVailDsraABT#K0X8tW|9$fDJU?qlPzn<5R7AcM39-U z@F5mAM$7Kv>t8egUs+(vA6FqqZXP!U!{&eH=sZiWrA}^jGvG(dpwfmY^EFYBHBUWW zIB=yokK54ZLYW#ue?=pk$IO_%+00CytyN65`O@i6?FKuRhA#5}>%WQnE>z)Wuq>r_ zX!yDt)B1ptR8WeFzvxK+9GIEBNsPKzEE$ZAJV)OY^rYJ1CFrohL)L#WekEE0+-iT& z$03{3?6gS{&Sw&-wC#!Z4Rn6f%45!?7q_RiNDjrPMg427dX4o}h&TFC)rr>2Sz*|C zHtjuOiu1P{>H!YIXGLxTjzL-{7&1XJey6vLs29sb2d(E}EC*N*q#nGs=w+SGYx6=P zrJtQ3=O?%r0zCa2kR52|zU+6;rFValt(Uxe30>JXx_{hP>!J0q2}h=O@^c9JRMEnx zRm^X_V2^m@IF95NCMj&qUle3Y8cR9A(&EI332pJ3l=PX{P8;$Jf%655Kp@UZ*dXVb zk?)pt9QcbBWk6S#wOR6-Cz|J6U)8`uyFYneZVTYW-^jWw(_tJXSML3p;=zB2(3r9$ zw!@p~puvw=x6!Pa8xPpbWvjp#XFs#o6wb`H8autY$psN+xlHZ9dW@*3i99r~Y*-)# zg95Nko?0+pMzNM zH>l!YfP?EEzrMavI}jj>Bo}|!d@>}aIf^_lZ0~w+4CK&bW)d&+U*+7uSjl^Rw<<3? zJxrw5@;SMUI%AD~hsEhde@wF;d!TMoi=bNB_6&{K>+%~x*fJurBImJfj1?0;-Wp>s z(GsnR$h(VWE2k_9M;Svj-)6yGOq_y^nyy@Xj&nixD8fM0iLhLA)IonZwbRD-!>r}GNXX;}^V@mKcW^h=%>Dk77jK)(Q70~CJPXwYUA&n_~xEH(Z;`Up{yVe=UNoLDu*>@VX=3ybzI-&bnCh=-hU44Sg)j zNKb37aH7asNJ9vbRtrTOh5aS#{atG+3iHS2GpmQ$@7(LFrRN9N$QL=?^um z+Qr@K44NHHWfD>Js}62Zf>&5x&wSyyEl{&M3&B-gMS zuy`o+##}PZuiL8zQk~(cmugFN#5lF&HS*{W9U-FWll@6aG`$^ zE_Edq+KW^ajg6ae?>q7v*Gd%Lp9p_}Ib2HW7cNYCW7TNg<0qwJ!k*{;-D67K6OqUM zp)V?1|A7fYaxd}>m72=eQ?A06&a9(% z+PG44?2Lo_d5sHGw;^=Qw-TknyX_?puspDU#gUH|!=<(yaSM;P)`&uN2PJN@lKr|U z#|D}-?L>c`!P7UM0om?cJY@St*Ai50+JS3%+T{FrVV0QHX>g(W(GioqG>A0~B(AYB z5$_F= zD5QS@62p*XvQH9tQX-Q2-Yod&wl3=z?>J{In&|vI!FQDPD(p%Ts@2b$jWl?=;MiV# z$EHh@|BfIp2DK)}j+?_d&%;QSQdicjPKi*jiiNs+J?TE?HUFJfAfj&C<43e$HWwOt zBQs}GR58&PJ>uV`bC@BM#@6GxMnXCqb>rbltjom-2kGV(F(LN|)+ zLzA7+i*Imj5*3GOoue`h)L?(8N!P|qyqM$cCOHh2-SGSP7#0=$821a;F~d!%dhL8p zmE3F*+*no{t`ljCi0yd!(blERTe%}ezMR)qayaZB>Q8%ML251-pJYftb6MU}Y|?-G z(`O>Zm(2F%u$Yl1cI3sq4|h9ZRmFv0<|b*&Z`3Th^ERorn>zS5r#Re*q6((lCQ_hK zkPsfnpjtJYtdo!Bv7y-rWthe!t3n&WV( zjKY8$FE~D^W>Z33bL~8b{@x(gY@C0#s!umF$#>8r4m+af4L^w0!X^4*{1t1^e4v^R zXfAjKiL2xF6FkQ~Dz$oec%3Ccc=GbS3E6a7wUpc_mS`!5jR}o|rItGF7m1DX!M`?B zHG$~IZ37-+gwYjGi|@~~cpS;^n5YHBP(~Ns94)S9Cd9XSzocD;Um0)iw7q{9^~fkj zO>JLzh327}U(E@4jjf#-C9zD1MjS?jzY;Lm>R>Vtrbh^{sS}}Ghsc^(4_-%PChy)I zqGEV}*tTK8{aQkd_|3q(!(PNgfBAVU=r-Srj+EwB`Sb}R`z`NJ6l_Dt^^q*q?oirt zo*lpEH1n2#=qy#+hub(KqJe*k^+oYU$AE-5D}G~}^@z44$(zO+FX}$1jl*`8L5ZeM z!Hx0Nb2Mt~icP<4F3Nv75Px>`Ezo_d0ZS5Ou3BZX+{(XwkvJc29gMGto;+5N11!rB z*C(yjqplc)+lx+fQpM11DvMm{)4Mwmaq)lAg3|W(vw>-1N+y}%&!w1=D#J$FBswI~z}~!q&vZWEF&d zY^m^qp<~3M!5G=ZVFW)$9ykgq`)wVPx@g>$UVl-L+uieOcFBLz+7EeA<~~PIT{C|~ zrLIgtA9oot?9vMQwp1!!B5k7~g-8HYbmgTm7uelR08|8!x1qM@4Rwr&XfZYU*%GQp zQ9X|}o@5`6^(p?4d@x-6;G9Q>xG=nNTFs`-1Dh=Uz?=Strwv^_dLH*Fx3H+xCg}2f?9g&KGL?cU! zVzUP4J@S7lUMK!r9*dPQOsNn2N%&POqUAUlzoJpyJ9|V z{WL3gtaefTK=&Sbr}mN=(l4)NC#6F@Rio0SJ>-8Arq8?1cA2z+m2NdbD$(rrdljOI zH`=US@93K8#aCrg)G-7HIbHbRjQXXZuH`leiIyRMRx1ryrltx>3oh&`=M8av=9m3~ zl{q#+^}aaNL~fpj+SrnV(b-4$$Bh+Bdoh{LC$7Sb+6Hw!MH^H^Uzv9lKb&l zl6`-_Pb}5fnZU7R;#AAyB8Pgtl6?tEsp8n@&8TO7QNJ)Bl$NUBOq?uz78Q^{*X#u$ z>8^ik(x&ZzAhlkt4(qget3Y=-qHey|25l?c(o}1+rC4Yc07ciidoT}!MEv&iR~$#wMOJA@#3m!%KfjJKs<{^z=fZ`P_s~6*6jbgJ>6W_DmRe zVaHu(Jdc8{i7Tl?^13(n7fnmdirN?YNaa zQ+dH4p-YFzoBy7*1w8btX}`vMWP{$PPVIzXh6>u3>-w0y$S`V}tgRL=ef`bU18#qq zL|Aont`bFN2}mOL;dgnM}yYsqYHO%-)4QVA` z#3yQnFs%$zxi%Yb69&i}28HQ3>Y;y-uy}N?;l_pWRT#}P87;*Ws>^z_vh&JGuc1ee z99OnS2(LU=#+%~7C?RS+!6cZ_w2cJEP_40j2FsTfy<`D+;ly!QnU;qd^oLcvz(dva z;ykz93qcb2$1LM-9n2$sLv&dEK-&NvN_g^pJSEIvi-Ibr6up$BiR~flgzJAXMoSPT zllI3;lw6-G$4Kn^wo*SRxOt|aStuqbtmP_PT7A_j4#>-#!6m4NH%%rdyK1-*DDC%Y z_Q3Knndr)>=eZU#mB>t7RB0y6*Io7E;W0)cSh|P&=&8JqTirKem=tvna!>FfkIVBb zjK7#EJHpzMdksx!k|U#qN<@DqeYl>siqnj_`~{RF{8#0n?}S)Z2aLGa4QJ{K_@+D0 zEE2;ev_N0!)5Q-e*J8zg9p9N)b*zv3792$lBW!R1_b->)a6SUHq_En0b`V@fHLjjZ za?G*WfNWI7_;XgfkH!#`-)J21TxyIWrrcL19AsF0tE`X}ms}n!7vq1{aX$F9rObtwmi+8FfQr>7s?C#ak`8LMObU;SY&o-!5QB310YOWw zw>|PsK(H1^85ex4PDIDk)YB_FiEN1XBbLw!B#p5GY12^2?1 zzQPk8UUupip6{#Yrm_Fl2y3OwEG-%C*LYc0D@RCoW^!)0X{&!42dirwOqAc}`L-=> z>csg8pSS>qnWDGfNw;VuZNgV;%`rQ!X(x%>)_2W8oZ$yB`YY+D3jH`m1c3 z!#IGx%n`Ce+iNIVGwo>Ox(bYb&XkU;Kxu+wVM|eatVVwpiH1VC{?H|!Cx<25btTVk z5tHTt@L6Q9b>-%WX*jt;E@Gzx{9L=;@zR_$re~-H27N3uzM7AER((0nSDMOlRZa`> z&~=8+Hb0hUtS_zab&M9V1+>sbO>=r$2E?JTl}1iGiZRB&C2#ok>n#2~D>)9m@Y)Q0 zymWLWdtHC5qtoS{Bj3xf*>^0-NWVTB@6>J&VmN<8M6Ze6it?~9z{-zQv4Ko#Ta<-I z3mbeo4~YU^_GMy3b1zLyZqDIT~y9ovl3ydt4$74%emB;fV zku!h8YTeG{Veokg5L7F3&1ck~)jREqg#IerpF>Z73KtDccsL%bm% zsR|jqe@rE92nZ?3)mbyW)M+hR(7D!lL3DqU9KM|XEKc(l;l0QCYgEo<*||mwM64>7 z(Zu@cOb#>VT(3-uu}+93`JqDryvV}v2|-vl64|exp=iwm>wTf$Nke~A z9HFL{3{9-dWC1DR+kQqruXxCEL3twDP?;4W z5*-woAt79LF^JcS+|gl%${bpA|Z7n^2JkpC-N|>;n5t-Qwm_ z+)>WmT!F6PU!yd%E#;T}VDXu&qo`FDtc!k7&GgZ(ltp{WCn7w zY^a8t?tNS8fHaVcl03qNe5ZehI!#0`Tb)*l(k$I7?O(Q;ETeyvfn6&DpTS3I_JaKL zHL_GYt329OB~ZHi+0PG&^0PK=^TW|W_)WT6edd-^!nhTOi;{zlSAYfsXktBA2$wmUn;HxTeB*>kUO# zKIL!{{iZOgF!8GE(D3Il#Y)Ypmt)VD(W%TZrOZ!FMCudWxuO}e;Y#M+#cxQ2yr|`<#C>4OkQQM`>V<&7xmMyJ>HYsgU>FmQiLZt=ZVNb=Cd%YD zgx%M9uvilojYqRO_DJ}Y$QOPHJthpJ=4rS>v9|@7#izL!IS_w{y=?81EqrQ$RF*og zl*nHqYbeZ%PoNs zIgpw`sx9KekT@)ch%Vpu95smT#g(cWls8R=svi#Nu#1uqUF?Z{~EEF27B@8;;{ zzM)X)iAV&V-mGZzp8M~<38><}HQt_s&eQRmEl;m&{WxnYaEPT||As1T;u=R`G8I|a zFK2FF3n?2F=F(QU>BfmX+!O=dK4ufa@ipWog1(oORyWF)1NCmt=iCfJarDMr{YTL+ zBqkwgP;q~^fVslFjV)ImU2;oaAa{0`x_{k8d&GOq*mABKn#B-&#D((~ChdcIbH?Y& z8vJ6x1-9vQf8%BDzAm!jd8v=4gP;snsBCgUJ5oG0WNeF-w>V~BcQPKz7jdA)+QgK{ z%Nlq2HfWHEdMVj?7v5k)yMFj_|E^>hu@MLD>z#k-V3Y#;*x5*1&o02`ZPockfN!DH z%0*lE=Qn95y$A3P!6MzrJp?Zkch=eg9qsRUlJnYe@43x;RueBz)JCBqDW*MKujy!D_963On6?e=2 zI(UE6CV+azKYAr6SE&E#3t&(y5Zk+|{c~L2)VB?P5zM$cR+uZx6qj`!3lP27hP{nx z4$t9U_I%lSf)EOa#9m^rstvF}>;tz%!*1jtbXb{a2eb_~1QQ@*MI_^_M=1GF>iLHX zH;>7a^zd$0C9egyBuEc=ZkllFOBYuUCuo21nFKxy!}3D&kv3#J-rSTXwPkccrH0sU zA$yoti3$ItgG*1iK3S#!h@e|h`OHD-6a*zJ7)&eMDI zMd7a6o`eQ0E60Z|a7UzfLC2i?58!${b)z8j$Q2Red0J}m;-FkxsX7pvm+Ff~N?%Xe!6qJSvJ0^bzQFQoG zNDPK3>sl0|^}Bs>hR^_a$;zJ0?tZf@!x0QIj_PBN%68+T!2tXeHf4we!W5bN&m6FF zmNNNvyWiVySa2efL`?E=Z7b5de*udm;@%x3dlZdn)AqkzLJlFc@xi3@c4a=a5Zocf}3ee4*{1kb2 zvk6&>Im$CH^5Lp@`7?i~$`jr%B({7BCFG|H0&!*7o1FtBDqTK4VFMn-#_y66KW$*i zy2_9D7Sj9%%|L2CL@n!TW8HFx&qQ|4>_}h9Y-xOjnpTAsryM?0L=ver2s}3Er*yM- zt+aBwAJB_1vemh{2v=}TY@Q63@u%Azl=VBQUNKoI#HZK1sq;t2^;NrZJ#838(<^3Y#?G+F7 zf>!SxZkR&b?!8Pq2ca!<9bcGpw>B^TxdR%^}x;Av*r!kXZy zJE|;5W?T6|fNaTAA2$bQ{W3cYc{3@BsB=iAC%Q$f#cPTs|4>NC5+fU*9G*x?v_N)` z48LiDplZ}{Q|?PQ2lJBpG}R@FpAY2Z68u?8(|TyPams%cY!HNNvvQrc$TB?c<>k|< zc*3sOt+~E|jr=nPHK|pnp~$<)Orkp5y12@$RzgIj39*Q^5HT}LxJ<29tZ>?(tLaJ4 z5}8{V^s#gMq|RBwJS{AT@wF959PeNO(s*W(`Dw0bUgWw&9en=y-p{2V^JOAUH{BY2`(k# zjKSzY%Quwc?rNx6#iI;+a{N3~XhxrsOmSfH^!25Ua)!C*rO*U4Af(7%&uT1F&IKcN z>V>sU$BFt(bw6DlVtz3|^9bMy=OrnL?%I0uQH+0TQOdtvz&F+Veo_E2Tk@uHO>k`@ zZyL5P^c!=cPu#+L0-0elKy+}-Qw6`6VL^b-k3U_Q4LfkLeS+QQ9{BLWxu8Iq33`8F$huXD2tn0$xYkz?U%Hlw46h$3YzwV zcKBq;^0lR&a;cQCPqSf?#K);#2W%sZY;|^5oMMvsvj?Z*g%>%UI$`rRDj><6xhM|_ zaz#kPl(jbkModmtbBMh2(GR5^%;TLmPd|TixUG^2SUFab>`U6s+^2rFt8$yFgLYzG zrP?w+bhk(IN$U6!TjHpRhYa~RKt1q@h)pcA^IZiv=#k*jCq9uO&)KXRZt5jL=xo}o z5fAdiwMID$6B(B( zRP;;tSxYM5=MYgCciPKOr(I>t@0}ZpaFaEfoRKc>ow_d*2p2~U7V*Tc&79eoANa1* z-4+dz?DCW%YN?bmDyQmnML<>S0omq&(Ww4LWbcP@(uZf>xzlec7iHg(AR@%jN$k)AEgrAU$Q_zR`>e~pQt-USj@Ist9L;2|C$iJ&@ z&fFHcs4+#PC#DkNBy@^Xq9_H!o@99?nI5>>5Q^3IPFx%fs8k6cwo$#r&x=wy6ein? zxwjdAxGi+<@15`9oEzkd+VjD(G(Pcy%!U4;^-+ERCB}+f@9GY<+KlDPk4%3fn?BD= zN*K`@EUd*J9h{VKTaRV4f&8zJCK@B<^sYDi)#Q{Ux+$d`G`n;aR}w=w7!@K5n-4*h z4eZl**wrZt$b8bBjm~2E(}qlLL>5M7)=L!LV;?F%Y~f?vG0G8T#D{~5m~1v z7{~P~88~o;LH0zfqB+Fm4U46gMLfmn{Yo;d8bobCOG3>_+A>{HKNVyFp!tZ&%##Iy z$RPY6fvWgcqs@U+zD1T@$yaPYTv&Mefuxi*t znoaY}(T#ZrHL#7>nooZ=4~^myFG0zAR$11Zz(cK2G+W#I{V#N# zX09RE{a^8lyHz?qYxy!tRYk%vT%smF7w< zGKn#)ZF{kOPPS$KdB3V$7LLqYULjS5n=$T9`z60e_`y{WG-$-ET^gpdoceqPj;PT& zT}8zFgI2zFaO!{fjc18mb7xv}Uvh zo<)wQFfj3_Co(dXtfux$c}&I8U8W$vZi-OoxomE(YX6- zhP&jhRdVG%zjrF)(gk_9`+_HLyO3dCy=-i-^&U(TX^oas73?ZFJ(6RVU6ohc^ftr{ zhuVhJXdD=^vL-c&OtuTNX)bo`@Aor5gnoxJY(b+lC1Iem$uMacNpz}?qoF0?yZ%|d z{W-v_Fj0S}N`Xj6H~u+$J2SvRt&|`M^9Q;NM6p-#{->d#Bsud-KX9+EmXU{n-M@IbDW+!_J(bXNzJ}Buu5aP~ z%5DtG$oy6{_jGPcSc~p?=y75i3{M$Q>xF;G-tD9{zx+^MNfKkNeI|a2cwmsPZ1X@v z!EA(0n^7TUPUZ11NNK&o?IllBb)AMPN&O3&s35`w;t2~^1eM_m=$QESs)!TeSs1n@ zwmGH4s~TpzFO=r1`Wu*C?8)8R#yf{9H?^=#xa0n=Tq+Ny((kcJ#RRIoMir#kXY7Bh z0at6cUId#B4ujZ|rwRu!9bj+-ec zIw>VgV(F?&KvyJG#qbCFQ53)P+NXUTz9>iVj`jJ4!~;FYCTHbOY+v8n%6T|>9msyH zHdo09soC~e&ay+-eJ88kuv;nj`n9`#aQt4yNa0h(HgZ=kv%2duY2A?K&SHNXCWb@B zB86s=6Hk>hIJWWLG~8hxJUG-|dVhG`bO< zm0{YDAee{{oxLh{2glB-3{7>SDjAq|pm3y<)CV7jM(q?fb`e{vMm5Ly+_~Hui<_`B zui1nbJ4hcdP|nUmRu6RNM67=}Wc5Iuobp~BkaYKGENV_V)_3jjK|O|K5^6$gH`~O! zlc=A|Gt~NGu@52i$C&wd%QW_f(Eb{n!P*p;iZPkK(9%-x`ko?M5_vHP1vw9gdDV0h z2qmDK$0hdyAZ5Ox^^8j9y<7Y=*eYm{nb~K)F8G^#S@ZKF3K15pxo`U&HJt2=j=eq+3>tyQmbwbQ>OdgSeXeL}3`6}MR> zNmW1lM?O{@gE1I@bBhEr?a<>h9 zTn8sok{_1G`HfPQo)3Seqgo;XccB&4{L!Rk-;%jX#HX^MG9o+^$l0|9I@|c@sUCNW zeGOZ`K_DGSA%os&P!pGAOdwq)SCZBhq^CE_C`~uu&!W z>(dV^yJ(|UWBe(eX@6@A=!Gr2c9QG-JNh5;Y>BR$Z)jd52F!oxZIc0&Mg_GmsU>+l zW`){U(q)tWA+2uB-SZ8+9m>x}dHoZnHi1^AQthFYCOw_=798^<%blOxO)#Ir_3 z%0E<+*Ddjq&g6fn72zX`s5MS1Xy02_iQa{A_xwT7=6b$Hr?BqfIY4-(qia}DUUS!T zQI5qKFuEN+xaN8lqi%NoNORPhDz>W9*V4tVVmjL2X;_vizc&jb)T_OqL$qB=YPxzk zUj##~-=B$o(7_>MVt+>(GjpDq)et&>4U<7{2K7YOT7rN1%%iJ^?I`lzp>M89%-#0e zt>Ha38jC8%@i!Py=!^j?l$tDmj*648ysNV!RYMQ<5Z-nz>6PTr1|$f31QSmnE9_14 zInCxrzD1os**vvpLn2!#Ew8tzAMu~f#3G+>EX#v!ge~jp^x8t zNtJulQPII9W$3_1sxvntr>$z*hnJSO&b3AuwCT8+EUYdvFkPQ+p$x%zn>v>DItoVHCE9#?pU-vx`b~)i{`-9?z>7mf%p|_8DRI zq_E|E!#xnQxAt7u)5Fo(>?N5?`qgxJ_}b`1pfwb=^l?{n0f~BTqkH5Nd1QaGN~~j$ zuBZMawc-puSG`o`1hN=U%v=-KOs{Ju>oFtVzAd%Q=NYjH;p88SItd0!YgP;`&YVsz z^+`RHHvWcX~ZgvpBNrh7a2_%v67Q z)m|>?29ga#!}yuBmgi7w7s*Ov{R{I<&L1)-Qcc))qC_&!(DVWsk;GZyE${u4akx+j z+a}I{_HP%}MK&e7y*p+A2yOb&&-teO7#H*$s!b;`jPQG*I{z zYv^PbP;;j&eBh|@(BJ2toS0mE$G3l_?47<~bWe2;f^Jdxyh@n$u`hQna)dI3M@ldX zaG6h6*;^$O&P(6-Hq3$)B58#-!i+b^Ly6X85dtO=!kxh|((BzzrF*=L^d7y{{w;dD zfT(d>|MlA}SuO@41v&@g zl;SIHK78i;uzZ-5727%^CnKCmX>TnJ?~wfmA`4XTK0%2^o*Nn}s5f(oPg$D*H}e*t z{h5uEY`#Z3e=UQChW>gR`JS8i67sf0_Sd%vxmADS%c}P!%W_98r9~0fepEIi@m%L4ra62_6v+SF&iYaj zg+I?!tJU}w7cdisLy32kPXmoPysOlW_-p>~20uM!e$h<<5{jVyN6Uy@5w9lRU`o!L zVAo{M0!@aK<4L=1RZn{W3tY?RP-CVDPvmTnB9ndNXo=2hqq(;yMi+k+gIGn)B=c?= ztxjd+O6T%&cSax4X>{+Bkp>hezH3~(IWB+!9}9CJ!Rk|)(i_;Z>d0*o*SP!ANkR4! z!tgBNG4$ueVmRtLpFC17$Zq5t=x^^z(B+JE$hSX~;;_^raTK(eZENiZ ztL>YJhE9sHqKJO0E>t8Rnk*RV;_Xnau|slQgCI`I7||GLy73+3$>#miui=m&X1dK8 zIIq4BS)}z4CTOP<&ImsWBrYok;2he_K5S+ltV{1XehA_}=cb76diQiD z&_n)dWx-SZ(hecDrU5&%TrL^H0968V5#o(7{gWeiELYL7)5eVF8ufa@97pwexc8$T z>KhXw+2Fbg6%K#$gBF@W55r)D4V(vUi^z;aB77iXNUW4e8P&oYN$vNU!%C`BQjgX-e{E7Bi8XME4lgsXpTs)m#gkSiz$7XlH3qs28z6yQh0Q_W|hr0+A?RFIxXzxf^Vk~8TJX=X52ye`K)k=yK+pQ_ppjN8kj<@w-w`_7%wb(FFZRMF zV}a|)oX%Z|I6RkszQ(kKN>LH34ebTnn<3J-gj!@m=N30A0y+_oWn}P`$ATn75gETx z5rU}eFzNM6Trz%7Wmx%iB@%)ixB>BLG}-RQr0Oi2T0tm1Yt8@H#yK@t0w_p0wr$(C zZQD4pIk9cq6Wg|JOl;duc5AEdKHdM&58Yk;HQ}k-Xgo}RulWl)#Bq-TT1(N!Ozi=YUPfRG+k2rSF^v+En;WE_84%;v6g&J%{}u-cc^d;vkRd8pEQQ zJob$Em3A#zt;GtPf*y+vRg~J4+eF{o2930RNA}44P0))8Q0sqIyop5J!$eGsBixp# zJmZUqO3=K27aE<(vU&FAsgi8OI0zxZ5MI{Q2ieu}LND*(_>Llk8Fj6(lB`W#c>$km znHNtdAovwd5p!l?Q`7HE!Ew1Augm@MxMdJ&s@csI3dTH}b>9YsAd|Wszuh}FXdR18 z!M~0GnBGB^jw8{93oVnuw)<@vyc&DsG3OnH$ZSM^?y^VI?M>F{4oP(pK$QHa8JT{m z6MunObD3!X%Ue4-<3cS1G-57T&`LUdSW2P_$;qzNWo5*E&UlcOxi~J|zl9k*Og4wr zEVSw=53GgWR%0L+cAPbUZf1??0F8u$EHSK zizZ1OwIB}>hc$ACYik*fZQ*IfA0ou)CoR$I8k)uXyGZ=0`vT2M8tgQgE$hIXf<*Zl zE2GgOM;)jD(&2iy_M^92!>tce)0FRacmt#@;lDBhA{*@;S;hhv~!XdFib2HKxLv z${9hoWDgZ)2HRn@I(`;h2pX>mR#MdT8DwC;JAinR&R;Mb^h*tJo}(CzIlW{9E(_~} zRp<-U?LTok(Y*fW91)7satM7(p>H!Hbd*5tyOYe z{tuzDzTlX2ETRN>2*g1P=)+DFyr21+UNd=Q&ViVD2pCK_mt$=X^o!i=@Vm$cL-~ zb46nN%FKODVxX40IG>#MDYLN^rZ5SZ$5wk0>zW0vudo5LVA4elt4bW@(O_xq?j43G zf-F2p`dibM)6&;Bt?WG4XG=KGW7Ec*6-5Yra}%g~+P(r-L1Xafww{0rJW)WmIYCVq zD)E8cJlgx6U$1wJKB#QKh;dzifTX`l3m@@zcCygeU=O|75!3S@X}UShYRr)3h~a!+ zm$S10ql0!pS(t4Z$=_KL-vxM!P9&-g)O9pqg-mM~za1998R&eZ2*=zOQ^gNBJX;7e zQ$r}O8$2HrYzTR~hMO_hy#OmW?x!pB&k?%DOIirw&^iWnXI$Zoa z{Z9HFP-LKo>(X2>Gsxy#d1W_?p)2=MN$Bm|a_#QNQ&&&C>j| zH#@u)m+?Ga-rZA(7oU1`j|4RLBc|^TpMrp)`0&`b&@=qo)h-u3x%B3%-0`vOj&#m;R+Q(p&jU=jF;*Q=>1?+wb$WN%NZ_wRTJEnAn8VPH(qQx z>JDaq?yqeVE7|XA0pP#Bg_*CWSS(m#l++|&PEdc{1Rq@J;W5+OUJ?ca#<#x>)jCTx zN8K{NU2oD@7E9$jsdTrgpu`t@4+O|qRI^vIL4skB#ZZFO#YCPObORK-UV)Yu`AWj& z@;-zY;Dia^;;(De2pXJl48f8g+Hj}t?1$HX6j4+bAd$P|E*#A~Uo>2`;(ye)s;r9h z#WEpmW;RzV;A_4IuvjP969d12z2ZNM8$w1sUS|&du@QwLB5Xwa`id$UQ;$4F$jRBl zqWgQ6s*QOuA*%TvLF5;bK$S~NvS*+0%Y|_TLh7>An>ncReo@fBJ8UcU<55_ zUutQJq#~zusF6^cjZU8W?u@A17gDAH(dt_5HMSDfPJSd-);kvS+*|h?x<}%utVF&u z=WFP>B>pP{nb(YvMgjg`w-F%@nG8EWK&yU5+_CM8l(kxQVB+^fmkre&VfA=_kOg}X zh~r#Mm_JP_42ZxdaUSTfB5YZq6wB;J1&7Zl7ijjaOS8i<6zaA1mxuZL{AbZYKQG z@Nwb6=4qnla=t>W(AhDV`N3R&_fB3FPu!@p&;U}U$id()L%V@!i%TYA&qTi^23in) zX^kt2+BASQ*zL_DPR2~jq~?JKUWPx(K;>$)52%}Zdp-k?KnLg(2mBkz^)@tjr4Te( zI$1P%513&Qnw)F0$s5S}`3nik{-O&m;%CPMEA0DB$BG+5bNykY1K^T>V^m2CeK&j1 z&x7TH?)i)3sdGTVJ!dGarI!a2&{$ab+J}134&t#IiwAlZU%e#K(X*yoSQCi?r1HFCM2_S-_Sj5nv-0SGvV|HY)7^OrtY3YbQ)elD-r z7C>*M!pT}-ME&rl|J!MQE@}QICcvX6N^Ja~-WP6n$MzumE^Fn|=R03;<&09vN=;glG<46y0gKiFD9r7qx)I8Xey z=Or&<%ox`Qvirs|d*-z|aoi9}dUkHT)1xy}tiIOWV>$*e&=z2SOA$T{P#bOl(FE{G zYE0o92hD9(O$Eqb4&mRvy6N_qk#sTto@hd3aYi*jptjzBHU>ziYH5>JUE@`^%e~X++VygMb9bcE1omD?p~1{gxak>H`tajB2AiTk&C1g1ITY+TzlSfOJC#}09C}pK_%=h!o31a z0#GCd_3^KNp@Xpg1oI`s|EUkf2i!~>ope831$)(Qb?aAmsLlkqz9qJ7FJ6vEN zfAS)b|I;id%&K1Mx|L_KNT;2{L@`(tTOwScD}{?L;Vb-*k{reY|5QVq1g*x8;iXsn zN$W%J+wG9NstKq#%>lti({Ir-W z5J*jL;p+wV^i&wF!VCdh4$=8S^tytG+5EUWOq#}?SV@k~#5U4WSGpWrQhW9*RcjCE zNCjhrjw@!wh zuGo=(jMF{qqg>p?iNjZIXoN}qKkgfPb@S&J|E$zC#|8P$cX*!u8a+T7$tj{AnpK>o z)ZYAMjqfe{Lx_Ae*pPX2w4tg_GLxOxb136B&T~(es=dC2rnYR{Xo*veVI}xNp9fmS zb6|sqv%YAIti1yUQSNfdg19y;o=E0Q?=In%*lQU{P6{*^hVnJHc@j|TAe*f4 z@#+buz-C4&G1!`OEh~Agy-EFf=!f}#|K(D@7>IglY5#{DdfnS#)Ry%5LA<#v|QL5w!J>;~8-4W(A67 zIJC}W9^0^P?fN?5JAVaa_Vos$G-9!M_QaewZCMtKJq_hMj8CSaw$ZhBZ=P zf%-3hgD3!@mXouQ!igHVG+N^k0&#s+jvBbO=nuxclM^$kr1 zTDVgPU=|8yc8ej2byJ1o`~;Rf!N|-^N)E@;qswm&aZ)770GU7P^EUG5K)~!1hflBw zjS{FSe(h)k%~HElId?S(Z}T9B;>r zn3Vwu&#~>Djmv)rHk_k6a|oq-f69jz=yZ}$K>$P1I;%fb52zQ6e(;|k9l{z^$SHtm zgVZio!CMLyL3;!E$l9}}T7o+%=KlMKZv1ce?ACRBC2z_yvnmF{dWYeELR`8mcL}fl z%-s4Xanaj4ocJ5vj0wuaijhmS1l*|2$EdiivV?tJN!#g&t7+T5y`HVwA5YhUe^LG#mchRGp6a+7-x)xBRe#IrG^1y4XD|x8Xh&6ez<_rZbhA1JG(}07lDSFG=nG|f!WI^ z$%GaE&(J@BYF;!cz;~L>7NxqTkyoYlO4cCZQ20%%YIQ2B7sFme1T%8}qC8OeQw?cQ z-FbM&JXu@GXye@=LGe_R0&0}R2Zk3q)-tICn%~tuD))eBKeG&fRNwXPTu>{ujGQHr zXJ_*XGfKpW506>Z+&ze&1qZzXCqGNVF3T_t{% zPc*|fQ9hLwN^br2I4Y-6KwwAA%b}D5!KjZ(5x{5Pwhd+mxeL9S0X*Z8Q8|*_*eU=s z+*z258$?s1#eqJ5T8HSg%o@$(;vDBnh63B&5YF&XU1l|k=;sVAnTkmBGv_g&bN@*s z=-YN%#l|Z1r*3*CXl~G!Akz9QE{5vaT!=lYP3XGF>i7Xscsris0pzFJmzgN7GV1jDdL-*x95{%-bfXiQ zRy7|j9#E2h*?yNx;L_qIh;#+G4s8fizQhr&v!mT#!i(v?weGh~mL50y)flM?6pmKPP|K?wMC}S-aHezTZ>e==olqGo)I^+9D(GaPrAS4^)3`}m`eYLr zluvoVl(?=LQ>$J*d+6iY1Sa1N?c~7{;i~pfiSW#SSYVd6jyGr1nfgjb6AgR?KPdQ^ zEpB8l|Mn0@2b+aeib|;&!4p>B+)3E2xXw7dqW&=j-W6~bl_kT+Tv7Ib!+KnUe1r5j zw4OG7f@h!&kWQLNhH{ol(;7#hw?&Z_%|I^N^o?pn$Dug-c191phHXumjO)`}Q@aaR zqJE)&SWm|aP{}MU?bf^`qcNvrXekX#Kx>I=fwbyB)v?b8v^2`UATf%XnY!y7P(p9Px^SqPUv!2 z_8?D>_pG|g4=M-MDt4zVn@prI;_In?YJS5ZNp1~BG+7LiM9^j_{78;ugMB)Hlm0sJOYT^hqw z-xY=GxpE8z{pHt&`o;4WHiq1FpvKH~ZD-)J(yr80DhUa1-@dChLj5ylm5-=VGWV0% z>_wAV)V*zoCx)8SmQ_2BEgOd)vcE8YU|1$pfbda2knL@2&HJ8|=5vvOQEu~lF-U4- zjjY=xr`Tvm5ig^z{OYN_l9+*B5fypesq(w_fJ)o6`YV+uHo^`2Hw*sQ83n|VG(~>l zHzxY;Nt7=?1Pi`hJKU=K#$*FCP9-fjtkQ88TEV0C@PKnFn(rnG6}`wnN4}>no~*syG@l#ZwP+e|T!s0KM$K^# z75Cy0xv_AP zYqw+v|%l6uZqlK-6 zW*L{HImK}muIisj;Xx@^D4Sb0x6!qW#b*RfVAgAwv?x3ix?x9L&aEYwKQl1j@+YK2 zJ540K6%wXxZr+i1G&&}K1W*uOzuy_Lxgvc%ew>Vmc?vppb93DUJs|*b6=N8p@dR71c^17_4T5R0imOFtouppm~a>$Xv>qqA+Kx5Fg)T+*$TrIY6)Fr4=TyFlt*PubI@JZi@B*(5xjQj+6;2(Li_G>~0yR3Q+A?GENO z8O?3a@tZbrk|{U8>F$yNtl``%C^BeVMGs-UzpUZ_S^C=Ftj}i z|5G~)!Qv=}93tasEUchXq6 zsur3)IULx3n}n2RKK??wK$ynrNa_wAmxl{Cn zWkHsIbkup%t2OxXafrUh!n7A(fEWpOjhsv9BNmEVZ)g;P5eCrRJVb>?_c65)5nkjV zOdDW^XW&(u<>Xf~ft9_+E(<&4-8txOJnw2H)&a$b<$66I-C{?6 zbNVG;unw6Mgs@|yUCeoi6!)TmZE2?kQAmL;P>&m%&tUkm_{1t7U+VkTj{O=6hbb$C zJ!!VocACl@k7+x(Fo~^7@?wo1!x1euV`NQv`%fUuW_|}q+ft&jd433U-BhlKZG_>sheS6fh*F zPozwpNjRY;kE1xuNQ?q)@-J>RA5qvUeixJuFEq0}Z600POAeX;OF$LnfW@{u4!|(E zKvzZN8Kg0R0m**ru&xE+uXFi_F||2=a@!+E)0u%mV&5i)5-$uaMW7eTTWX|SNM16(6@iv2Offk#1!p`HBZ^U8$w{$)2j{?v zf(~L1%+zjzqV@s5)Y)*dTpkqY;PvR(BIlkwEl6-$9?zT}pLqNfEJj-+oR})%_l0P~ zT(5zl!ICf34>>V7w89GZEe93FyybZd)KHt4%ZqBQ(*Dh&HZXuLLm^B_Bz*YKmyivo zTlno~uJTB@4*RQOsUq^KElK`=N0g_c1v2B%u1DW|g-6{4y`4(e@ysG;SifK%K|avL zuVTecAFAN^r$sX}<)Y5!vHq?tvB=b6Rz>78Nnf-%)@#PyR+V_(o zIFhaT9IjbsIS4_%I(uQ}Rd=dzvYKf&StB8P4rh3p00k1KnGA!6p_uy+#>Bmdhkao3LWR>0$*y zv8Ht2l%0#yC+C^)Tz#Ayf$eXlX^-)zpzFnN)nMcdm$C{`+-12*#Jj?;RAS8$@LVPN zRSTS0=AR$Amxmb4mx2XWEPb)}75u5Jzj~bvB!6;HH_n1wlG%lS_m{r{%ta%40C>_V z@tq!hBss~UHBn8?F^b>(A^{wGu9%1>tht-`wGmvmJtb=`XRg-2rPp+X+x@FR4HF#(sNcSAF9 zSCSrPOw6Nx3aFOM|4@uL7+5S%z-mrFigLltS|&P*R|i6Wo1a4@;{{=Th&Amk?wJAW z)L&Dxlz9jM@TE@pZnzhDbe17Spg8{B0ni3UWtL-fnUutvd zX5ji0o=v@5mbe7779ZAzu>3#m7pj&R&F_eYvYnMIi&3hx%c1xiRyBx*Fya3iTAzam z47|_8fAlneN6MP{{qI%**9@^*zwHkd7g-8 zV|mB6!>~kE^NvWpBh6!-@YO>)ps!rM;ps6#FHTc0P*@9Bz84(igo4tg2`@4Vg_*LQ z2x{jzM6YxAAD|N^q=>8pM!brtFDA{87MY5a=M1$9f2Jy%8}v}dDq$bQ*v<(W5(7S< z*?G->rN3tq@PZR^}8IkQ}Ai3WgKN-E`u#h-^ z$FDwI$6~9fU@VBgx7o7|g$(fF^~FA=o|9%Mn?FjTNByR*%7rUd3VlR>NJtCc0&1bd zJLNmf5(tJJmo*PuX?$HHb%%X97Ij$iJEfX-Zq{m`dpf_!;hogqRtamtoq>_YqheNX z3~N54plqg;g6F)^nYEvzSlFCMeOA1GslV3q>`mH#7AqYqJ*?1nKlT|bb6T` zq?sWar!xLMkUhxtf8I6)*?|$8;c-RD24TZ_VOMBF;5e>zX9BS=nQh^}JEa+a=L8nl z`w2n`+%niAuTOdV*E}_ZVjAY&Z*z9jL0XSR!I$uE;l?{~PY!-*p-OUjcm0?%6V0Sm3?Cy7~Sm8g7t z_BeZksyNCsb!ufG_&zbJCg%r#y`BX)Gw}K$q4<-nb%|R^DW~{+1=85RN?iI)iW>e^ zKCn5l`2wK^7?nt50U{nBCMK^V;%&0h3CE0IYcWJUq^f#&4(A)rVqS;3I&So{dQpPqZ!1A%H?O z-)ADog)yiQV;ZBM)8=OcPnQ@u%pR+KWpJKU?eo7*k)naI_r$&r+o+O;CJoJav53VXh7UkAKkCMR;0mhh1`2$&? z^WKm9=1c)uo!p0oh^0Ya_jS%k(ju`OZ0{Ki9sjlOo?fWEP&AmF0j)0HV=43aA`8Q%5y(M1YMm#O)`HrfOAb z_*lDd^JPP+RF0M}z5U?r!u(eCN{TAmhX2T6*BCDu=KH3J>iwr>8zFX^YPd7j?J#fz_ zNVv&+Ou4lNr12t>x;kiAQbORvQ8WVp8f-Xw-HmXa{dX9C zg)0XX;~M)Q(H&YuOpx+Pw+=1YNnJdT%NK%C@!%X|g<@jnhIDvA#dGYbbE%uix4df`ed zo43rDn9GuEo5m)~&7b!R*L2?SQKOHXI^ul*BcW6%OoktjAblB=rzCC0VN|$OQ0F~O z-Uo1h326>wi`@T23>rf2-kIvkoXrp@HrX5mJ^>9HkYtNS%@q+qQ!HC{{YDh%VxMj` zJeLl_NE{UR&OEfAuL4OmJxOKyM#?LQ*Sj;_ z1}FGMPEK}%O#cPk9vWyZGIEU?e)=UiXi`=c^!+Vze5N&F4|Tw8&5hoOR&khY9fwLMfy25lC(s?%H1^S?NN=cicxKaulWh*~_ z9^m^dpzCPl`V_EKjJg7Lsn?vby3Du&eKIY#neg8^rHc);d%p#F|o6 zf6rw0V+rj+VkD7M2K?2<9;T0GgCUa~mvQNkZ)E|b?avbINx;JP3PLV&x@#`PE#{M> zB6{w;X~@5Wx6+sk)U^|}ayxdPr+TS>IU8YEYUA6)Y%-s8haCVVB;`4wglbG&r(`=2 zWWy;}gSq6XTwvN+r*qFqTzgnE)`rh$D`2t=V*T+0y;SKjNe3G-^KrHRvN0?|{)S*WCh3jh8smj7M zuNy@s$ffsK#%zm(eYn%BoZ-(__%x#C|7k_q_`{Fr%fV7{9ZF`<^O=aH^1{?Y!nXE$ zI+rTbHanUDMKws|g(ia{O5N&zC=>k4;pf@#amU|TWWW#WF|JzTQ^b=2gR(nhYK#*X zI=H?VprMAWP>bmYToYZ(pbcGq7f5)}nfF7Iy`Tsj@S{Puq0JE4*8)hh<1W)rRC!ekW5OHBdUIg{&8OUDhX@;6HOgL7W z+)H{M4=$fCOW#1cCpDZ2(Z)D`;1SmaVj$Jf`_QISE2hfr=Fq-$M6@B_`G#54+Fb{N z2xbJ=a~H30hOEw9ncQN3Nw&Ko({8*$SX@N;vzCnakvM}g(vTza9S)5dtmfAq86gZh z4;Htzn$NdI$n@P;$-tY9t-yi%_|ofJNZdZg^T|2ghmd@VsC_x9%T#QfWQ-^PKRYvM z{f`wmGxPaE?o;7eLS}Bjt>{-t>v8aR{pZLBHAZl1mtsa76posIU^L^*s4Fb)xw z(HCDcvNK`ux{tpn5L>UNSr06svEv=|AXa)>8#Y1+l+jAzqtm zAOm32;7{}F&)cgOcnC@t%gT&C$2^tGNmz+k7~k2y1zveB-y@DO$tPAb%$Ab=eDaqx z@Q4CK-4#W{kJ6-n_Lx)S;h->F?BV0Qd`KEav+q^bzJ|MT#QqkQn-LgW@bhrCwCS?i zCB%td%a0L*!8IEoCKMAs^28@q`mpKF*Qkum;fPMYkg7JmgT6>ae+Ogw&-;w}pqOuyOE3w=anBU@^~U+C*X(v{Gj} zgoh`4%2l*cN$CY(17ilV0|*n!^&w+a&1J((COuUD+;0Z0ZX`Taku6D4dUk4wWEhx~ z6&iuph$cjTwt(5$=;G>25Z%cYk$xT8^4Sm{SkzPYY}`RWR6f+wo?*~SKyk|kopEjZ zvdl7_2E>Q>(BXDB85eN-N%joD|Z6lSMa=ggMn+9&iZZDj=337h~*ciT`W>+NaHMrX2W_pS67qN*`gsBir- z5xc8n1M|u9Xgb{WG~VEBU-dy6MO0TP_S!rxZunbGV=dqU2*(>QXw?dR6o^%GSt79* zOh^-d*-)M8V$6{v+Nf~qyEk!$v9|$HbrT&u%nQ2KvkJt8b3CB9ue*_Z=GWbS!U207 z#f6hU!oD#BN*{gDwhWC~4c!EX$b*LEa9kcx)Pxe@(KQ=`1P+nBQ2@;t0(ixYI4IJ} zGwp(&L_d@+xH#~|Mt+6v#+pB~zc^s>pvU*Cwfs#>lrs77{Lln4W z2H)6iU5JZrSAS@u0@t2hK230V?SKw4{NaPSz+Tb5TTmBi;{cD9)!_kxi?r<});(m2 z4bh{?=OtM+R{LPV-IYZbcL|NNC0V3vsym5qYR$?aZjm_w)~DjuJ=r7O7%1LX^b<{~BH8@_Po8Ua*#d*CmF9 znioZ4ZI#u^U1X^@y8#n@{*!J7*=`tTte3g$WG$^pnGnz;PH^lP`;v#nl2Wr(3SuJr zma6@iDdQJr%uJm-dOjk|-lw zA6(f6nS&ZA_G?Kh`V^Dx4usxVGTY|)M4()HeZ4xR4Xoj_&M!^-9OF%2%en|JH;)xM z2!ekB&ORut4x8SLiue5HTBlZ8V@6JG1b6zrRPa`2Bv<4}?81^zs1;U!l|8!e@~&ck zozm6Y;_Mcm0@Ln&(O|bvzp1dWn^lu`%Cq8U&AdXydd~jPWBFUizbVj5+D9bXdrBpy zM#g?sq?bJUhGkYZLuAGYyWVGecO{wcBHX&}UR_9Q6``rOE9;iBs($BwEAhQdOtS5B#HV^~X*ONAX8|_*-e3?;LA3lZ5 z89eb7Z=N~{n*e<<8gp)h9_X&vdhv->WpaI(t@Y2%eB2g%>$#C3ZDnuoa%n!eN@L-u7CI3R#!tTwR?gu_ z0L6Og1?*SY3iO6ug#$#_svxp-p9-o7CMJ)CMe$^&N38?rbRB}hd>ZT=rQItfY3|X0 z19?oLiFmE>1CC85sabJ71V!Q0s!UW6kW5{ScFSW;yzQ01>SE z+m`_)5fcJ4IF}*s4-~h=(hwL$0Wp_QB?&2i?OI!N+cp+{*RR0LzBIdI;(pu7bjESw z+DROzw$nCsJsDY|Wi_(sP;{E~*Y9^gnxbq=vXitAJAEJs0tW}@eCGmyq-26=rX@2> zTF)#~Au`8|QVdVlGAWn~o=M5P!joa%MvyWeZBGUkv}Q80;3ZRnMG={jEE><0W-(ZQ z7Qg^ig}cOf<(O33F)hH#qa7@*R6T_mtd&gT?hNjj!-F-d(t8DYm<$qfVYUFN48#r5 zFtCzQqu*irfN_{YM%6QcDI)?ikjFq8D{zm{68#pFTTJa7X~+~QWHH{yzyN^?n9O2) z06%o8s6@|Ud?Y%BJLoV}LlNc_(+W&~956IM#X%$sxSWM5)-V^L3Y7FfIieFV86iTC zR1%O25I{`y8>k9?9;!(&^<)A8%suW#Fb`EAAPWKzz%~%gXeU_!A28}U(3744{A@4` zERBOa0qu_HFy2Lsi_Ca{NKkl8iY5~P7N9f%{G$a_QuZQ21{??yN#+;8Oi8kTCP2}S z{6eD=fxj@!nh1jm2RvbZDF|4wmV%IlS`uDly@)+H0V-rwc#;hQG_b0Zj{F0#NysEI zjkAg>aK#YFWq<&|Ai>=S5`!>M&-YfZK}ofhD7%+fQ$lS-FLpURWo!t9B@#}BVgtMvSGJbmBePl|GXn3riLal&`_ zF5fzq>4790RP5MTlR;~xWoA=2$xM*%8=aXDK(@(lAn;w)Kb_>6d_?Mh6B(=sS?CC1 zrOiBiaJ>-f+X;c}lTjj&HfDjG$jgk98{NJ2e-{!uQ%C`D(<~rVtrQt~tnpbe?!G|G zz4Vv2zf$Ib{8>P9nO;qQCMQd%;D-pd)f6_k%&(=<86~*YR2n9Sxz|u3^JZ8b)=_z# zX>Ha~IS(k^8Y*M}xZn-Bwh_|__if}Q1Za@z#^kBtbm&M>P zhnbNqo^CUKlz*yj#_^3TqHd)T<-aVCD7PYQ+~t{2kIB|Em6a)fJT$^XBQt9kG81FE zD?@aRvnOBYn~xu-8TjU)8W&T3$lo07()HbWRb9^h%lR<>m`{q!yqsmDq8P!42gL=? z`(<^$kxxhCY0iiJsz1xC86TiBDMtLs%e{A-yL<0G%6CV3S#Cba)$^@pylBkiVc+MRD>2cf^p2XwgU1KK^r2Rgp) z@0S)jWLO9@hv3o?>G7`c@r!YdEE=$;uOqV}i^{JfLn?7mF9Uxm;I!4cBN)3ux@eSN z_D_E&FDJIU7uZbd)%F4rOtiJeZpPoVo3UROyV`zL>}uG5Sz6kt#>`=_W~9%Arr5>m z664|%^-OA+7eOX3uwF<6= z!PQ>ozAjB5+rn5LArO34fv42Rha=eLLFh;n0g*{`rFXQOd-es!9S3to=${LzxV z@_PBCGFhZ^XTa8j&f&sh5y4qSh|DPU1!=4pXGJ-G;@D*4bnE!3EX~CD)M}dNE|^b%Xvk ziJET*gFLw;X&a=`2PN&rbM-7wPX6!ac>mpR+B;nSHzC9!6<1VTv_+qal8VodmNQeg znQc>%w=AlbciDE#R&dm}IX zxooE#v>jtAKDH#+^X|2zm)+*4?T}g9H)%;%p=+05@u`($sdPoF%C#7&<*eH>r@pwQ z%WM&V@x5&4Zp*vbGMjU(?gnkz)lKR7u&9X z#Tb6EdBYO8KDMLBi_A_J+s3Uxt7eHyW=Yt*Z!t{n@kh&h9#x}dc39;JQrw>PpyhG4 zXs`T^fClqpE$H=JU3#pKfq6HtTPZVn-9m3xnHM^X$jn$#vG!Vq{Ww}3)-R02j@mVJ zn${p3IV?s8jBv_l^hKe=DRHgGH#4YzNMmp`Q`a-pl&Or7_;g#G)R}H%w?bUk?0jU? zlC&^5bYu2bKAFV*4UZMNN1~S3i$%?!=qv%N+UYI5SRvMPMVneZ z(26busm&P5``vnDmX>0{SZWS`M`H@RNL^FZW~Mjl_Sztw-dcgWUYBWRicxye$-bu4 za$W|^Z&>ixwe+c@em1mr`xqf*sA(}8rqgMC7isl}xrjOvG7=gD`qx0DU4YH#gSzuA zL4X1=Z60h>4?3ck?COIeF|fe8W@1VILDbc{biub$(+l3FR)<5PXj+VaOS==B5Ks4d zSAqS5ZxEo?28FscQdze|?Hh_YZQXAy&0^9BGp9;@H9~bgm9Uhe_W;U4F{+u6Q2L5e zrO%ucHj+HGQyQnzo-4Iz`tL-yHu5F~3ie}wTGs|piAq~dJ|=V;sZw0PXQOZWYku{} zhPR?{v!>-uRJf42T>D^uV`x;agX=ocQQsU0Vq&x<0ot01 zqNS?)G~A9#61J|RkBnf*f$OqSLdN8W2{=t=t6K;(jXY$igx`V^8%F@32rwrUZJc3F z-B4;pG{(z@10?ZqMM^NKzu}rjH+9Hm(&zlx%+So4%4jJSbXzQc@}q2IPgvHqgl0`q zw-_4;i^SIm*UnMb6+$sA&@`CT?J46n_;{7c>#<#)iAK{;BI(nwY3kkHq|Zs`^&`E{ zdD?u3Ez0`+&a3_fdVYNQcIWk*|GeBgC@%U_sWzSzlOYHu{n3n>n(|3{4ZdN3=m!0h zCatHx&~*9*V>Y0F*WoUJcB_B6laEK|%_B`neH$|3X}9W6#)Hl2Xp%G0E?PYXwlI@JUqXP|`tzLg;>5?r%`UPTU~Mxk^a|OZT(ynXGjfY9WLrI^ zi8Y+aGAZ63Avue5_Dep$%j{Xvr?NU(eqYzg6~*t#^lZDtGY#SaoC;MYy^Xt|@J-&r zWID(%5#2hH0?bnRv$;6?Zu;4S^cbxC-LiOh2cP4Kc@#SVm36g*NXIc(-KR6ZJ?>Ga zN8%E(jQnM*$jH9gSt^{lswt{?@R)gl4}lxxEz^8|rg^huav&yec6)a=+&wQZWOh*3 zb#_vbLIUQr9aaAJ()(|UHdLwI(cGzWi>VU*B|meZ)aGRu^@lDu$6}hjP5vpggaum9 zub6RfFRc#gz!G1JZ%6`d0LQ{lpmTy#`qt?KcQU~{OKy0;+hP!b`%qnWY|aAqz7WUU z+RhPQ&pdXoz|O}VQwD`WdV-{;cPZ9} zstfi%-vKEKiCy1Yk)%Kd&t%jjqd`XJP#E-XV9p^B2!}oR?!j#j0ef)Y!#RY(ZXgg> z_7JiMUrL0Uq>Mz^-rIi<;d}6-6sb#^>Y(+}2oypefzm}7Y9J6Anh>~SxWBb8DF6zy zHrC($LHyT35z>$>#26r;2xvGV6k=)$fxE>-1VG_Vgiu!!5duF6MWG;Yn~+PFNl<-^ zJ_PO%5EeuVh9Z$zBZ`Zf5DaLi;KSKysm_Vtn(MaU%; zBw^!@x=u>77123yRCB`?XNIjf%U8JP9gvXcP@r}yOdoXB+gi`{-nWyfjGQ{P5 ze722qtez}SXM7Si?M|HPNeStPGWCxpm*=KC`_{QbW5vsnSxtTGVap5i<*CEg}h~caD?PWPIx*{>M&acJ8ZA8KVEpP{>fNhg{EY7$udJo z+QITC^1+Lt_W#y$d|LV^xH=aJ|6?T7-cahySHQkM|Y=W(D#z5?ubV~vG>n!3O(Tapj#Rpyw z%^&jBdTm`3TLT5)v&(+fif7z)did~6FrI$7Mi@46LVahc^sAWM;sQvA-#^~=3UNO^ zz2kU0*l1G0*dCxuZ#F4`3zwt%ehLk_di^MK{M51Zjc`_&LSqb7Y-B<%jIRz*+`KMq za)`n6`ofh{M%W}~#JpDl>jwSROnz+0#JqNu$!&GxK_W@#sts8*G6bl~jP)eY+OGs_ zj@%nf!<{`9?HGFKoOVcyCfd6rv_Zw~iB)D%W^Sd}1W6ZD)U3#506xRvv&h6F#-`gY{GkB`P?8Laz(?)i^zm7sbznFV&J z)xyBXTL52{6>FwiojM$nBO~xAM(f%#UAy}&4W}dx&$16fmtV~DoXdI|$jo5&d(6ic znt)&ZBL5M!fst0jf7vmi0&7H?*us{l;X?4Q)KUa$;U#^`$Pr zf4mO`79BK`ci?F?E1>Qh)wpQ9WGuQqinh2A=_JM9d77#2OV~_=ytZSOwRYSfA`9~N zmnn!hdOzD54Cnd!@$#8!`~4^Hbnq|^-}DGcz|+rEF$N;*Zp~Y~DDyA+TqISL(DiMuFB2hRb4rPNySm6wfkT@I? zgFA{oYK5^ehyMQ-FVX*`iZarN{wq~Q7UmSMi%3Ugh){=5T+3&lOFh`xv@UFrR%k55 zIPfn~BT!pRun~{+vDRKotM!VF^m?bY(O7EgyJ`*2E&lfKqqIpYNa<0zbuA9t);Nh9 znPw#4a}A-Z((_X7KrIyPBDPS6)Jkbl5QHA&Xk{tK9Y?D)4<)g}X2d<4(ua{DqR&i6>ueseB8Waj!%vpFpRCLbf=TKGmF`|} zWiP)FrMq^wnpQeB@Ys0*Rd4V~YM$TU;ZvkNo1UF^FKK8xN46inrahdbJDj38oNP8s z=1t!aZ=@VcA6Kn3W({A>I^8)BPxOc?=Jq$7rq2Uoo>67XbxsvMhTLu6fn@&fzjF-< zGb^tzS?*JijIe$!2|Dh54xD;d1)L%H93m>0%Wa!LY6Isp%xWQ&MLkI+VYC4n3sFV~{R96lUADZQHhO`?PIaZ`-zQ+tz8@JZ;;)-`tt0`7x8KRh8ub&Pw)^kz=v6 zT`?qbB{2zlW(IZ`^7-Y_br`lpL<|rvqV$0WL`p!cj;zZz2TI?m=Cl!_T51>t)Lkp5 z1BuV^rmad-)l0*MvoLEy7;|{e9kKTN^Cx@P;C_rLFD9}j5d708vy28}wdwOu_z~?M zVEgCpB)3YVDnd0ibLINGO6G8~=AkDBy{~<11yKAm*x7GTBcCMcuoc~^uy__G2-)2! z^$0j?_H%^mxb-Gjrl*goYtL@9CxogfD)-*LfB3vHj(tXerLksEBRj3PC2Z4eMRdDl z`0@DMpXp}PrbgG)PFqA(R~}O@(!AUrvC6t91+-yWA@d*7-IYy{!^6M@-~#wCOf)7( zHjfCVbkx;#aS9g9bj^cK4qNuNelig&d;#jC>Uk$Wt6b89Y(1vxCDqesT(wUZ?(w2| zSs=@7aqA@@*#6_DZ}$$;X+5{zYs-dzu>H&{aNljR-;8rD(08k0Y}MiFAU`M)kI&1_ z%WC1ezm1G<7N6$Vk-zpHZ7;(<3f-Dkra~JGRWBu!ItV!0?ytxvlZ=J4G#XdQlL03> z-sdJe9@lwe;%u7`JuA{Jt;NTQc6N_^%b>dsF;R7Y1OL%%E^Sm|Up@oF(Fj-elFmP# zDJTbE#NenvjYR z`kRcn%KAV;OY-Tov=*MYFGs8N1%ozsIGY@KURD{oH!$>AHujf-LXR3VdQL>O$yJdri(2_e~x5RYm$#Zkz z-z_lk&>O`%&pXb-B{l4ym;$&C%%{^FBTOfK7kQtjA8V%&=AcoyaazB&C+f8M%^(2sJ z7ZlGB_ql?Sav>tu{2eJVW`GLBF5{hq_DqJvG)3;Gm=YPq=5}h_Z3Wc9tkQXw?;?;1 z6~|Q2uKL5fhcX*P4)$jf|2c5``WzUrtub%3N0*h$$ zaF$2jcuBJW!G zPV*r>Rax?R8!o1CNywI}-idA0P=`ygg)`3gY0+{b9x3I3fLQ{VLV3+f%p@Cpd|_1* zQUuzGTmopz?=AppF?1MBpGc7Zfn>$WXweYF{mA#W(!wCayl5&QR4pkiDR|-I3Kr;; zNx{qdufgpy4+WV|5&mbC~ zQ55@yfsA#;r9+!fTNB5tWuH=YWS610iAZ%}mZSCl z!9>GmAhCc|d6TI_yMmD9o)+gz9dd(W;dyB{Q#v5I7x!{z`7OaJsaY%%a;LhA} zWYw>uGBxYWy4!7L=V)w!y;)#xALAswGIbUv#8A3WM9+N!BCut3uk*0-aV2hq8zvQW zm>yt`V8W;5H;8)OD@_lp(Z^{-ScokG62`03VBWSnxWWJC*F!^=KFBOOirEL8x8-vW zR|rT#$+wda#DoYYN1Y*2zyJreKE_JCSuPnIx_1Bf7qOY19nNwG^%hzn+G3d5qUcfd z&OloQGFfq08(**gJ(O5?M4!Q(v2D&jO$Y7^ZnxuV6tw&Ue5M zo4>8jPkz5OGFilRa0^4Gu5nlP9$ZqjIU2y>{I^&)a5Q1h*fVR7xh<{>7|qL&?A|7}#5A-6K{LYz z@_a`eTonB{zn!pKb3Kl%Of9S3pp`dTF2+XLrGIv#Bx2_FoNQ zaV{B|>)BP&4bj?NiW4?ij}suyDI+R+3}Ui<)FBl+{B03W?}vb#UGpa(j3F$vKS$kQ zrYe7=G>o;fO3MtEoO_0%@I~R`-56KM|1skvR*z;1EMxTLYKtm2ufNG|<(_ z#UVW?2VF!8{7*ZX{Ljn3vC9_%a_6cbS3eCIc*rr;5E}T24tz;Q^e({tUFvxCB#8!b zDS{oEwOe#S=mJ2_#UQur&ummLB+;qTd%;39nxD2xmSdX-K+YaZoc^NM;U^Ka%L0i` z9DDds^m~+D_%Dw#_36PSzb2a5S&C5tx%3dJqL9HH&ea#6+#l}7?aL09Pz(j>AADB0fRJQ_2o(7*lpXCedFdg)mC~w#H8nmlO4y%<9-8pyP+rt@vlX{qw~^4Q8cdvPu(DM+&Zi=IqX(R%#dN$7X! zuWnXBIk$Va>(;(@yX*G}T?tI-;^&>>n_rrzo0k4ijh4q`VHKLCx*dCKf&kZZ>+4bS z?yS{EkybK1$n z{}&t14CBVfTR){yn8V5WYs-ks_XU~}d?}B%?qf|8dIm)@-z0#o0j_{@^_x9;;nW5u6-|8E*yag4)i!?p zF*&_>?4+7~Ey%it|k|$(=a%WVDw3=hL#j~k(?@eK~-H2*D{Q*NTjZN(S;R3NE zrK47duVlLCw77gcz=z;keZmc~6K?h9T6YNIE>Or#DVs1C2(La8#yBM|QjiFs3Uv`c<E_m2=y>n;KXIgi-{Zm|Uk! z$@Oo+7iJ}QS>OL%a-8f$Oho^q_W1Z<80F0DEnF>$m^nGviT?iqlRoxP9}I{Q0IwWWGVS0aSRO)Q#6$W`jd z@G4*%1Lb3`YEddGiv~bs35|P-wxe;+iQU#mx3iu!OgUT^fh~*&b7uXpzBCp}B!WnSf+OHU<)$`sK0f#z zEriV_Q%;$p)FkR`GUxXYt`KF~(qQ*sHfCO(_6y!Z8O%L&KPN54lb|IJoC8pUj3dZn z9ZBk=y%R9rwNj?8&^)Q&)78{ zvc(b5Rg-nuloXcZK!JJUj0TVld+YJ~T_<$w4c%vzFFkYXPqO*JmBEbG4l&35BYMDP z{b>?sszWmr5MGZz0g*%7Fx1-S!!UoXUUH9IkIM)Axn~lAOF2LDOMcjA@#E)O=@m!r zllgYeziF}cavb~I1(?!jE5&D=4pud1dF5p<(=g1m_Mx1VMGk$NoB^5;hn;Q`Rbqaf$6EK!@3o%|9Q?PQYSbxzM60XU1 zJyFXjS+V??fx(ZRU_&2^CEB*hMC|(jvDULHis8t~@EMuqgbaB-b$?m=AUy>!Q+VF)Ri%Lj$}>=n=60{yEh&OSl^exIQAi zJo{vl8pA@f?58!PvmWqD;sc2rRVpoR=zcvLkC+kjGwWUIC_BbxG^c8KmMEng9_dAE zb^l29UPcx%tnXtK=cjiO9dl`Rcecz!tt1UkZ$H^sCB`R>ve$_m>9GQfl^L`#+AZ7M zYTwK=GXt!el1}hdkp-=-sra(OUYL5aXJT=mqtDTbBDT(5$tu?qf~`L@L1&SQ{h4*p z27+P8LFM2G-&8SBQ^s47TG2i6uVEwk!(zwYq9XaQNb%5GQ^uFN=anp=gyjXDpZ09a zdG_rHM0FbKiZB=N{tR1c&_Yb6r8NPsxsQX z-T`n?#b;LOMe7(eJg>2&n|Eq!&~o=f^c91f%a@Dx)wvtlu8dLBB%YTpi@HJa?AS_> zk+1jo-86XjF}#`5iHMa)+ z@~V>Ah&q_rZ_a&?97fRffXAnn`idDxz7&any$sO+kcMe`bG78EW_6XQI+YXhBoZ_ zdLotUwU4)mvlG^uGk7mHFjEHPgsoKDm^dgyWLKy>k2YXiE0 z`pjQtwxZdwG6*5`m-LwKF)%h-AxE+MwXZ;9@IM)w#7ml;{{|YqZK25KD-n(^Y}Z46 zoDR?fLb(O=npO9H3f2-^4g(i;6>kO`FHMx<-XpLBTR>qx76Cw%+--dS4>vOXA2&8r z+3hwA3aN z%nEJqSJEbG1n}GA&RO3WIWk0s@~M0E-8G51l*3>1@;IvFWBzi(zcQ3Ta;mr`IRRHl zA^7$Q@x}aKg7rE1#k6 zVawyiaKm6jG~vCEV8`ap^+tR0Fq#7XdFjXZTq9DM^}dqerWdg!Z}U-*P+zxEDL#nw zs(8v#!dtqz3bLVHY+9YO1Mhm=phmIF@T$ykgr#Xn{-|ZG}kibyK&3t7_8Lx9p zMAOxs{&Wb4fA>rD1N_bYCBw=H)6YjCmlB`Vv*QKqcc|!8a`{%kAgepo9YJRxBju^* z$UJIHlBAU$G*~kqq1fyPa1oqFbWLNI3bkg)xbeXGAV*ZYg?d-A&h|ctM<=G$hc-kRbFAlF<}Oh>rf6x>P>@S0-W~#%w5qbqFB+bPOLtP z9({Dkll8Aeyx~G=^|Ggk?4qvfH&uw)!lwn?10e&2N@-yZ&i+xJY}`~s=Tc@KGIaFe+6S;=K%T-ucO zMoT}To;E3)JDoUWn~R%S#uEZzwtf{CxvT@*pE-M?E;6Ej=aUyl1}wmN&16<5z+n(W zA9`V1s6Kl0#wS}BI6)T|QdRMOX2~02ywXLA#GL{e0a!DpS+C)0X-IpOXQ)^T2(}8D zqvV`ZpMF?f9*ok2@U*0F45@3!SkbAkS3E2oUfud{+o3{ly%2D4RF?7b3ym=-#Z zk+f5sv;w&&j9*KYlja%-o@i(zE;=MqI#FDx3C^L-&~QLJra?Sfx3zFyxWM4SI;D+N zR$N_a0ZK<r&wTdflGVN-^ar%mY-=N9bk=Ew2J3j zjWz5vLuLX6^-eVI-|h)S#D;+W_z2QZN0EWb0z3?5E`SdzF5Y~T-XNt^6!OFoKpY3d zs!}-9wRK15D|{36hTecnWLDA2h+(L}147h+%8^Uii^cDE%Ba$GRa0(1Kqa*1T%kVH z-OQsaB^u|GWkS?ocvWVyZ?@7@YS)JCK#Nog304a0sJ@vRWW11E=4WZpBb2Mym!Qy3 z0nGx`wZO=lpL}M(YwVPNSPgtAN}!esIBqW8eL6ym_vf?pEs~)-!hLTz^*UR}8h%%f z-_IHI?Zf)@^8LDau;J_L6y|mf{JHSq4O%mB7CjA;4a0;de!A~&%3va zxPq{&VVuHXx{0v_>ig`_~0kV$bP@Q+}tbV$BbW(SBRfuBHY zh2%$TreHIO1xIXp8eGy$Bj|dxJ;d~Ks8{Vwf**t1T3^P8`zR=tCa}4wNK;gfgLYu1 z73Oo%ZIs=W^pw(3EhpBNLtVHdS+k2nz2tCTi|HTzFs^ftUk6aOxB2_-jJ0=!0G~X& zQ>2iC9lmt5nIz*DJDGIzuj79EO1qJnd=n&UyE7aoO?C;@_-v>Pg3%KH{^qhOslDu4pwg1;^W>oTUwY70L1<2sq)-Q$Q_+Ne!SO}2|XNYn2jO^y{A-_^Gr$D z^AFk>31oj6s>i3^I(9J9nn`A>COGK`9ducCGbnY}8UY<{<~HW%ay>yoxwLlO0H1D= z8!K)NWkfHJg>b_rX!8WVg5xJpfQjqv23Je*jhjUPhfIjbMd2%9MJ*#O;OXxYqNs@+ z+;=|gTBwGMKZHpe%|UkluJ7S&I>b1QJyA zQi}`28uWvNA2DCebh($t4*ss|_GNwL9MN3Zs^{%iK)Kk90YXFqvM}DZ#DOc1?86PL zc=euN-HW>G+B|5Tf5WelQEqiDE_;aQ~du&E-#MzVK=v-R5|#@_zf833W{Ec^fAL*`^cJf>i{ zsBpL_;Qs*m577Sr`w#H{fcOu{cDN`g@N8@@?*Er5xmo^~DQC1~UA8!o`aU%sor`o} zDF}ScHiIw8@I8{`lC8_a%ZdZ3rHqF)rDz&IFT0~d(GuHI*PRx1#he5E{JMbzsO~iM z|9yL-+2+&5ic|I8H|u$|!}s>puXd9n2!O`>C{KbQ#8a6nr+907g*c5xj;65vjpTZmQOHh8e43~v~li<2#kE=uWQ#g1mu^hq+J zIxr!T4}}kAFm?@oCYO(U#%WpKeO|U^1*pP&qG-&8;ZCHF<&hxRK3R(b-n%SUy;_~b|QjtMc(T? zo~jH=2?MYeR#=HkQz8a`~fTVV?yz zW0|filVDFaixwj+CrSTI z-=oZ;7(#inSRVz0&4GqomZZHg*S!F^?_eHKNGnvzwj$=dH!*uY+#;K+eF`Z{L<-ys zxRRN#gQEcUIyF<(>b~3cAX`esC9TYK%=Zm&jTSvHI1>=PS|eN4&L1A>x;X20mtdpV zhs&PYRYOpIn|3W`d?4A=U@W{SoNL{%KGN$~_54W5gQD=FCdNXug$V)(TCDSxJd~JW z)WK}!+P1KGh^x{C!5bGC!-;xa!KYsU!iT9`&K}gl)^wf2M0ACOor>8YzMIdmhuARH(_HFW`VPEcJZL}7(nZ^Rw#2dgHzFGcQM-2e zTTD13r<34P@u&a#MKSq1Wyhxeh_t4bv>1J>dL~toqqi|>l$A0E z+Q+VYc#B{+lA@eN{55cv*p`I_C?wg88A`DOBkBj>+ zT)bS&E7Or;+yle^#do^p`fzy0diD8EePMs zcHgz+J4n#`X6pqNY{rSt%kT4j_VcB+;AJ#UfQkq zTjm(>HxXwQ3|3fZ+n2t_C2Uy1)^#oW-#?k4)D=)Y!-fVGi5VC&-1=hlQ``cOz}$70j};$J-p`-7I8(Jg?qeuh5p z2LzgXqKAB8;+X!9P%T&4+3cTL5<@k-x>@MWQBlnE{O$p3eX-`ZxZ7Y&BjtqBH~RDMg~r-PG{1W7nCu7KqQuyb)ocp7RXul zQZpDxUK-v2Q4*46q%QCE2S|t5)vrkt!Mg%g%C7}Nt}ibdO{~`R(x)LZ2!;_^Sk+&# z(3dJuRJ8ePwle5A(@G+;lDkrNzwB&!6{wf?bz2PK8eQ2m_DD;$oC}QdC^O8GAtJ!|`@AHX=Tx za-6W?B~=qX;+?=!77gDpNzu<1YSs}|yx+H?9-at-q7YSG+-|p|l%TLnWIW?GkyXGb?{gc+-~AqOaLDws5U%Z zmyr9@zxLSU?&|6gY1 z;`(2y37}N-kHcXbivL_aQj~y_5P3A38|~$ng|e<&BfQ zeC#A=(_8mHPdyo=&S5!IoR}619Fs-e(YCrvWyazJx%K9_-x~{G`7+=B!uvCHMaml` z*rBCi?6k!zKOnDOsh}?SjWF)njUGLGO8j9I~fg$XnD29ipj|o zbGH^P$^N9axa1)8BLnHLz0p6O43P9oC}=XQCkEUbnYsku7i6VyN$d~D052lc0 z!s+Hku;o@W!VdKlYd={grc^p3IB(c9{2Q6YAV>a;Z4Y91)4MrkUNd8y{!>v`4qMP) z27umfZ@{BxYesMP?8Szggaz!y86x(n_N-t-%#h4wy@Kl<&S@e-sWyUIyGmn&tNaMufxkI~w;50--;mdvi=hxI0Uv4tTkFgp_sv7Z^F15h%)|$hl$4yE zPZ_K)dnnxv>!W#77Iih`LO1gzT&YC5p0{2wc~^Up-7$_{pkp=V zm<4xA`68}^M;UG#Z$d41XP!atD`0t^ws&+rD1KP)d|BiUr0BOj#_UW$i~0okZ0wQ5 z6YbV%lbCq3?-A`nJK6E4s{C_1!=4bq_BN8&tRFWmkK^HANVopRY>%AUGua2z`j%W# zE+<>*D3)F4ZOs)w1|+0$83x8+XTrA0n*=*80y~9|;0BqNW|zzTC;%hq6##vvXaVjI&IfWl7z!qU2t|XxiY#DBk(;5#PHiV5=T~-Y8qU=8 z63}iiYfb@tWJ+e4*Qb+OHM^1c(Zem@Mwy*K<2$uISsv1 zIHOeT!}qRkDeq?&(W>OHcVWM|QM-jE%}@QpDgfoKTniL#3!j<)h*xiyZ2l*YJF*%K z&6268O@;s@XEaco`$NW*QrdH{tAH4c2c{k{px?0-?r%2VBU+KD0Kh%cowBfS+QOF* z{~C;_=*MmE+?FDJmU+o%jKj_y315gnSGso!6Yi}?5V$pktsC2ZgCMjpEqsY9I+EYQ zKBhtT-Im(w?%51ovMKi7!gv3%VF+(+H>0MowQb2tlJp#!oblXabb^O~gx6t~I%J`z zWWq}c9I5d|f=s7G0<0bB^WKTNg|64tc*p5E@kJ^$q+nX9GdY~kky4ZiNVxefzTIY< z6svC5KeB}pgvuz*IoJ1QU!rolNG&?&5ZK@P59#P7^HvJWS3KSPfWShzXX@XiUq@SX z-aL&x3PJ6#?CZX)M%N2u`?OT5Xs91H&l==>UHNW?G3u1>0@x|_k_Jn3A^&c>Nr-@o zFbWuTcuMTX6GI35OW}jMywgs;4MEVG277^gMhk}kKjplLSAeXPY`tyS>vJ%G09$8( zvY}i8AHy|(T-ju0bT@{g1mt7g&ps*AIcq!F9 zwfnbv%EQLtt26~&AppLl_KGLbK~b-mn$MeK$oYun9kM?t+HJ|#V)UpXNDG;#AnMnc zA3SH%rZz8%4TmU?altXA1vGEBWx~&Hgjd3Z-W;T!4=94M54=#k1flphx%cow54Dh_ zD{N0o+CPY0MEo0RlM&wl_HTp4o!$Eq!RCRV??i#-LA5`baUAyu4@K!nB9l^ar*5rD z4`ER!GN7L2Co1p}%}<@ZehLkaUm4vu3)yPL+On`cppjfx@Nc+L!HSX{htfjAsDn}X z&~rGZCBWumO4SOOm4W|J&U;tha#WZ^j-}2=)aUQ6fYr%-vw2zjFi!V}@|xxVW_{s5 zCh`T4=^9ZXfU-ZqCbYpi?D(Nu^v^Mw9Ou7Ir+KIIM*fyuaIAs77Diz=-&A4MA~XLV zy1v9Cvy)z|VX$tEZy9`@(okqgmu+{wZpn;PZa`<4+{H`y;K7I}wEMh5Hy`MWZm(-J>)-EdMJezz<6n~h{!5@` zE&=%ep=4p)`HK+e>bf>6q*Kb>)!zZk!G#$AZ)J0{{m=JqGC3Y?yPz1n{uD4vdf)>h zT6+^BLZ(T2E+PVYyP*q0cUZct3j!J)Gbao4{|;PPh?u!JnK}OV^1mMQ|DF;tb8|6s z{*S-jRDf<5*KLC}0X&Y_6)yR*M!PL{9EGGvJ64iLV;G?5J|s}0hzWJE)3ZFX;FWaZ%=-gHX~fb}~TqX<+A7@po% z7msclNXUjX04=E)T9O@zHMs!~~Kf{DNQ(nEJuxXhJj%_zinOwzhRJc~%4l3auU=W(Cvc z4fuhG-1wTZd|sH0FF@=)1OR_ph8*tYr2RTWas>$eR>x4!@8@e_gM(uSpwCZlBAddu zaC|wsg7NTh13mpN_-zYI9Q}eofk5$YZ2i^}f96ws{?a_=&xhIyXq4HV?}NL(177h# zb$Gcue&ct)Zq0BvwRkhQxp=;4NudmF&7nV{clU4eGPQq;lhIR>(NNVj3RB;S^4M!@ zW+Xjo&hhl{fA4>?6_v!oUVAPkR8fth?0)Rs4;*2g9hQYMs)13CZ||9<{hfH;FO0SUPSB~%wu zfA0nT-W}Ix-PeBD>p%gcXnpjud(T|f8geou-JKBCh^X2*76EZ-eCSRt8NH4_qp zGK&k(`*l7UdrL^~f`5ZnDbfn!?cOCzKRin41Uo*RelouK`+!1j+sZqzTv0 z;04FO_i!{IfO?CgX8;p1axp;O?Y{{Bfabe{uYS#b_okHJco%;Gj;Z_oYp;YBJAzG3 z#l5lMQajUUhdIB53{66kKCGJbVTeV{{%3;rzwdIV0Cb}7TkLnj1rZUBI)WBDIXNI5 zin#bTCV(70DJg3A+HVy5*IMHv;)Ec_f#7jJD?23mcHqgx^I(6kxC>=; zp)~iof(-LJTZu8CFinN>&wYb2MdZW&u*muDmYF_AXb{gr98A5 zDyKvg$d-_UwgVrT@?atzJCFJ=+lKhwfjh~hXl0E-Rw&())lB0I7|=>0snP6e++V4( zdynO6GLq|qlNCJ6^e^qPFvgcO%erW)4m;22WV?THxA6vmBGQ1JAXzomkNzNL$r6DL zcejGu^oy*bdka2!BclF7n=p4(SQXqh#6m_;akZv`enW(RLiQ|;-3J$RMBq$=}miK5|b!q5cB8os}3v*i-j&0w0YjUrKxNx{Jiq zZ%rD(@JPo1&7H6|Agu|4)z~>SZ008{OEwr&DU0O;K*&_nsT9P~6`Vf|O)|h;2Ieai zw>>qO<*h^nK}Q@_J1yHkoKD!;tosZ2MnOHD0?rh4UVct5+;2#k{XkPrHTzRe3sr0t zR(4&A!c}f17JVu|Ui5C%Ac>EgH&sH!g&kq6cOnO1D&)cw&T)`7WlB+D&9y9DwFeNWdPWFF&|&Q4#A!*3A z&$&!G)ChKMpM+ldf~MZCi8~aJ=}Dxy~Nbj zMOKLpbYqR}VVJ^Xdf|V$k(<3>vRDGM(?b{0-B$aew?xLa?+9rq86QvJY`My69nNOMb5@lwRy!zLwg+m;a=uk?^pnELMtxNPT!0e?7z9I%>hP#$gQXA{bNbgQ>w0wjpgD!NZ-~yQ8sJWbk6V-)>#*agV_^tUBqA>xA zytQLsIQQdrFCta>l#{Gp4ejf?tON$F_xa0x<*5^pBVxuL{2b_N!N3_NCF!B5SE9k1 zvMD$6>Z%-vVo7|E3JBrc5k^X5bGQ{ohy=7uy5BUn=NDInFDa#1%e)Lk@M{(AG_3wp z#QJ+4{R>KKYFktHMZWLpZWi|6`jG&~N673n6H>Vj!DclG(PO=?bOv z#yzg4v2yF-QfK^Sv`{x~K%w1ThX45q)$_o7GqS8@77Y?tE)P|5m)4624l~GKrMRPXK@ zJ1Lk#XfWa5m}bva#@jmK5*9S2V;_U=Kcqe>Vo)PB4KWl7Igj3CWM$CIs6|P9h-w1X z;8^eB$d%hwq2lHwg~TZdzpJaC!4W*k;{!;qf6wkaR1|Ui)x29nLZr_d)-q?*a?=Z0 z4)NE%&Au8@yDbuPUOfQ5s zGTsQSBau_`4|#3a;*kt!4Lu@qp>1Uy{^|4yuiqopnKI0k&u;^);cNV>Of&h`G#=1k zJ%S!XPT_RY(_yzvLS|JM3FJYK2{#PgFVBu^Av)H0J2P^Vsqv!kAO+yp2j!c@tL8(o zOIjbamLPIpzM$ox2I?lvjr(=tbyqQO-dsRKP@@4pF z4(F|xon#9CK8yhDFPT#a972ik?#A4MFmh_FmCSBFbMfQYy@R9(#a(-d@;J_5IPvpZ zq9#}9NM)Qlbiakh-#=oi6H=ba3E(puH``K%fAHbTRta^#Us2k@p<2a$SmK}gP*@yj zoj`;J$e%Y05~2=GVPxK|Ba;$|NjF7j2HZ@MO{x*yR1N`jb|a1pYq&P(RyBtL-B{Rr z+*(0dWkF`aH<%pqZ^!n@4w!U8f9-B!`Jf)DLX&F^zsN^oPmhmZA7;}msyeqB9abHB zsUICRBOe4cggC>R?$nAxJtbg~t_>&^x7|1(WHw;a+?z(i_B$t4{hNh z3rI*E`;5?U^Om7=Uti!&k2_0AHT#J2Tt?40jb-~$TUQX72lIE|$=DBf?Q|t2sT~8~ z{!?7b1D7oB;QgnG+F)@FkACSoqWQ`9zB&F7*yPOY`JQRYwZ;50Z(W(jo!5Bg2KW;z zH4p$@QJAKhC!&O_GX~{}EABZ`P?P8c%bHxKLmOvP7+1fP9Hkyeiy|qSGUUj8>gm*u z6*PTv>Wg-5d=c(%!}Zn1byC>Xe%w2YS;)0#$fz=DSn4=*?O`GxonS6q#8RX;pYIB% zNN%)9Ga~Dz+#rPr_C+ixcWa5b?z3rVNe|!y`5UgK_tN2ytZiMqU11KF3dY!m99gsi zZ8Rsi=9R2rDvFC>~RF-K3%R-ga_b-6ls9{ z(wUPX3fhmx*6-tiDO7dFrOGav3!Cou;x@;;R?{@SCgtW)P^63I3+!O@dPYF`WOc8N zIqOt~zlMMnw6=#fRZIRXR!P!TFLrQwMw5DChyEuc(j>wo)tmOMy}E? zUP=BQr?BYCEH$8d#1@qdN_B2JRSf`vm1$G7rqzoJ=gnBPQTS?m%e*T(fWa0@gc_MT zY}iAU&oV$*4u$DmqW4)tB%<%m`XF6Hd$swD+CzxLUX)#ybqh;hEjh)CvY7#9ghH(z z421k4q2qI`OR}ta#L#r0PBp^S!u6vRxl?q*sj-3f7um`X>}<^x?K|C&hXD}GmG$m< zvCbR(mLhW3`|`o}KY z2_w|LVO(t-F*AaP(5Dsrjlk6TOuOrhhJ)0PmB0 zyeyOKGVGyn2YKW&2s#7tK^l-`9-&buau0@&X^qwWJA;xz?QnW%U2PO5Ip`*N^EP1W~iO@ATY zGD5JG`trQPyCUHL{uy9tK*!ckc>DQUcJyUeDiB{?pv(KQ(s$ zxWEq~Fn(2j`jOH`4c+g-4Wpg@o{#cVHj1#SiV*Vi^<+Sd=Lpb3YiwDY#s#>Z$ZW-{ z9?WC@_Kc_TZ9Iq{wP0wt- zuU)<@rCxb!V!w#f5U6tFgS^aroNSwQ^Imw4!j!xrk3IU&0KizBy&LbQ`ZNwySCmC=bzY&{FgtzLeFWp=AH zq*LZrTwpec%NSHE0oTy2r8dGR%%{@#591v6%_2Lmg#g^ZAzFEn4DV4!PrS08Db z&AbVLHM3pUnLsjtx!MW)-CrY?E#&1qQxN9o#v;Oz?~1;#d9g5lh-EoSr(hPU6yCTj z^xw&83R`=*f`~LSw{hGIoUCNQi|#T}7b~)^^XCE#?E9t=5@1YBn`Fn&6lBOf(3R*W z*swB#Z2*Y9dKi*u@NbDvZFPqh*@1uAQ;3@O$}LX%lKt+2n*H@9*SvrT>CIwNYgQfb zLT_q#*g;`o?bE>@qZAYI7Bo`;*vqAh8Vna~$&(umV~_h#Kka~M*I3s#S_I#AdQmB_ zVD!leqf)I=kCr+?rC$Y<^w2T$56(oaW*i4>?qM4#j|+XGt+L++j#j?Uz;4Zui+aIc zOXmq`-#u%9f8_iPpyRAaHjRt%77*o8*sJ{$Uc(Jv?JbZS9?C^`d;NXiQM68j_5BoDF;tS<#e+LvLZ-hPeB;N(CGEnXid5Nzo8w%}cMtt9kCI^r5`CTQ z%p0Hoe_)p|s)V*6*&46AGk9F{Fnr7HnTEM7kH-t1$xui*ZSuPOsgj`*L{;?7TcWzM zxsr7+Z6i;?wt^Vt0**>d8lkc7Xw4wzQ*x7q*vcMo=;6lX>A<5*yirj}aaI&RPXm?> zlCt!+5#74evsD#Nv;BSq6`@=xSu=hwGI#Uje>|GM&Ru&@mf%&<%~E~^$nSnIr2L#K zD1({!B1a3IKd(tgQtxb& zO@1N8nS3r#)E8X~Xas#juF*CwLZfg=W*X-aA1#S?* zf0@vmt`d|{_hQAJHg1BtRaSex{;cpjRTU?MvPhxf0mbZ{3s`~XT69O?DXLw+F*7>y%?qBA6uS8 zih87H>&&9|;AQ+K82-!m%`0$au)UmaUU8df;K7ZNLzbr&SXTlno|U)p!7K<_sS{}! zC=?WIXY6Hx&ee?@zv&}_o5I*i60OIxwABE1%(wYl0_F4hf~jm{+tyPKIb&twf9in@ z54*q(n#T0et~wz)FPb29);_6T0SmN;pG;kuXp!|^HV$u^cpC}S!!1ccT;g$%I>b;X z@-YE+mW44Ez7iI712G|1jWQl$+w;h=#QmBY^qQMd*%_0@^AA-#7_$r!YqDGmvFBw z#Q1X_>jF)8C@NV9ii^sjf3Xhfc@9_b2|7e2e>oBT*}^_rx|@>AJdG$be~BH`T|t=R zIk#%bv9iAvpDp(rHerUCSHwH|hmt4j_1K|od~QcFF}72ZL~2OFA9n7$$;~2-9#5cWyvV+{SQ{ay3@Njyo+R z!-j(qaE7|lQJ&($waFo-e}w=Pgx*1zw6a$Qy0Uzs9bKsH`>ggVF>7V|C_tpHkKQ+p zn)OVqEfO8Yy6O!0N&93FFALss0Rw-3eN>S4w=G%~0BU-y;`hPR$7&+#eS0Tyf8FQdZvqi|f!q78e|TDDXm>#9E0^7R zN4=Z;5u`)pJUR?YQEf)fE;cM)oy?nFtxe_Y*YwRSdsExLA?VViz1_=uxjGz>x4@Ic12Ud2&NeqpNAHTJHJ*(7?e?;pjt5+;f2h~$EgK^KrD%A*jdJfs zv?b6tndV|$&?=Zo&?yp8_cwT`d^Q%8!&LYIn=%KgPkBt!HKrUD>cd_$2aq7XDwPj3aEnS@!U%3W^Wt@GkHk3<81LiPo(BJ)V~C#TJ;id z>4=5fv7sZjfA4QS`U07{Xq$P!+QJ7CI?)tZje#WT!a0cA^agU@Fh^q@i#wtRtSJ0w z?f2(bbY%vrko#9xZ!pw)I(9dCzU^#!{D6?IuLIP)>&Mt8)aovOs;*zcCh>eT5E!JY z02arCKtrdTY;X8L;4Ibrc$q_f* zX<(DdIAh;s23|h@w4NjXYf*^}3nXxonGMx)*0 zSt(Kpf4c0v|7vSG?&7jnGZYrtLe$GGs?xfB9U*nSf|2Qszr~(&G3Vo{^f>E+ZI%=9 zPQq8%0oZ5AP9e_ZVu6F#Ye$#VD>i16mnHxCMFqrfOh=CaCyv(X^J!5>EVm*-_(je1 zf_Ne*b)7cca$N8<_by6FK>^c2f0C{Ac>Gmpe{@;oeObk^z)b4CLVw=3C|qB23s%}= z7-(fl!*_z`LtM%VygLP<9D=>SgpcyFewXL*XnY4pzvD3w<*Ko^X8L|wn&A)fiJJ7} zmoE8oEAy4oYGGCy_}#LLrwGfCJb0a-)mT zP7B9wN~R z%ZKqOCf;|{>IrCRf_E$WcH@Pm0hcIVKh|(FIwMyhs(LN&OI)QR?>T~Fo0V5z#lqjS#IZG14x=BP59FZarEM-(n#w6Zp%)N^X?I>+V+p*TUPf3V7i zPv`WhJmpYz-e5I#A=Oow;SY0D_kIdU8$Xd<=i`;fmAc@r<QhKHd=Q zTdMFiOHJ?{H7voS1b!JIA`F3Ge_@O353B0#m_!Z~l}Q%opgjnx?6G}DkrX44=h)03 zD-@2VJ8m{;xlT6mKa&>S5S9ivMtJ8BQ?k3(MY3Ukk;^=ZTE&&b6eV>UsJW4Zz%OMP zI-Is|pMyAbH$DzS2dA+Jxh6q0XEz8NKvj$xA?fd&O)D@&a{TeE$Z`6^RHuYOLQ4bH`>NrFMz%^r>lyjY780%DS zW-R+!-H+2dZMKWp|EJ+ozjKqszvb+ zof->4yIH_f+PdOV$vTJ9f6!UK!#ncN=w`lFloW_IB|C(QT($;Xo1H5?tLpdk#x)t| zQ>`LPO=WGC=7d3ln}d1Jy^D8YK+ZzS!IHb8G)M5J=1{-RDt;n$s?0ssifBEhk;VZjni2^P& zndrz~`DQg3;qSntz*~q3U_NLmE0C0NH3c+bUR*1rOYpF{@f8X(DdK9h+t$~HC@O$%Bu>4uTjL zBW6eN-yzO1f4mtVM&cc9Rp)-@z>k7|t8Vfr@x`~rZu+!@!AhbrG3V!1is8)>)Oftg zW@_25bJ?7r4o;`MEuV-iQs<#tDa(p5#j&~mqZc)=E2z;`WMK9S*MS7Ma=q94I=uvl z>=Rpv8rrkZpeDD7`HdLLNf3wiBe3g%Z$-+8w-ZY^nOvgTMJzO!mmAoEymm}i{ z@nbzFEQXXYd=sG)P6DawS$%|!Se|a=iPfTr_-I%?MI1hBYkThCGtE-UK7D ztMqSJSmTZRsqj`@!Ou{bd^1L|SF)bGhSH?nV;jN*hwD#mh_x#+ZIqvt?T9*BXXb@M z@@pl6U$@2&q*#b3bO^t2TfIgf#$yy069l)!d$YD_MNvH_x=DlpiE#x!TBD7c4+Q~Rtw~d*D~+6X4sR^DyGsrehKJ^u646}s zij3RH&HD%wHDsAG965`DtZghdGYS(~e^9@_Z}9tG_l|2mJ*p^@Vrt%8e~v~8l9lub zgo@}%lopxMQ1SpQ`LI7-!-thZoOeL**Ob;AM>347^R=J_T07|#rs#Kd_w`rfL5%q& zR&NZPSv!$J2Dy#DHpFQBApHf#S)gXjdc_n0$iP>pfISN)48DhqH1LjRgFdWye}8Ys zWT$F>J;#~qXyn8T>ucf+?0amNO@b|X4If)e8IkZ1S*W^YpfJuC9&A2uc;^rGD>_A= zgW79`VQzkGj{i!kir*sagP(f`IB3g{lUqw(sYbIh2)g@HHEu^sq2b>pXG$!suM&Sw zOaC>e)-!Bxa(U#WqT@UYJLofxe{iAtw@A#DcGp@mGT8Gio6^W7r-aB3&g1PUs>u26 zdJ26LR3S#mrR(_H6CZ56Vy`jSKJjbV4#bu+uypZ$B`7201e_1@E(bq0i z)Brz%0bhE@fNekGO?{Eae~(P#xdSf} zt+bJlSe@EGQ+hQW3Xba|%O}41suHk1puvWih8w30%XHCD-&`}l0Mv|o;d=P-ydLwZ z28ozzh%A}qho2sd#-*iw^*$!r!@8Y}P$aMpNRedl?483B(irMhbr z&WT$}M~*4&C`sf?K>td)({`~|QP4_cCC_7VSFCsj^7{o8!ua)t%G{7pi)adF|SK+ux<`AD&z#v~r+H}?rIlMZg+4*UQpLLiu_LB+}{u~chXF+U) zq=VI(mHth%=stdaU)zTtq72!VJ;bq7Uo~o0fYGjzsV=V;EcmvO+8HIdv?xq%atRu} zL{GHo^TX-;!~#hzQ=E$wPzpl6X}>cT>e+p1jk$u}f6m?MXZJ(>q5~aE8M0u&kt+Aj z-oL&3cVmp(HTec_+f!4hqbM0u?HS*bw>YM6Ml}*Sw!YD2l&O0d6H_zBO>6S35(QqCh9au=p}ffLI7e;a7$QOgLS*|$#ki~r+e%>5C(T*YgI zUnx}JMJF>`S;w~8+I)@Pkzk*RLMVPhUUeOQ!1&UO%z`l`zL$onIz(yDtqK+*9?`ff z_VLf616bn2OzF$66@(Q|2A{Sz@+Ci@(^OIb6#%5Yjz27x1HVAgk=af2UaF8Jckzm% zf8@ayGFJNycS>NFi1T$rdp`2^S23(qzUir;>nQr#kM@Xf$6To$==Z;S%}eIX)<(Uf z#(X;s;$;^Jd5bg*m*Op}UY84!p3->u3=4aghhVnrM%>xamG~qwM%&Am%kS?uWQMC# zwa5-JZAHg>RF22AXwc(Bj|i~H+^Ubie~9eb?mT}(Whz61mu{ob*)g-MNDIyL5J;jT6699B;{%ehyBC#J6SKgc^lQaauh!X3D^xKf4(=P zS+HhVA{RCT;bAdlK=n)iup%O6aw*|OPC7ePGHY0V1BHI`5*MAIeDC%X)hwy|vud@p zd;3(5b*6r&8|kbhM-zu=7(QMi)|YV(5AX9Zx6g(tOjF zn%!fmW~xAs2MK4V&6-OKt2y2He=cfd@EGFY_f+L}Z~RXIst8Je`MKDsudLUJmaEg9 z`q|vx;)3qq|De66t-s=z4Ie%Xhq}7@X$(VnZP@s2d%9)zb4M@9-!2u>M)rYbPe@Fs zv+uspd9C5byC6$ACN(G?W|&5*8EDNs={t@Yr=dv&Fuw>ooDK#Pf(hYAf9VAw+r-N& zLkQ{Lw_y^*XquBbbv>b+uv_Zi`^J5b#jb|i`b`v+t=-rU5p^-GCuZ`#zEu&%aKKi> zGwc>DY(uUkdjCm`ya68A&yh%-2z#$Uphoceh6QfeEGN($r447Lcxv4`g{NRTJQ7r7 zOcSxF+#BS_N6`xr%7$bqf0e4uh+{#%(Xe}_jM0P1r3;EU5ShJLYnXCh9zxWYU{)k> zt=T}nQuoaf>LEbl@Cd=}#e#AVcU zL?w4N39khkf6&>^-A6RFlYfmU`QoRLk|oUHePd_5QQfUkKUp#GQsZXYSdKTm zZn2o#jPlQG?C0VuV9O*KHoT%Mdx+&zr^~skH2qt9I8R1{`@PQHK2NnSABJd@k1a2; z%Yd1GV>$Ja-($Cf&s!N;X!-UbL;JhfDml;MkaaP{eKt2h7=uT_VN5_)OU z5bJ56f7t1gQBLzz!7qdUCdkaNBjOSR?TY#7=bU1w6nMBFY#ucm--t>}7b- zLu3Tjc#6BfIUbEtu~ptwmPlIG)G3ThYbI7pFS5eb;4s+RFp112X zkO_VI?CVoNWRDnK%xpW<9BK3G9Jy)RWf47ie|Tc%FDRo?<)i6{1OHHfhX(Cb&}UoS z4)Lj=R2rXmC|rW`=__NHc7J~-u+hefCY(GC^{?$5Sm2Ipl-KR4h7A z&U8Ok@hU9&DKRS?Di#luN}7>P+TRMcTroXLDZDlz!Px%i+?GkBy3gDk=pnJ2Fn;>+ ze^M}0T-@pA`_V9ofb3`QY?;9zqKo&6TB;6f5uX0qq~2~Sp}fOKHl5hmy6FkP< zU&svYML{0HzuH%hh)*~5yRc#(HqnCke;s?BLxWTM31d+9GkX_pK-3)%VD_t04@~p-@ zp1U`&p*LJiAWinfk|lDp6Lq&ue=UgzPEbSTn2mM?d7mGyYy#z;Oq+JbmO9e zY0QR)S%S$=odM6wL7As4Pfzglt}CJw)`dW0fIKjFCKbKS?ii{Sr^G*Ne`)_K!PIO_ z44ph?DfZqMT1d1{qpU39IZE%$Vx|{yds2ZgioIi`8Fxc5W|szyYt=c;&v@4I((Q|D zzv_~}BSuKeDo@7l&O)wIJz5|(0vqEAKj~TfKu@hbJ7p2=p0StsiCV9c8}CMV!{R&w zNwz1U)Gig{HW4Ru^R>~wf2D_Ar28D%OhU=xQk)rF=xtakM?q@=1Msps-KeYXE_&94 z&#IMP7D-iOB9>6EBg|Bm^@9*@&?Wr`NxU2xkVZ=bO%jm zuRec?7IzU(q|13#q+)7s)efZslYPTnOaEQgWH^VJoOf&q6HLnxe^VOfw+Np0XnMx@ z4U#IkkgvQ(FMmL)oQvnCgOAUfFw)F?=Dg|B?L@Kbh<^u-kW^`$@-VJN7XY|UArI*iFCJnm)_%rT8) zhk5a(%`RRZI=Nqws7;OE886R)E@@^&7TX>!E5p_^g9S6Ce^B`g1)aF9LwNG`xRqBG zm?4!67QgJi6w@%qmzuNa9p%+{`$~=?dd=6#GJ!nWa;AK>9(n^x3S%!RJH0BlcnKalRo|Y%(%#6N8e*skn0951UGo0nVIM#YGT*nY} zfyN>xjWjsHhZ4+uU*WaZyX_gKlZ+CIyg;G?7^2`I(gPwhrW2~2>O{ZCzO8K1XU{r1 z>pL|=k*3l}&c|RdKBLtpq3aCbD^v0JtJw9n@{@(prmokV_1a;Ps>k~zbT=gw?vT*% zOo58rf0%j1Ri|c{ho6hDkumJ8aOJ~ERBFvx;kAtXXS3q*42%3&Gijla#M^>sNXWT* zMG80r)2=>W&5%pS{9-1uxd2pHX3khnp75V+%x|1Vlgg`5!mOg2=Rh2Aa$;)`=m=16 zcwMXe_Qkt4A4FU8Rq(}dmvFct{h+OX6(2W)NP}w4z}ZfsBG$r+^L+broCRiWs4^N-W;RjHc6Fni@F36i_lXLz47K2l+>Af z$^cdN1}#iw2pBCzDu`3^5+5CzeaqdT%G+RqdCE~2RtG;1J^?Fd1#f6#p^$7hh=0b$ zi6*9zL`fx?DpTC=r8)moTZZR0@U4)Cf2R1HsDi{Gx_Nv1UYimA_yVFMFQPQv9^f$$ zde(`$U7ZSZY5B4=0jjulj8baKe$j`17f7DM$x9RDpWC8{aKD)UnhB{vNYpuFtO>t+ z@x{xur@}_qx?MAF8KXKqM`(aM2O@6LNlsi@aIg$6$GvRbXmyCuH|h099p?|{e?2st zFUYR7CZtZc5^cy|0IA?OA#+er3EUgJM(eqU0*M$`)BAN<{=$`_cth zKi5-!m!Y~9jn7(n;q5A*)1vl9I6j2T%njgtE%c{&_jz|Q3Dv8l_vfa5A={34D@ApT zP9wf%A#0V|z91|1^Z6-MR45aU!&$J*@$!)QtMx^t1qVnMUVV4j#S8@rf9??Z-seY3 zilCkPTuI*aBgZxOJ81~tMwn|6kWPmm=vA50=+A(vC3Us5A-1_&V@<>n)Z$GLPSFaz z;LIbHZutj@`ROszGImIV}+=by8eB>y<+PCz(l=qod-O$cg9SrMRr#lYUigqnRY zlTT6>XN4dZ_#=I1f3DllMttkHBJ{LP%01>Dv|APjSidh=0T1ocejg_Rp-wSHy~XH@tEw2feeHw_*iA$0#nSJ=E{ztK6PBj%DcB^DZG_#m z-PjR8BN`!ge}~gPo|d&OQB!KZb^pdn$rILN>$(&t)(?4gK#-c6~pyWlnTzh$j(g?H?Ezjjm2)w zyV7!cfm43snn=*nw%gYT%?f;~^9&q8hHoo@ro*|L)A0>v_JNO6mEgLK!EnZoN;HDH zqJFD%e*!Z23eK-FLCyJwtgEH19FJiaGq1zwgvuO$?8qLSdZl#f z;BRkt_eiA9eQ?D@@YlNf2!caA_kNn~i-Z*7wyR|X&N+HmojbyZl^ztfl~3D!>x{=1 z3Sg=ke_qfSS^2@r^>Z;PxsG`_!I!tO=!D zw=Q_40a847^!EWg2o?Xw$8QfLB-@r|Vh9Td+9YYH#GMmw@+?AJjL-#Uw~+?VpM9{I ze`bSjku+6f3V_6V;Lk-j_NlQjYw!MTp#YW6*-g-=K7=G zr5sqyolKB$W~pJfl%ua=b$#zR<0s1BL?a8ZdN0UecmO$I92kxyd(K$imgnJ1rKFGew%qKvDOVue^Y2>hFg|wi|)Vt z2uINwY6_~e$3<@RXm?^b<$-ip^!AaB8z^y=b@-0+iYWdhZClZj^l82p{! ziwsAGR}!1A_CpUd%a(@{1JY|#oK)2J^eMdlg+cda=t7qDKN0mb4>WHTD)T>($)cfh zfn|6y(W`{uP8@-1YnZz-t~h55Ykv59w1R@TxIX2Y*x}fBMJa&9a7X|9=0X zmj29fV-7#XfaXcOEu<3yMeVK($_?K870J8Z!tGA@+waTL-+VH82g|ki9IvA$u1PnT zGW_3pSCsuGMqRH_iSsi93mtNoq6ut2RR?=|YeWkF5jU~+bClg&%*w~cml*obzs4!0 zcgT;3Esz%&4$f6*f82tz7{9R=YmoE%4Y+z>^|ERz=U=+%{Rt3ymd}4+j;pPc6D}$5 zt_e*VSCt7Nv9Cg6`;d8$Nl8@$i(M z;C(UNX%|L?dI1wcG)ob5mHke^-0vq@>}*Z4pOIf7x0LA(e>K^9GqDX%cGfm=bM@r_ zL={7@6jHLn4JFDz4VsvN5(PU7q!&I-@3B82^2iPfDJ$KAHPxTKW9V0PFEt-&&j}^= z?T$H9cVeF&22UdLn|6-$L4RX0|M81}h}c-BmaM#KiPS_rhsu*MZ88VrOGDk03ASa@ zp}$b{PF$$Hf6b74!)V^=s8cgm&1pRVj3rU(Hwp>Yl4)hP=EjLqp&SYC=e5=VFJqo7TUB4hPbY3xbMq%EV!y$dQHhdEgTR@gZQe*p$_%12?UJ7D028+3-<*|Je+ z@W7#}iFO|K{sIW1{Qk0s{&yfrLtLyY{5EK{92wyi9|vNX&f^T@Rs8=8A`IR0!K{Fg zMzOd|!a~iyNE!if44Ew5&oB)(G&t?KO(nQnFZu?)&OJx) zist}ATv?V%aBF3ry=v2n+oU9YKq!1^Uc~5i6L0-+niZ>3BF})1?_o+odi!CF2w=V( zF8*}6&GbADM^$;G$xGwp3VV`bpa<7``%URce}hmd{dnR&HK=g)x=Q&wO<6?l`o@s| z92eR>OKZxGr~ZjhMNY$K0|>1$YtU-*Jy)+VHYT6x5pL`>m(tiv8TO99aV7f#k_2X{ zM}Fyyt=A8M*pjr;+OWF1W+8ROE>^iknbdWo{#Y{BF7BG@lK8E)feVK(RXvS5qcPrQ<>7<94P#j+dBR5k$xM=_UZsx~V6t z!GonZB_8980EHh8gEMxU`2mKFI+P_QuO$c-$TS=@u8J2=Ie~MC#R;K&u^8hYZ>RIW+MV{(ZDchsnV0Cs8 z4=jzK_Sunemx=>@EUAKja02={LPX8hGCA~sa9kZ1DUtjgsCwYw3IdCm`H-~G6%{

h@;e{$>* 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 diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index 071ae143..e579309a 100755 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -39,6 +39,8 @@ init_rgee <- function() { ) } +library(mapview) + user <- "data.colec.fbf" skip_if_no_credentials(user) skip_if_no_pypkg() From 8fc1de48893f76170b335a04ee6455b3c27988cf Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Fri, 26 Jun 2020 07:36:19 -0500 Subject: [PATCH 02/15] fix doi and tidyverse --- paper/paper.bib | 2 +- paper/paper.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paper/paper.bib b/paper/paper.bib index fb1d59fe..7143cab1 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -25,7 +25,7 @@ @article{wickham2011ggplot2 number={2}, pages={180--185}, year={2011}, - doi={10.1016/j.rse.2017.06.031}, + doi={10.1002/wics.147}, publisher={Wiley Online Library} } diff --git a/paper/paper.md b/paper/paper.md index 2eeb5961..54bb23c2 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -138,6 +138,7 @@ rgee can extract values from `ee.Image` and `ee.ImageCollection` objects at a ce ```r library(ggplot2) library(tidyr) +library(dplyr) library(rgee) library(sf) From 7b0edec6c0bff030bc18b2e453c3c1e8afd098ba Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Mon, 29 Jun 2020 12:30:30 -0500 Subject: [PATCH 03/15] rgee 1.0.0 :rocket: :tada: --- NEWS.md | 12 ++++++ R/Map.R | 2 +- R/ee_Initialize.R | 13 +++--- R/ee_image.R | 1 - R/ee_install.R | 27 ++++++++++++ R/sf_as_ee.R | 77 ++++++++++++++++++++-------------- R/utils-upload.R | 80 +++++++++++++----------------------- man/gcs_to_ee_table.Rd | 12 +++++- man/sf_as_ee.Rd | 25 +++++------ tests/testthat/test-upload.R | 9 ++-- 10 files changed, 152 insertions(+), 106 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6c9ae5a3..26b11f10 100755 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,18 @@ vignette: > %\VignetteIndexEntry{NEWS} %\VignetteEncoding{UTF-8} --- +# rgee 1.0.0 +- Removed `check_ring_dir` argument from `sf_as_ee`. Now `rgee` expect that users fix potential geometry problems before upload geometries to Earth Engine assets. +- The message "Initialization aborted" was removed from `ee_Initialize` and replaced by `ee_search_init_message`. It will permit to `rgee` to know if the user accepted to create the global var "EARTHENGINE_INIT_MESSAGE" without need to restart the R session. +- Fix a bug in `ee_as_sf`, new test were also added. The argument `command_line_tool_path` was added to give users the option to set the path of the Earth Engine command linetool. It is useful to upload files using Google Cloud Storage. New diagnostic message were added. +- mejorar upload test +- mensajes a la hora de subir informacion +- cambiar el earthengine al que se instala con el local +- error es ee_as_stars +- mejorar policy +- agregar test argumt a todaas las funciones ee_manage que se puedan strict +- agregar los cambios de los reviewer + # rgee 0.6.2 - Earth Engine Python API updated to 0.1.226. - Fix some typos. diff --git a/R/Map.R b/R/Map.R index 30ede6db..5c951846 100644 --- a/R/Map.R +++ b/R/Map.R @@ -191,7 +191,7 @@ ee_centerObject <- function(eeObject, zoom = NULL, maxError = ee$ErrorMargin(1)) { if (any(class(eeObject) %in% "ee.featurecollection.FeatureCollection")) { - message("NOTE: Center got from the first element.") + message("NOTE: Center obtained from the first element.") eeObject <- ee$Feature(eeObject$first()) } diff --git a/R/ee_Initialize.R b/R/ee_Initialize.R index b258cce4..84c7b107 100644 --- a/R/ee_Initialize.R +++ b/R/ee_Initialize.R @@ -57,8 +57,8 @@ ee_Initialize <- function(email = NULL, gcs = FALSE, quiet = FALSE) { # Message for new user - init_rgee_message <- Sys.getenv("EARTHENGINE_INIT_MESSAGE", unset = NA) - if (is.na(init_rgee_message)) { + init_rgee_message <- ee_search_init_message() + if (!init_rgee_message) { text <- paste( crayon::bold("Welcome to the Earth Engine client library for R!"), "----------------------------------------------------------------", @@ -82,12 +82,13 @@ ee_Initialize <- function(email = NULL, repeat { ch <- tolower(substring(response, 1, 1)) if (ch == "y" || ch == "") { - message("Initialization aborted.") - return(FALSE) + # message("Initialization aborted.") + # return(FALSE) + break } else if (ch == "n") { - message("Initialization aborted.") + # message("Initialization aborted.") ee_install_set_init_message() - return(FALSE) + break } else { response <- readline("Please answer yes or no: ") } diff --git a/R/ee_image.R b/R/ee_image.R index 595614a9..812c2562 100644 --- a/R/ee_image.R +++ b/R/ee_image.R @@ -574,7 +574,6 @@ ee_image_local <- function(image, ) region_fixed <- sf_region_gridded %>% sf_as_ee( - check_ring_dir = TRUE, evenOdd = is_evenodd, proj = img_crs, geodesic = is_geodesic diff --git a/R/ee_install.R b/R/ee_install.R index 9db285d9..463fb051 100755 --- a/R/ee_install.R +++ b/R/ee_install.R @@ -404,3 +404,30 @@ ee_install_upgrade <- function(version = NULL) { rstudioapi::restartSession(), cat("Restart R session to see changes.\n")) } + + +#' Search if EARTHENGINE_INIT_MESSAGE is set +#' @noRd +ee_search_init_message <- function() { + home <- Sys.getenv("HOME") + renv <- file.path(home, ".Renviron") + if (!file.exists(renv)) { + return(FALSE) + } + + con <- file(renv, open = "r+") + lines <- as.character() + ii <- 1 + + while (TRUE) { + line <- readLines(con, n = 1, warn = FALSE) + if (length(line) == 0) { + break() + } + lines[ii] <- line + ii <- ii + 1 + } + close(con) + # Find if EARTHENGINE_INIT_MESSAGE is set + any(grepl("EARTHENGINE_INIT_MESSAGE", lines)) +} diff --git a/R/sf_as_ee.R b/R/sf_as_ee.R index abc37f73..cca11de0 100755 --- a/R/sf_as_ee.R +++ b/R/sf_as_ee.R @@ -10,7 +10,6 @@ #' will be monitored. #' @param assetId Character. Destination asset ID for the uploaded file. Ignore #' if \code{via} argument is "getInfo". -#' @param check_ring_dir Logical. See \code{st_read} for details. #' @param proj Integer or character. Coordinate Reference System (CRS) for the #' EE object, defaults to "EPSG:4326" (x=longitude, y=latitude). #' @param geodesic Logical. Ignored if \code{x} is not a Polygon or LineString. @@ -26,6 +25,9 @@ #' If unspecified, defaults to TRUE. #' @param bucket Character. Name of the bucket (GCS) to save intermediate files #' (ignore if \code{via} is not defined as "gcs_to_asset"). +#' @param command_line_tool_path Character. Path to the Earth Engine command line +#' tool. If NULL, rgee assumes is saved in the same path that your Python +#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite A boolean argument which indicates indicating #' whether "filename" should be overwritten. By default TRUE. #' @param quiet Logical. Suppress info message. @@ -55,12 +57,6 @@ #' See \href{https://developers.google.com/earth-engine/importing}{Importing #' table data} documentation for more details. #' -#' Earth Engine is strict on polygon ring directions (outer ring -#' counter-clockwise, and the inner one clockwise). If `check_ring_dir` is TRUE, -#' it checks every ring, and revert them if necessary, to counter clockwise for -#' outer, and clockwise for inner (hole) ones. By default, this is FALSE because -#' it is an expensive operation. -#' #' @examples #' \dontrun{ #' library(rgee) @@ -80,7 +76,10 @@ #' ncol = 2, #' byrow = TRUE) %>% #' list() %>% -#' st_polygon() +#' st_polygon() %>% +#' st_sfc(crs="EPSG:4326") %>% +#' st_sf() +#' #' holePoly <- sf_as_ee(x = toy_poly, evenOdd = FALSE) #' #' # Create an even-odd version of the polygon. @@ -99,10 +98,11 @@ #' x = toy_poly, #' overwrite = TRUE, #' assetId = assetId, -#' via = 'getInfo_to_asset') +#' via = "getInfo_to_asset") #' #' # 3. Upload large geometries to EE asset -#' ee_Initialize(gcs = TRUE) +#' ee_Initialize("data.colec.fbf", gcs = TRUE) +#' #' assetId <- sprintf("%s/%s", ee_get_assethome(), 'toy_poly_gcs') #' eex <- sf_as_ee( #' x = toy_poly, @@ -112,38 +112,38 @@ #' monitoring = FALSE, #' via = 'gcs_to_asset' #' ) +#' ee_monitoring() #' } #' @export sf_as_ee <- function(x, via = 'getInfo', assetId = NULL, bucket = NULL, + command_line_tool_path = NULL, overwrite = TRUE, monitoring = TRUE, - check_ring_dir = FALSE, evenOdd = TRUE, proj = 4326, geodesic = NULL, quiet = FALSE, ...) { - # Read geometry - eex <- ee_st_read( - x = x, - proj = proj, - check_ring_dir = check_ring_dir, - quiet = TRUE - ) + if (!requireNamespace("sf", quietly = TRUE)) { + stop("package sf required, please install it first") + } + + if (!any(class(x) %in% "sf")) { + stop("x needs to be an object of class sf") + } # geodesic is null? if (is.null(geodesic)) { - is_geodesic <- sf::st_is_longlat(eex) + is_geodesic <- sf::st_is_longlat(x) } else { is_geodesic <- geodesic } - - if (is.na(sf::st_crs(eex)$epsg)) { + if (is.na(sf::st_crs(x)$epsg)) { stop( "The x EPSG needs to be defined, use sf::st_set_crs to", " set, replace or retrieve." @@ -151,23 +151,23 @@ sf_as_ee <- function(x, } # Transform x according to proj argument - eex_proj <- sf::st_crs(proj)$epsg - eex <- sf::st_transform(eex, eex_proj) - eex_proj <- sprintf("EPSG:%s", sf::st_crs(eex)$epsg) + x_proj <- sf::st_crs(proj)$epsg + x <- sf::st_transform(x, x_proj) + x_proj <- sprintf("EPSG:%s", sf::st_crs(x)$epsg) if (via == "getInfo") { # sf to geojson ee_sf_to_fc( - x = eex, - proj = eex_proj, + x = x, + proj = x_proj, geodesic = is_geodesic, evenOdd = evenOdd ) } else if (via == "getInfo_to_asset") { # sf to geojson sf_fc <- ee_sf_to_fc( - x = eex, - proj = eex_proj, + x = x, + proj = x_proj, geodesic = is_geodesic, evenOdd = evenOdd ) @@ -180,10 +180,13 @@ sf_as_ee <- function(x, if (is.null(assetId)) { stop('assetId was not defined') } + + # Verify is the EE assets path is valid assetId <- ee_verify_filename( path_asset = assetId, strict = FALSE ) + # geojson to assset ee_task <- ee_table_to_asset( collection = sf_fc, @@ -202,17 +205,31 @@ sf_as_ee <- function(x, if (is.null(bucket)) { stop("Cloud Storage bucket was not defined") } + + if (is.null(command_line_tool_path)) { + command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + } + shp_dir <- sprintf("%s.shp", tempfile()) - geozip_dir <- ee_utils_shp_to_zip(eex, shp_dir) + + # From sf to .shp + message("1. Converting sf object to shapefile ... saving in /tmp") + geozip_dir <- ee_utils_shp_to_zip(x, shp_dir) + + # From local to gcs + message("2. From local to GCS") gcs_filename <- local_to_gcs( x = geozip_dir, bucket = bucket, quiet = quiet ) + + message("3. From GCS to Earth Engine") gcs_to_ee_table( gs_uri = gcs_filename, overwrite = overwrite, - assetId = assetId + assetId = assetId, + command_line_tool_path = command_line_tool_path ) if (isTRUE(monitoring)) { ee_monitoring() diff --git a/R/utils-upload.R b/R/utils-upload.R index 5bf9acd9..313e74dd 100755 --- a/R/utils-upload.R +++ b/R/utils-upload.R @@ -84,14 +84,15 @@ local_to_gcs <- function(x, } } -#' Move a zipped shapefile from GCS to their EE Assets -#' #' Move a zipped shapefile from GCS to their EE Assets #' #' @param gs_uri Character. It represents the full name of an #' zipped shapefile in a GCS bucket. #' @param assetId Character. What to call the file once uploaded #' to their Earth Engine Assets +#' @param command_line_tool_path Character. Path to the Earth Engine command line +#' tool. If NULL, rgee assumes is saved in the same path that your Python +#' environment. #' @param overwrite Logical. If TRUE, the assetId will be overwritten if #' it exists. #' @param quiet Logical. Suppress info message. @@ -140,16 +141,20 @@ local_to_gcs <- function(x, #' @export gcs_to_ee_table <- function(gs_uri, assetId, + command_line_tool_path = NULL, overwrite = FALSE, quiet = FALSE) { + if (is.null(command_line_tool_path)) { + command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + } + + command = sprintf( + "%s/earthengine upload table --asset_id %s %s", + command_line_tool_path, assetId, gs_uri + ) + if (!quiet) { - cat( - blue('Uploading'), - green(gs_uri), - blue('to'), - green(assetId), - blue('... please wait\n') - ) + message("Running the OS command:", command) } if (isTRUE(overwrite)) { @@ -159,14 +164,23 @@ gcs_to_ee_table <- function(gs_uri, ) } - system( - command = sprintf( - "earthengine upload table --assetId %s '%s'", - assetId, gs_uri - ), + upload_state <- system( + command = command, ignore.stdout = TRUE, ignore.stderr = TRUE ) + + if (upload_state != 0) { + stop( + sprintf( + "An error occurs when %s try to upload %s to %s", + bold("gcs_to_ee_table"), bold(gs_uri), bold(assetId) + ), + ". Please make sure that you set the ", + bold("command_line_tool_path"), + " argument correctly." + ) + } assetId } @@ -373,44 +387,6 @@ gcs_to_ee_image <- function(x, ee$FeatureCollection(fc) } -#' Pass a character, sfg, sfc to sf -#' @noRd -ee_st_read <- function(x, proj = 4326, check_ring_dir = FALSE, quiet = FALSE) { - if (!requireNamespace("sf", quietly = TRUE)) { - stop("package sf required, please install it first") - } - if (any(class(x) %in% 'sf')) { - x$geometry <- sf::st_sfc(x$geometry, check_ring_dir = check_ring_dir) - x - } else if (any(class(x) %in% 'sfg')) { - if (is.null(proj)) { - proj <- 4326 - } - sf::st_sf( - index = 1, - geometry = sf::st_sfc( - x, - crs = 4326, - check_ring_dir = check_ring_dir - ) - ) - } else { - result <- tryCatch( - expr = sf::st_sf(index = 1, - geometry = x, - check_ring_dir = check_ring_dir), - error = function(e) sf::st_read(dsn = x, - stringsAsFactors = FALSE, - check_ring_dir = check_ring_dir, - quiet = quiet) - ) - if (ncol(result) == 1) { - result$index <- seq_len(nrow(result)) - } - result - } -} - #' Pass a character or stars object to stars-proxy #' @noRd ee_as_proxystars <- function(x, temp_dir = tempdir()) { diff --git a/man/gcs_to_ee_table.Rd b/man/gcs_to_ee_table.Rd index e5ddcd0e..2ba17062 100644 --- a/man/gcs_to_ee_table.Rd +++ b/man/gcs_to_ee_table.Rd @@ -4,7 +4,13 @@ \alias{gcs_to_ee_table} \title{Move a zipped shapefile from GCS to their EE Assets} \usage{ -gcs_to_ee_table(gs_uri, assetId, overwrite = FALSE, quiet = FALSE) +gcs_to_ee_table( + gs_uri, + assetId, + command_line_tool_path = NULL, + overwrite = FALSE, + quiet = FALSE +) } \arguments{ \item{gs_uri}{Character. It represents the full name of an @@ -13,6 +19,10 @@ zipped shapefile in a GCS bucket.} \item{assetId}{Character. What to call the file once uploaded to their Earth Engine Assets} +\item{command_line_tool_path}{Character. Path to the Earth Engine command line +tool. If NULL, rgee assumes is saved in the same path that your Python +environment.} + \item{overwrite}{Logical. If TRUE, the assetId will be overwritten if it exists.} diff --git a/man/sf_as_ee.Rd b/man/sf_as_ee.Rd index 25de11f0..90c6c187 100644 --- a/man/sf_as_ee.Rd +++ b/man/sf_as_ee.Rd @@ -9,9 +9,9 @@ sf_as_ee( via = "getInfo", assetId = NULL, bucket = NULL, + command_line_tool_path = NULL, overwrite = TRUE, monitoring = TRUE, - check_ring_dir = FALSE, evenOdd = TRUE, proj = 4326, geodesic = NULL, @@ -31,6 +31,10 @@ if \code{via} argument is "getInfo".} \item{bucket}{Character. Name of the bucket (GCS) to save intermediate files (ignore if \code{via} is not defined as "gcs_to_asset").} +\item{command_line_tool_path}{Character. Path to the Earth Engine command line +tool. If NULL, rgee assumes is saved in the same path that your Python +environment (ignore if \code{via} is not defined as "gcs_to_asset").} + \item{overwrite}{A boolean argument which indicates indicating whether "filename" should be overwritten. By default TRUE.} @@ -38,8 +42,6 @@ whether "filename" should be overwritten. By default TRUE.} \code{getInfo_to_asset} or \code{gcs_to_asset}. If TRUE the exportation task will be monitored.} -\item{check_ring_dir}{Logical. See \code{st_read} for details.} - \item{evenOdd}{Logical. Ignored if \code{x} is not a Polygon. If TRUE, polygon interiors will be determined by the even/odd rule, where a point is inside if it crosses an odd number of edges to reach a point at infinity. @@ -87,12 +89,6 @@ the function \code{gcs_to_ee_table} the ESRI shapefile will be loaded to their EE Assets. See \href{https://developers.google.com/earth-engine/importing}{Importing table data} documentation for more details. - -Earth Engine is strict on polygon ring directions (outer ring -counter-clockwise, and the inner one clockwise). If \code{check_ring_dir} is TRUE, -it checks every ring, and revert them if necessary, to counter clockwise for -outer, and clockwise for inner (hole) ones. By default, this is FALSE because -it is an expensive operation. } \examples{ \dontrun{ @@ -113,7 +109,10 @@ toy_poly <- matrix(data = c(-35,-10,-35,10,35,10,35,-10,-35,-10), ncol = 2, byrow = TRUE) \%>\% list() \%>\% - st_polygon() + st_polygon() \%>\% + st_sfc(crs="EPSG:4326") \%>\% + st_sf() + holePoly <- sf_as_ee(x = toy_poly, evenOdd = FALSE) # Create an even-odd version of the polygon. @@ -132,10 +131,11 @@ eex <- sf_as_ee( x = toy_poly, overwrite = TRUE, assetId = assetId, - via = 'getInfo_to_asset') + via = "getInfo_to_asset") # 3. Upload large geometries to EE asset -ee_Initialize(gcs = TRUE) +ee_Initialize("data.colec.fbf", gcs = TRUE) + assetId <- sprintf("\%s/\%s", ee_get_assethome(), 'toy_poly_gcs') eex <- sf_as_ee( x = toy_poly, @@ -145,5 +145,6 @@ eex <- sf_as_ee( monitoring = FALSE, via = 'gcs_to_asset' ) +ee_monitoring() } } diff --git a/tests/testthat/test-upload.R b/tests/testthat/test-upload.R index 8060c8d6..adf1806a 100644 --- a/tests/testthat/test-upload.R +++ b/tests/testthat/test-upload.R @@ -59,12 +59,15 @@ test_that("gcs_to_ee_table ", { assetId <- sprintf("%s/%s",ee_get_assethome(),'sf_nc') zipfile <- ee_utils_shp_to_zip(nc) gs_uri <- local_to_gcs(x = zipfile, - bucket = 'rgee_dev') + bucket = 'rgee_dev') gcs_to_ee_table( gs_uri = gs_uri, - assetId = assetId + assetId = assetId, + overwrite = TRUE, + command_line_tool_path = dirname(Sys.getenv("EARTHENGINE_PYTHON")) ) - #ee_monitoring() + + ee_monitoring() ee_sf_01 <- ee$FeatureCollection(assetId) expect_s3_class(object = ee_sf_01, class = "ee.featurecollection.FeatureCollection") From 3c57bf68c5a2c8aec219e580a572d299e9e233c1 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sat, 4 Jul 2020 23:24:06 -0500 Subject: [PATCH 04/15] rgee 1.0.0 Read NEW.rd to get details! --- .Rbuildignore | 2 +- .github/decrypt_secret.sh | 8 +- .github/workflows/R-CMD-check.yaml | 78 +++--- DESCRIPTION | 6 +- NAMESPACE | 1 + NEWS.md | 37 ++- R/Map.R | 2 +- R/ee_Initialize.R | 126 +++++---- R/ee_as_sf.R | 11 +- R/ee_as_thumbnail.R | 5 +- R/ee_check.R | 1 + R/ee_clean.R | 10 +- R/ee_download.R | 101 +++---- R/ee_extract.R | 8 +- R/ee_image.R | 251 ++++------------- R/ee_install.R | 38 ++- R/ee_manage.R | 105 ++++--- R/ee_module.R | 2 +- R/ee_utils.R | 28 -- R/print.R | 13 +- R/raster_as_ee.R | 163 +++++++++++ R/sf_as_ee.R | 133 +++++---- R/utils-upload.R | 260 ++++++++++++------ README.md | 12 +- inst/WORDLIST | 2 + inst/python/sf_as_ee.py | 10 +- man/Map.Rd | 2 +- man/ee.Rd | 2 +- man/ee_Initialize.Rd | 25 +- man/ee_as_raster.Rd | 34 +-- man/ee_as_sf.Rd | 10 +- man/ee_as_stars.Rd | 34 +-- man/ee_as_thumbnail.Rd | 3 +- man/ee_clean_credentials.Rd | 4 +- man/ee_clean_pyenv.Rd | 4 +- man/ee_drive_to_local.Rd | 12 +- man/ee_gcs_to_local.Rd | 26 +- man/ee_image_info.Rd | 6 +- man/ee_image_to_asset.Rd | 5 +- man/ee_image_to_drive.Rd | 5 +- man/ee_image_to_gcs.Rd | 32 +-- man/ee_install.Rd | 4 +- man/ee_install_set_pyenv.Rd | 4 +- man/ee_install_upgrade.Rd | 8 +- man/ee_manage-tools.Rd | 41 +-- man/ee_table_to_asset.Rd | 3 +- man/ee_table_to_drive.Rd | 2 +- man/ee_table_to_gcs.Rd | 22 +- man/ee_utils_shp_to_zip.Rd | 28 -- man/gcs_to_ee_image.Rd | 63 +++-- man/gcs_to_ee_table.Rd | 50 ++-- man/local_to_gcs.Rd | 11 +- man/print-EarthEngineMap-method.Rd | 14 - man/print.ee.computedobject.ComputedObject.Rd | 6 +- man/raster_as_ee.Rd | 67 +++-- man/rgee-package.Rd | 2 +- man/sf_as_ee.Rd | 95 ++++--- man/stars_as_ee.Rd | 59 ++-- paper/paper.md | 18 +- pkgdown/_pkgdown.yml | 2 + rgee.Rproj | 2 +- tests/testthat.R | 47 ++++ tests/testthat/test-Initialize.R | 47 +--- tests/testthat/test-check.R | 45 +-- tests/testthat/test-clean.R | 68 +---- tests/testthat/test-date.R | 45 --- tests/testthat/test-download.R | 68 +---- tests/testthat/test-ee_as_sf.R | 47 +--- tests/testthat/test-ee_extract.R | 47 ---- tests/testthat/test-ee_help.R | 44 --- tests/testthat/test-ee_image.R | 50 +--- tests/testthat/test-eeprint.R | 83 ++---- tests/testthat/test-imagecollection.R | 50 +--- tests/testthat/test-install.R | 44 --- tests/testthat/test-manage.R | 135 +++------ tests/testthat/test-map.R | 50 ---- tests/testthat/test-search.R | 44 --- tests/testthat/test-sf_as_ee.R | 144 ++++------ tests/testthat/test-thumbnail.R | 54 +--- tests/testthat/test-upload.R | 61 +--- 80 files changed, 1328 insertions(+), 1963 deletions(-) create mode 100644 R/raster_as_ee.R delete mode 100644 man/print-EarthEngineMap-method.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 47097309..62a9238a 100755 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,4 +17,4 @@ ^vignettes\setup.Rmd ^vignettes\BestPractices.Rmd ^vignettes\thumb_down.png -^vignettes\thumb_up.png \ No newline at end of file +^vignettes\thumb_up.png diff --git a/.github/decrypt_secret.sh b/.github/decrypt_secret.sh index 6cedee77..16c2cf8b 100755 --- a/.github/decrypt_secret.sh +++ b/.github/decrypt_secret.sh @@ -1,15 +1,15 @@ #!/bin/sh # Decrypt the file -mkdir -p $HOME/.config/earthengine/data.colec.fbf/ +mkdir -p $HOME/.config/earthengine/ndef/ # --batch to prevent interactive command --yes to assume "yes" for questions gpg --quiet --batch --yes --decrypt --passphrase="$RGEE_SECRET_PASSPHRASE" \ ---output $HOME/.config/earthengine/data.colec.fbf/GCS_AUTH_FILE.json \ +--output $HOME/.config/earthengine/ndef/GCS_AUTH_FILE.json \ ./tests/credentials/GCS_AUTH_FILE.json.gpg gpg --quiet --batch --yes --decrypt --passphrase="$RGEE_SECRET_PASSPHRASE" \ ---output $HOME/.config/earthengine/data.colec.fbf/cd26ed5dc626f11802a652e81d02762e_data.colec.fbf@gmail.com ./tests/credentials/cd26ed5dc626f11802a652e81d02762e_data.colec.fbf@gmail.com.gpg +--output $HOME/.config/earthengine/ndef/cd26ed5dc626f11802a652e81d02762e_data.colec.fbf@gmail.com ./tests/credentials/cd26ed5dc626f11802a652e81d02762e_data.colec.fbf@gmail.com.gpg gpg --quiet --batch --yes --decrypt --passphrase="$RGEE_SECRET_PASSPHRASE" \ ---output $HOME/.config/earthengine/data.colec.fbf/credentials ./tests/credentials/credentials.gpg \ No newline at end of file +--output $HOME/.config/earthengine/ndef/credentials ./tests/credentials/credentials.gpg \ No newline at end of file diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 7ad25512..da56bb0c 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,10 +1,10 @@ on: push: branches: - - master + - dev pull_request: branches: - - master + - dev name: R-CMD-check @@ -18,61 +18,48 @@ jobs: fail-fast: false matrix: config: - - {os: windows-latest, r: '3.6', platform: windows-x86_64} - - {os: windows-latest, r: 'release', platform: windows-x86_64} - - {os: macOS-latest, r: '3.6', platform: osx-x86_64-clang} - - {os: macOS-latest, r: 'release', platform: osx-x86_64-clang} - - {os: ubuntu-latest, r: '3.6', platform: linux-x86_64-ubuntu-gcc} - - {os: ubuntu-latest, r: 'release', platform: linux-x86_64-ubuntu-gcc} + - {os: windows-latest, r: '3.6'} + #- {os: macOS-latest, r: '3.6'} + #- {os: macOS-latest, r: 'devel'} + #- {os: ubuntu-16.04, r: '3.2', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + #- {os: ubuntu-16.04, r: '3.3', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + #- {os: ubuntu-16.04, r: '3.4', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + #- {os: ubuntu-16.04, r: '3.5', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + #- {os: ubuntu-16.04, r: '3.6', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} env: # otherwise remotes::fun() errors cause the build to fail. Example: Unavailability of binaries R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - CRAN: ${{ matrix.config.cran }} + RSPM: ${{ matrix.config.rspm }} + # prevent rgl issues because no X11 display is available + RGL_USE_NULL: true # macOS >= 10.15.4 linking SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk steps: - - name: CHECKOUT CODE - uses: actions/checkout@v2 - - - name: SETUP PYTHON + - uses: actions/checkout@v2 + - name: Setup python uses: actions/setup-python@v1 with: - python-version: 3.6 - - - name: SETUP R - uses: r-lib/actions/setup-r@master + python-version: '3.6' + architecture: 'x64' + - uses: r-lib/actions/setup-r@master with: r-version: ${{ matrix.config.r }} - - name: SETUP PANDOC - uses: r-lib/actions/setup-pandoc@master + - uses: r-lib/actions/setup-pandoc@master - - name: FIND ALL R PACKAGES DEPENDECIES OF RGEE + - name: Query dependencies run: | install.packages('remotes') - install.packages('knitr') - install.packages('pillar') # winbug - install.packages('vctrs') saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) shell: Rscript {0} - - name: RETICULATE_PYTHON SETTING + - name: Install Python dependencies + if: runner.os == 'Windows' run: | - reticulate_dir <- path.expand("~/.Renviron") - fileConn <- file(reticulate_dir) - pyPATH <- Sys.getenv("pythonLocation") - writeLines(sprintf('RETICULATE_PYTHON="%s"', pyPATH), fileConn) - close(fileConn) - shell: Rscript {0} - - - name: INSTALL PYTHON DEPENDENCIES - run: | - pip install --upgrade pip - pip install setuptools oauth2client earthengine-api numpy virtualenv - - - name: CACHE R PACKAGES [LINUX and macOS] + %PYTHON%\\python.exe -m pip install setuptools numpy earthengine-api + - name: Cache R packages if: runner.os != 'Windows' uses: actions/cache@v1 with: @@ -80,23 +67,28 @@ jobs: key: ${{ runner.os }}-r-${{ matrix.config.r }}-${{ hashFiles('.github/depends.Rds') }} restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}- - - name: PREBUILD TESTING + - name: Install system dependencies + if: runner.os == 'Linux' + env: + RHUB_PLATFORM: linux-x86_64-ubuntu-gcc run: | - earthengine -h + Rscript -e "remotes::install_github('r-hub/sysreqs')" + sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") + sudo -s eval "$sysreqs" - - name: INSTALL dependencies and rcmdcheck + - name: Install dependencies run: | remotes::install_deps(dependencies = TRUE) remotes::install_cran("rcmdcheck") shell: Rscript {0} - - name: CHECK + - name: Check env: _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") + run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran", "--no-examples"), error_on = "warning", check_dir = "check") shell: Rscript {0} - - name: UPLOAD CHECK RESULTS + - name: Upload check results if: failure() uses: actions/upload-artifact@master with: diff --git a/DESCRIPTION b/DESCRIPTION index d48f2322..a8703bec 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: rgee Title: R Bindings for Calling the 'Earth Engine' API -Version: 0.6.2 +Version: 1.0.0 Authors@R: c(person(given = "Cesar", family = "Aybar", @@ -75,14 +75,14 @@ Authors@R: comment = "Appel reviewed the package for JOSS, see https://github.com/openjournals/joss-reviews/issues/2272")) Description: Earth Engine client library for R. All of the 'Earth Engine' API classes, - modules, and functions are made available. Additional functions implemented include importing (exporting) of R (Earth Engine) + modules, and functions are made available. Additional functions implemented include importing (exporting) of R Earth Engine spatial objects, extraction of time series, interactive map display, assets management interface, and metadata display. See for further details. License: Apache License (>= 2.0) Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.0 +RoxygenNote: 7.1.1 Depends: R (>= 3.3.0) Imports: diff --git a/NAMESPACE b/NAMESPACE index 0f828e28..06412872 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ S3method(ee_print,ee.featurecollection.FeatureCollection) S3method(ee_print,ee.geometry.Geometry) S3method(ee_print,ee.image.Image) S3method(ee_print,ee.imagecollection.ImageCollection) +S3method(print,ee.computedobject.ComputedObject) export("%>%") export(Map) export(ee) diff --git a/NEWS.md b/NEWS.md index 26b11f10..f43d2012 100755 --- a/NEWS.md +++ b/NEWS.md @@ -12,16 +12,29 @@ vignette: > %\VignetteEncoding{UTF-8} --- # rgee 1.0.0 -- Removed `check_ring_dir` argument from `sf_as_ee`. Now `rgee` expect that users fix potential geometry problems before upload geometries to Earth Engine assets. -- The message "Initialization aborted" was removed from `ee_Initialize` and replaced by `ee_search_init_message`. It will permit to `rgee` to know if the user accepted to create the global var "EARTHENGINE_INIT_MESSAGE" without need to restart the R session. -- Fix a bug in `ee_as_sf`, new test were also added. The argument `command_line_tool_path` was added to give users the option to set the path of the Earth Engine command linetool. It is useful to upload files using Google Cloud Storage. New diagnostic message were added. -- mejorar upload test -- mensajes a la hora de subir informacion -- cambiar el earthengine al que se instala con el local -- error es ee_as_stars -- mejorar policy -- agregar test argumt a todaas las funciones ee_manage que se puedan strict -- agregar los cambios de los reviewer +- We implement `ee_createAssetHome` to help users to define their *Earth Engine Assets home root folder* without leaving ee_Initialize(). (#70 Thanks @jhollist) +- Fix a bug in `ee_Initialize(drive = TRUE, gcs = TRUE)` which do not permit users save credentials. (#72 Thanks @appelmar). +- Removed `check_ring_dir` argument from `sf_as_ee`. Now `rgee` expect that users fix potential geometry problems before upload geometries to their Earth Engine assets. +- Changes in "welcome to rgee" message (located in `ee_Initialize`). We delete `stop("Initialization aborted")` and implement `ee_search_init_message`. It will permit to `rgee` to know if the user accepted to create the global var "EARTHENGINE_INIT_MESSAGE" without need to restart the R session. +- Fix minor bugs in `sf_as_ee` and `gcs_to_ee_table`. The argument `command_line_tool_path` was added to give users the option to set the path of the Earth Engine command linetool. This new argument is only relevant to upload files using Google Cloud Storage. New diagnostic message were added. +- Now `sf_as_ee` returns an `ee$Geometry$...` when `x` is a `sfg` or a single sfc object. If `x` is a sf or a `sfc` object with multiple geometries will return an `ee$FeatureCollection`. New unit test for `sf_as_ee`. +- Changes in the documentation of `ee_as_stars` and `ee_as_raster` (#72 Thanks @appelmar). +- Fix minor bugs in `raster_as_ee`, `stars_as_ee` and `gcs_to_ee_image`. The argument `command_line_tool_path` was added to give users the option to set the path of the Earth Engine command linetool. New diagnostic message were added. New unit test added. +- Fixed a bug in `stars_as_ee` that didn't allow to read single-band images. +- `ee_manage_asset_access` has a better approach to determine the user owner of the asset. +- Add a new logical argument called 'strict' to `ee_manage_assetlist`, `ee_manage_copy`, +`ee_manage_move`, `ee_manage_set_properties` and `ee_manage_delete_properties`. If TRUE, +the existence of the asset will be evaluate before to perform the task. By default TRUE. +- If the `path_asset` is not specified in `ee_manage_assetlist`, rgee will assume that the +path_asset is `ee_get_assethome`. +- `raster_as_ee.R` was created in the /R folder to maintain an order between functions +to upload and download images. +- Fix a bug in the documentation of `print.ee.computedobject.ComputedObject()` (#72 Thanks @appelmar). +- Fix a bug in `ee_install_set_pyenv` now users can set `py_path` without set +`py_env` and vice versa. +- `ee_extract` was adapted to work well with changes in `sf_as_ee`. +- R CMD check is more friendly with users, the use of `--run-dontrun` is also +available (#72 Thanks @appelmar). # rgee 0.6.2 - Earth Engine Python API updated to 0.1.226. @@ -30,10 +43,10 @@ vignette: > - Users can mix mapview and EarthEnginemap objects in the same pipeline (see Examples in `Map$addLayer`). - Add `ee_as_mapview`, a function to convert `EarthEnginemap` objects to `mapview` objects. - add a new logical argument called 'strict' to **ee_manage_delete**. If TRUE, the existence of the asset will be evaluate before to perform the task. -- Fix a bug en ee_Initialize, now users without an Earth Engine Assets home root will see a message. +- Fix a bug in ee_Initialize, now users without an Earth Engine Assets home root will see a message. - Fix a minor bug when ee_Initialize change of user, now before to change of user the GCS and GD credentials will be deleted. - ee_check completely renovated. - - New message display. + - New diagnostic messages. - Fix a minor bug when testing GCS credentials. - The file ee_check.py was deleted. - Roy Samapriya added as a contributor. diff --git a/R/Map.R b/R/Map.R index 5c951846..e5c68e91 100644 --- a/R/Map.R +++ b/R/Map.R @@ -135,7 +135,7 @@ #' #' # Case 6: mapedit #' library(mapedit) -#' my_geometry <- m2 %>% ee_as_mapview() %>% editMap() +#' # my_geometry <- m2 %>% ee_as_mapview() %>% editMap() #' } #' @export Map <- function() { diff --git a/R/ee_Initialize.R b/R/ee_Initialize.R index 84c7b107..345cbd7f 100644 --- a/R/ee_Initialize.R +++ b/R/ee_Initialize.R @@ -7,14 +7,15 @@ #' a wrapper around `rgee::ee$Initialize()`. #' #' @param email Character (optional, e.g. `data.colec.fbf@gmail.com`). The email -#' name is used as a folder inside the path `rgee::ee_get_earthengine_path()`. -#' This enable a multi-user support allowing to target a specific -#' Google identity. +#' argument is used to create a folder inside the path \code{~/.config/earthengine/} +#' that save all credentials for a specific Google identity. #' #' @param drive Logical (optional). If TRUE, the drive credential -#' will be cached in the path `rgee::ee_get_earthengine_path()`. -#' @param gcs Logical. If TRUE, the Google Cloud Storage -#' credential will be cached in the path `rgee::ee_get_earthengine_path()`. +#' will be cached in the path \code{~/.config/earthengine/}. +#' +#' @param gcs Logical (optional). If TRUE, the Google Cloud Storage +#' credential will be cached in the path \code{~/.config/earthengine/}. +#' #' @param quiet Logical. Suppress info messages. #' #' @importFrom utils read.table browseURL write.table packageVersion @@ -24,10 +25,10 @@ #' @importFrom crayon blue green black red bold white #' #' @details -#' \code{ee_Initialize(...)} can also manage Google drive and Google +#' \code{ee_Initialize(...)} can manage Google drive and Google #' Cloud Storage resources using the R packages googledrive and #' googlecloudStorageR, respectively. By default, rgee does not require -#' them, these are only necessary for exporting and importing tasks. +#' them, these are only necessary to enable rgee I/O functionality. #' All user credentials are saved in the directory #' \code{~/.config/earthengine/}, if a user does not specify the email #' argument all user credentials will be saved in a subdirectory @@ -43,11 +44,11 @@ #' ee_Initialize() #' #' # Advanced init - Load full credentials -#' ee_Initialize( -#' email = "your_email@gmail.com", -#' drive = TRUE, -#' gcs = TRUE -#' ) +#' # ee_Initialize( +#' # email = "your_email@gmail.com", +#' # drive = TRUE, +#' # gcs = TRUE +#' # ) #' #' ee_user_info() #' } @@ -150,7 +151,7 @@ ee_Initialize <- function(email = NULL, # Loading all the credentials: earthengine, drive and GCS. drive_credentials <- NA - gcs_credentials <- NA + gcs_credentials <- list(path = NA, message = NA) if (drive) { if (!quiet) { @@ -180,40 +181,40 @@ ee_Initialize <- function(email = NULL, blue("GCS credentials:") ) } - gcs_credentials <- ee_create_credentials_gcs(email) + gcs_credentials <- tryCatch( + expr = ee_create_credentials_gcs(email), + error = function(e) { + list(path = NA, message = NA) + }) + if (!quiet) { - cat( - "\r", - green(symbol$tick), - blue("GCS credentials:"), - # gcs_credentials, - green(" FOUND\n") - ) + if (!is.na(gcs_credentials$path)) { + cat( + "\r", + green(symbol$tick), + blue("GCS credentials:"), + # gcs_credentials, + green(" FOUND\n") + ) + } else { + cat( + "\r", + green(symbol$tick), + blue("GCS credentials:"), + # gcs_credentials, + red("NOT FOUND\n") + ) + } } } ## rgee session file - options(rgee.gcs.auth = gcs_credentials) - options(rgee.manage.setIamPolicy = list(bindings = list( - list( - role = "roles/owner", - members = paste0("user:", email) - ), - list( - role = "roles/editor", - members = NULL - ), - list( - role = "roles/viewer", - members = NULL - ) - ))) + options(rgee.gcs.auth = gcs_credentials$path) if (!quiet) { cat( "", green(symbol$tick), blue("Initializing Google Earth Engine:") ) } - ee_create_credentials_earthengine(email_clean) ee$Initialize() @@ -253,13 +254,16 @@ ee_Initialize <- function(email = NULL, email = email_clean, user = ee_user, drive_cre = drive_credentials, - gcs_cre = gcs_credentials + gcs_cre = gcs_credentials$path ) if (!quiet) { cat("\r", green(symbol$tick), blue("Earth Engine user:"), green(bold(ee_user)), "\n") cat(rule(), "\n") + if (!is.na(gcs_credentials$message)) { + message(gcs_credentials$message) + } } # ee_check_python_packages(quiet = TRUE) invisible(TRUE) @@ -391,27 +395,39 @@ ee_create_credentials_gcs <- function(email) { full_credentials <- list.files(path = ee_path_user, full.names = TRUE) gcs_condition <- grepl(".json", full_credentials) if (!any(gcs_condition)) { - stop( - "Unable to find GCS_AUTH_FILE.json in ", ee_path_user, ". \n", - "First download and save the Google Project JSON file in ", - "the path mentioned before.\n", - "A compressible tutorial to obtain the GCS_AUTH_FILE.json is ", - "available here: \n", - "- https://github.com/csaybar/GCS_AUTH_FILE.json\n", - "- https://console.cloud.google.com/apis/credentials/serviceaccountkey ", - "(download link)\n" + gcs_text <- paste( + sprintf("Unable to find a service account key (SAK) file in: %s", bold(ee_path_user)), + "Please, download and save it manually on the path mentioned", + "above. A compressible tutorial to obtain a SAK file are available at:", + "> https://github.com/csaybar/GCS_AUTH_FILE.json", + "> https://cloud.google.com/iam/docs/creating-managing-service-account-keys", + "> https://console.cloud.google.com/apis/credentials/serviceaccountkey", + bold("Until you do not save a SKA file, the following functions will not work:"), + "- rgee::ee_gcs_to_local()", + "- ee_as_raster(..., via = \"gcs\")", + "- ee_as_stars(..., via = \"gcs\")", + "- ee_as_sf(..., via = \"gcs\")", + "- sf_as_ee(..., via = \"gcs_to_asset\")", + "- gcs_to_ee_image", + "- raster_as_ee", + "- local_to_gcs", + "- stars_as_ee", + sep = "\n" ) + gcs_info <- list(path = NA, message = gcs_text) + invisible(gcs_info) } else { gcs_credentials <- full_credentials[gcs_condition] googleCloudStorageR::gcs_auth(gcs_credentials) + unlink(list.files(ee_path, ".json", full.names = TRUE)) + file.copy( + from = gcs_credentials, + to = sprintf("%s/%s", ee_path, basename(gcs_credentials)), + overwrite = TRUE + ) + gcs_info <- list(path = gcs_credentials, message = NA) + invisible(gcs_info) } - unlink(list.files(ee_path, ".json", full.names = TRUE)) - file.copy( - from = gcs_credentials, - to = sprintf("%s/%s", ee_path, basename(gcs_credentials)), - overwrite = TRUE - ) - invisible(gcs_credentials) } #' Display the credentials of all users as a table diff --git a/R/ee_as_sf.R b/R/ee_as_sf.R index 7ba77878..f3d3df8f 100755 --- a/R/ee_as_sf.R +++ b/R/ee_as_sf.R @@ -74,11 +74,11 @@ #' ) #' #' # Download via GCS -#' sf_randomPoints_gcs <- ee_as_sf( -#' x = subset, -#' via = 'gcs', -#' container = 'rgee_dev' #GCS bucket name -#' ) +#' # sf_randomPoints_gcs <- ee_as_sf( +#' # x = subset, +#' # via = 'gcs', +#' # container = 'rgee_dev' #GCS bucket name +#' # ) #' } #' @export ee_as_sf <- function(x, @@ -103,6 +103,7 @@ ee_as_sf <- function(x, if (missing(dsn)) { dsn <- paste0(tempfile(),".geojson") } + if (!any(class(x) %in% sp_eeobjects)) { stop("x is not a Earth Engine table\n") } diff --git a/R/ee_as_thumbnail.R b/R/ee_as_thumbnail.R index cbc84788..ad96c797 100755 --- a/R/ee_as_thumbnail.R +++ b/R/ee_as_thumbnail.R @@ -94,8 +94,7 @@ #' arequipa_region <- nc %>% #' st_bbox() %>% #' st_as_sfc() %>% -#' sf_as_ee() %>% -#' ee$FeatureCollection$geometry() +#' sf_as_ee() #' #' arequipa_dem <- ee_as_thumbnail( #' image = image, @@ -226,7 +225,7 @@ ee_as_thumbnail <- function(image, region, dimensions, vizparams = NULL, '- region parameters\n', '\rWKT :', sf::st_as_text(sf_region), '\nCRS :', ee_crs, - '\ngeodesic :', is_geodesic, + '\ngeodesic :', ee_utils_py_to_r(is_geodesic), '\nevenOdd :', is_evenodd, '\n' ) diff --git a/R/ee_check.R b/R/ee_check.R index afb2d1de..10b1bb91 100755 --- a/R/ee_check.R +++ b/R/ee_check.R @@ -211,6 +211,7 @@ ee_check_credentials <- function(quiet = FALSE) { "- sf_as_ee(via = \"gcs_to_asset\")\n", "- gcs_to_ee_image\n", "- raster_as_ee\n", + "- local_to_gcs", "- stars_as_ee\n", "Try rgee::ee_Initialize(gcs = TRUE) to fix.\n" ) diff --git a/R/ee_clean.R b/R/ee_clean.R index c06dc6d7..17d6df73 100644 --- a/R/ee_clean.R +++ b/R/ee_clean.R @@ -12,8 +12,8 @@ #' \dontrun{ #' library(rgee) #' -#' ee_clean_credentials() -#' ee_clean_credentials('data.colec.fbf@gmail.com') +#' # ee_clean_credentials() +#' # ee_clean_credentials('data.colec.fbf@gmail.com') #' #' } #' @export @@ -55,8 +55,8 @@ ee_clean_credentials <- function(email='not_defined', quiet = FALSE) { #' @family ee_clean functions #' @examples #' \dontrun{ -#' library(rgee) -#' ee_clean_pyenv() +#' # library(rgee) +#' # ee_clean_pyenv() #' } #' @export ee_clean_pyenv <- function() { @@ -82,7 +82,7 @@ ee_clean_pyenv <- function() { close(con) # Remove system variables EARTHENGINE_PYTHON - system_vars <- lines[!grepl("EARTHENGINE_", lines)] + system_vars <- lines[!grepl("EARTHENGINE_PYTHON|EARTHENGINE_ENV", lines)] fileConn <- file(renv) writeLines(system_vars, fileConn) close(fileConn) diff --git a/R/ee_download.R b/R/ee_download.R index a7f4ed75..4b622926 100755 --- a/R/ee_download.R +++ b/R/ee_download.R @@ -62,6 +62,7 @@ #' rlist$xmax, rlist$ymax, #' rlist$xmin, rlist$ymax, #' rlist$xmin, rlist$ymin) +#' #' ee_ROI <- matrix(ROI, ncol = 2, byrow = TRUE) %>% #' list() %>% #' st_polygon() %>% @@ -82,7 +83,7 @@ #' } #' #' ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ -#' filterBounds(ee_ROI)$ +#' filterBounds(ee$FeatureCollection(ee_ROI))$ #' filterDate("2011-01-01", "2011-12-31")$ #' map(cloudMaskL457) #' @@ -95,7 +96,7 @@ #' task_img <- ee_image_to_drive( #' image = mean_l5_Amarakaeri, #' fileFormat = "GEO_TIFF", -#' region = ee_ROI$geometry(), +#' region = ee_ROI, #' fileNamePrefix = "my_image" #' ) #' @@ -234,7 +235,7 @@ ee_image_to_drive <- function(image, #' } #' #' ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ -#' filterBounds(ee_ROI)$ +#' filterBounds(ee$FeatureCollection(ee_ROI))$ #' filterDate("2011-01-01", "2011-12-31")$ #' map(cloudMaskL457) #' @@ -243,21 +244,21 @@ ee_image_to_drive <- function(image, #' mean_l5 <- mean_l5$reproject(crs = "EPSG:4326", scale = 500) #' mean_l5_Amarakaeri <- mean_l5$clip(ee_ROI) #' -#' # Move results from Earth Engine to Drive -#' task_img <- ee_image_to_gcs( -#' image = mean_l5_Amarakaeri, -#' bucket = "rgee_dev", -#' fileFormat = "GEO_TIFF", -#' region = ee_ROI$geometry(), -#' fileNamePrefix = "my_image" -#' ) -#' -#' task_img$start() -#' ee_monitoring(task_img) -#' -#' # Move results from Drive to local -#' ee_gcs_to_local(task = task_img) -#' plot(img) +#' # Move results from Earth Engine to GCS +#' # task_img <- ee_image_to_gcs( +#' # image = mean_l5_Amarakaeri, +#' # bucket = "rgee_dev", +#' # fileFormat = "GEO_TIFF", +#' # region = ee_ROI, +#' # fileNamePrefix = "my_image" +#' # ) +#' # +#' # task_img$start() +#' # ee_monitoring(task_img) +#' +#' # Move results from GCS to local +#' # ee_gcs_to_local(task = task_img) +#' # plot(img) #' } #' @export ee_image_to_gcs <- function(image, @@ -381,7 +382,7 @@ ee_image_to_gcs <- function(image, #' } #' #' ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ -#' filterBounds(ee_ROI)$ +#' filterBounds(ee$FeatureCollection(ee_ROI))$ #' filterDate("2011-01-01", "2011-12-31")$ #' map(cloudMaskL457) #' @@ -395,8 +396,9 @@ ee_image_to_gcs <- function(image, #' task_img <- ee_image_to_asset( #' image = mean_l5_Amarakaeri, #' assetId = assetid, +#' overwrite = TRUE, #' scale = 500, -#' region = ee_ROI$geometry() +#' region = ee_ROI #' ) #' #' task_img$start() @@ -488,7 +490,7 @@ ee_image_to_asset <- function(image, #' sf_as_ee() #' #' amk_fc <- ee$FeatureCollection( -#' list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) +#' list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) #' ) #' #' task_vector <- ee_table_to_drive( @@ -572,19 +574,19 @@ ee_table_to_drive <- function(collection, #' sf_as_ee() #' #' amk_fc <- ee$FeatureCollection( -#' list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) +#' list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) #' ) #' -#' task_vector <- ee_table_to_gcs( -#' collection = amk_fc, -#' bucket = "rgee_dev", -#' fileFormat = "SHP", -#' fileNamePrefix = "geom_Amarakaeri" -#' ) -#' task_vector$start() -#' ee_monitoring(task_vector) # optional -#' amk_geom <- ee_gcs_to_local(task = task_vector) -#' plot(amk_geom$geometry, border = "red", lwd = 10) +#' #task_vector <- ee_table_to_gcs( +#' # collection = amk_fc, +#' # bucket = "rgee_dev", +#' # fileFormat = "SHP", +#' # fileNamePrefix = "geom_Amarakaeri" +#' #) +#' #task_vector$start() +#' #ee_monitoring(task_vector) # optional +#' #amk_geom <- ee_gcs_to_local(task = task_vector) +#' #plot(sf::read_sf(amk_geom[3]), border = "red", lwd = 10) #' } #' @export ee_table_to_gcs <- function(collection, @@ -655,12 +657,13 @@ ee_table_to_gcs <- function(collection, #' sf_as_ee() #' #' amk_fc <- ee$FeatureCollection( -#' list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) +#' list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) #' ) #' #' assetid <- paste0(ee_get_assethome(), '/geom_Amarakaeri') #' task_vector <- ee_table_to_asset( #' collection = amk_fc, +#' overwrite = TRUE, #' assetId = assetid #' ) #' task_vector$start() @@ -756,7 +759,7 @@ ee_table_to_asset <- function(collection, #' } #' #' ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ -#' filterBounds(ee_ROI)$ +#' filterBounds(ee$FeatureCollection(ee_ROI))$ #' filterDate("2011-01-01", "2011-12-31")$ #' map(cloudMaskL457) #' @@ -770,7 +773,7 @@ ee_table_to_asset <- function(collection, #' image = mean_l5_Amarakaeri, #' folder = "Amarakaeri", #' fileFormat = "GEO_TIFF", -#' region = ee_ROI$geometry(), +#' region = ee_ROI, #' fileNamePrefix = paste0("my_image", Sys.time()) #' ) #' @@ -784,7 +787,7 @@ ee_table_to_asset <- function(collection, ee_drive_to_local <- function(task, dsn, overwrite = TRUE, - consider = interactive(), + consider = TRUE, quiet = FALSE) { if (!requireNamespace("googledrive", quietly = TRUE)) { stop("The googledrive package is required to use rgee::ee_download_drive", @@ -962,7 +965,7 @@ ee_drive_to_local <- function(task, #' } #' #' ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ -#' filterBounds(ee_ROI)$ +#' filterBounds(ee$FeatureCollection(ee_ROI))$ #' filterDate("2011-01-01", "2011-12-31")$ #' map(cloudMaskL457) #' @@ -971,20 +974,20 @@ ee_drive_to_local <- function(task, #' mean_l5 <- mean_l5$reproject(crs = "EPSG:4326", scale = 500) #' mean_l5_Amarakaeri <- mean_l5$clip(ee_ROI) #' -#' # Move results from Earth Engine to Drive -#' task_img <- ee_image_to_gcs( -#' image = mean_l5_Amarakaeri, -#' bucket = "rgee_dev", -#' fileFormat = "GEO_TIFF", -#' region = ee_ROI$geometry(), -#' fileNamePrefix = paste0("my_image", Sys.time()) -#' ) +#' ## Move results from Earth Engine to Drive +#' # task_img <- ee_image_to_gcs( +#' # image = mean_l5_Amarakaeri, +#' # bucket = "rgee_dev", +#' # fileFormat = "GEO_TIFF", +#' # region = ee_ROI, +#' # fileNamePrefix = paste0("my_image", Sys.time()) +#' #) #' -#' task_img$start() -#' ee_monitoring(task_img) +#' # task_img$start() +#' # ee_monitoring(task_img) #' -#' # Move results from Drive to local -#' img <- ee_gcs_to_local(task = task_img) +#' ## Move results from Drive to local +#' # img <- ee_gcs_to_local(task = task_img) #' } #' @export ee_gcs_to_local <- function(task, diff --git a/R/ee_extract.R b/R/ee_extract.R index ee6dae60..3395b8db 100755 --- a/R/ee_extract.R +++ b/R/ee_extract.R @@ -127,17 +127,15 @@ ee_extract <- function(x, stop("package sf required, please install it first") } # spatial classes - sf_classes <- c("sf", "sfc", "sfg") sp_objects <- ee_get_spatial_objects('Table') x_type <- x$name() - # datatype # Load Python module oauth_func_path <- system.file("python/ee_extract.py", package = "rgee") extract_py <- ee_source_python(oauth_func_path) # Is y a Spatial object? - if (!any(class(y) %in% c(sp_objects, sf_classes))) { + if (!any(class(y) %in% c(sp_objects, "sf"))) { stop("y is not a Earth Engine table or a sf object.") } @@ -177,7 +175,7 @@ ee_extract <- function(x, } # If y is a sf object convert into a ee$FeatureCollection object - if (any(sf_classes %in% class(y))) { + if (any("sf" %in% class(y))) { sf_y <- y ee_y <- sf_as_ee(y) } @@ -192,7 +190,7 @@ ee_extract <- function(x, } #set ee_ID for identify rows in the data.frame - ee_y <- ee_y$map(function(f) f$set("ee_ID", f$get("system:index"))) + ee_y <- ee$FeatureCollection(ee_y)$map(function(f) f$set("ee_ID", f$get("system:index"))) # Get the funname fun_name <- gsub("Reducer.", "", fun$getInfo()["type"]) diff --git a/R/ee_image.R b/R/ee_image.R index 812c2562..c81e62ee 100644 --- a/R/ee_image.R +++ b/R/ee_image.R @@ -3,9 +3,9 @@ #' Convert an ee$Image into a stars object #' #' @param image ee$Image to be converted into a stars object -#' @param region EE Geometry Rectangle (ee$Geometry$Rectangle). The -#' CRS needs to be the same that the x argument otherwise it will be -#' forced. If not specified, image bounds will be taken. +#' @param region EE Geometry (ee$Geometry$Polygon) which specify the region +#' to export. CRS needs to be the same that the x argument otherwise it will be +#' forced. If not specified image bounds will be taken. #' @param dsn Character. Output filename. If missing, #' \code{ee_as_stars} will create a temporary file. #' @param scale Numeric. The resolution in meters per pixel. Defaults @@ -13,8 +13,8 @@ #' @param maxPixels Numeric. The maximum allowed number of pixels in the #' exported image. The task will fail if the exported region covers #' more pixels in the specified projection. Defaults to 100,000,000. -#' @param via Character. Method to fetch data about the object. Multiple -#' options supported. See details. +#' @param via Character. Method to fetch data about the object. Three methods +#' are implemented: "getInfo", "drive", "gcs". See details. #' @param container Character. Name of the folder ('drive') or bucket ('gcs') #' to be exported into (ignored if \code{via} is not defined as "drive" or #' "gcs"). @@ -80,26 +80,20 @@ #' ) #' #' ## gcs - Method 03 -#' img_03 <- ee_as_stars( -#' image = img, -#' region = geometry, -#' container = "rgee_dev", -#' via = "gcs" -#' ) +#' # img_03 <- ee_as_stars( +#' # image = img, +#' # region = geometry, +#' # container = "rgee_dev", +#' # via = "gcs" +#' #) #' #' # OPTIONAL: Delete containers -#' ee_clean_container( -#' name = "rgee_backup", -#' type = "drive" -#' ) -#' ee_clean_container( -#' name = "rgee_dev", -#' type = "gcs" -#' ) +#' # ee_clean_container(name = "rgee_backup", type = "drive") +#' # ee_clean_container(name = "rgee_dev", type = "gcs") #' } #' @export ee_as_stars <- function(image, - region, + region = NULL, dsn = NULL, via = "getInfo", scale = NULL, @@ -114,6 +108,13 @@ ee_as_stars <- function(image, stop("package sf required, please install it first") } + if (is.null(region)) { + if (!quiet) { + message("region is not specified taking the image's region...") + } + region <- image$geometry() + } + img_files <- ee_image_local( image = image, region = region, @@ -127,8 +128,13 @@ ee_as_stars <- function(image, ) img_stars <- stars::read_stars(img_files$file,proxy = TRUE) - sf::st_crs(img_stars) <- img_files$crs - stars::st_set_dimensions(img_stars, 3, values = img_files$band_names) + + # It's a single image? + if (length(stars::st_dimensions(img_stars)) < 3) { + img_stars + } else { + stars::st_set_dimensions(img_stars, 3, values = img_files$band_names) + } } @@ -137,9 +143,9 @@ ee_as_stars <- function(image, #' Convert an ee$Image into a raster object #' #' @param image ee$Image to be converted into a raster object -#' @param region EE Geometry Rectangle (ee$Geometry$Rectangle). The -#' CRS needs to be the same that the x argument otherwise it will be -#' forced. If not specified, image bounds will be taken. +#' @param region EE Geometry (ee$Geometry$Polygon) which specify the region +#' to export. CRS needs to be the same that the x argument otherwise it will be +#' forced. If not specified image bounds will be taken. #' @param dsn Character. Output filename. If missing, #' \code{ee_as_raster} will create a temporary file. #' @param scale Numeric. The resolution in meters per pixel. Defaults @@ -147,8 +153,8 @@ ee_as_stars <- function(image, #' @param maxPixels Numeric. The maximum allowed number of pixels in the #' exported image. The task will fail if the exported region covers #' more pixels in the specified projection. Defaults to 100,000,000. -#' @param via Character. Method to fetch data about the object. Multiple -#' options supported. See details. +#' @param via Character. Method to fetch data about the object. Three methods +#' are implemented: "getInfo", "drive", "gcs". See details. #' @param container Character. Name of the folder ('drive') or bucket ('gcs') #' to be exported into (ignored if \code{via} is not defined as "drive" or #' "gcs"). @@ -212,26 +218,20 @@ ee_as_stars <- function(image, #' ) #' #' ## gcs - Method 03 -#' img_03 <- ee_as_raster( -#' image = img, -#' region = geometry, -#' container = "rgee_dev", -#' via = "gcs" -#' ) +#' # img_03 <- ee_as_raster( +#' # image = img, +#' # region = geometry, +#' # container = "rgee_dev", +#' # via = "gcs" +#' # ) #' #' # OPTIONAL: Delete containers -#' ee_clean_container( -#' name = "rgee_backup", -#' type = "drive" -#' ) -#' ee_clean_container( -#' name = "rgee_dev", -#' type = "gcs" -#' ) +#' # ee_clean_container(name = "rgee_backup", type = "drive") +#' # ee_clean_container(name = "rgee_dev", type = "gcs") #' } #' @export ee_as_raster <- function(image, - region, + region = NULL, dsn = NULL, via = "getInfo", scale = NULL, @@ -243,6 +243,13 @@ ee_as_raster <- function(image, stop("package raster required, please install it first") } + if (is.null(region)) { + if (!quiet) { + message("region is not specified taking the image's region...") + } + region <- image$geometry() + } + img_files <- ee_image_local( image = image, region = region, @@ -266,148 +273,6 @@ ee_as_raster <- function(image, } } -#' Convert a stars or stars-proxy object into an EE Image -#' -#' Convert a stars or stars-proxy object into an ee$Image. -#' -#' @param x stars or stars-proxy object to be converted into an ee$Image. -#' @param assetId Character. Destination asset ID for the uploaded file. -#' @param overwrite Logical. If TRUE, the assetId will be overwritten. -#' @param bucket Character. Name of the GCS bucket. -#' @param monitoring Logical. If TRUE the exportation task will be monitored. -#' @param quiet Logical. Suppress info message. -#' -#' @return An ee$Image object -#' @family image upload functions -#' @examples -#' \dontrun{ -#' library(rgee) -#' library(stars) -#' ee_Initialize(gcs = TRUE) -#' -#' # Get the filename of a image -#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") -#' x <- read_stars(tif) -#' assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') -#' -#' # Method 1 -#' # 1. Move from local to gcs -#' gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') -#' -#' # 2. Pass from gcs to asset -#' gcs_to_ee_image( -#' x = x, -#' gs_uri = gs_uri, -#' assetId = assetId -#' ) -#' -#' # OPTIONAL: Monitoring progress -#' ee_monitoring() -#' -#' # OPTIONAL: Display results -#' ee_stars_01 <- ee$Image(assetId) -#' Map$centerObject(ee_stars_01) -#' Map$addLayer(ee_stars_01) -#' -#' # Method 2 -#' ee_stars_02 <- stars_as_ee( -#' x = x, -#' assetId = assetId, -#' bucket = "rgee_dev" -#' ) -#' Map$centerObject(ee_stars_02) -#' Map$addLayer(ee_stars_02) -#' } -#' @export -stars_as_ee <- function(x, - assetId, - overwrite = FALSE, - monitoring = TRUE, - bucket = NULL, - quiet = FALSE) { - # Create a temporary shapefile as - ee_temp <- tempdir() - - stars_proxy <- ee_as_proxystars(x, temp_dir = ee_temp) - gcs_filename <- local_to_gcs( - x = stars_proxy[[1]], - bucket = bucket, - quiet = quiet - ) - - gcs_to_ee_image( - x = x, - gs_uri = gcs_filename, - overwrite = overwrite, - assetId = assetId - ) - - if (isTRUE(monitoring)) { - ee_monitoring() - ee$Image(assetId) - } else { - assetId - } -} - - -#' Convert a Raster* object into an EE Image -#' -#' Convert a Raster* object into an ee$Image. -#' -#' @param x RasterLayer, RasterStack or RasterBrick object to be converted into -#' an ee$Image. -#' @param assetId Character. Destination asset ID for the uploaded file. -#' @param overwrite Logical. If TRUE, the assetId will be overwritten. -#' @param bucket Character. Name of the GCS bucket. -#' @param monitoring Logical. If TRUE the exportation task will be monitored. -#' @param quiet Logical. Suppress info message. -#' -#' @return An ee$Image object -#' @family image upload functions -#' -#' @examples -#' \dontrun{ -#' library(rgee) -#' library(stars) -#' ee_Initialize(gcs = TRUE) -#' -#' # Get the filename of a image -#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") -#' x <- read_stars(tif) -#' assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') -#' -#' # Method 1 -#' # 1. Move from local to gcs -#' gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') -#' -#' # 2. Pass from gcs to asset -#' gcs_to_ee_image( -#' x = x, -#' gs_uri = gs_uri, -#' assetId = assetId -#' ) -#' -#' # OPTIONAL: Monitoring progress -#' ee_monitoring() -#' -#' # OPTIONAL: Display results -#' ee_raster_01 <- ee$Image(assetId) -#' Map$centerObject(ee_raster_01) -#' Map$addLayer(ee_raster_01) -#' -#' # Method 2 -#' ee_raster_02 <- raster_as_ee( -#' x = x, -#' assetId = assetId, -#' bucket = "rgee_dev" -#' ) -#' Map$centerObject(ee_raster_02) -#' Map$addLayer(ee_raster_02) -#' } -#' @export -raster_as_ee <- stars_as_ee - #' Passing an Earth Engine Image to Local #' @noRd ee_image_local <- function(image, @@ -576,7 +441,9 @@ ee_image_local <- function(image, sf_as_ee( evenOdd = is_evenodd, proj = img_crs, - geodesic = is_geodesic + geodesic = is_geodesic, + quiet = TRUE + ) # region parameters display @@ -585,13 +452,13 @@ ee_image_local <- function(image, '- region parameters\n', 'WKT :', sf::st_as_text(sf_region), "\n", 'CRS :', img_crs, "\n", - 'geodesic :', is_geodesic, "\n", + 'geodesic :', ee_utils_py_to_r(is_geodesic), "\n", 'evenOdd :', is_evenodd, "\n" ) } # ee$FeatureCollection to ee$List - region_features <- region_fixed$toList( + region_features <- ee$FeatureCollection(region_fixed)$toList( length(sf_region_gridded) ) @@ -712,7 +579,7 @@ ee_image_local <- function(image, '- region parameters\n', 'WKT :', sf::st_as_text(sf_region), "\n", 'CRS :', img_crs, "\n", - 'geodesic :', is_geodesic, "\n", + 'geodesic :', ee_utils_py_to_r(is_geodesic), "\n", 'evenOdd :', is_evenodd, "\n" ) } @@ -770,7 +637,7 @@ ee_image_local <- function(image, '- region parameters\n', 'WKT :', sf::st_as_text(sf_region), "\n", 'CRS :', img_crs, "\n", - 'geodesic :', is_geodesic, "\n", + 'geodesic :', ee_utils_py_to_r(is_geodesic), "\n", 'evenOdd :', is_evenodd, "\n" ) } @@ -813,10 +680,10 @@ ee_image_local <- function(image, #' Approximate size of an EE Image object #' -#' Get the approximate number of rows, cols, and size of an +#' Get the approximate number of rows, cols, and size of an single-band #' Earth Engine Image. #' -#' @param image EE Image object. +#' @param image Single-band EE Image object. #' @param getsize Logical. If TRUE, the size of the object #' will be estimated. #' @param compression_ratio Numeric. Measurement of the relative reduction @@ -836,7 +703,7 @@ ee_image_local <- function(image, #' ee_image_info(srtm) #' #' # Landast8 -#' l8 <- ee$Image("LANDSAT/LC08/C01/T1_SR/LC08_038029_20180810") +#' l8 <- ee$Image("LANDSAT/LC08/C01/T1_SR/LC08_038029_20180810")$select("B4") #' ee_image_info(l8) #' } #' @export diff --git a/R/ee_install.R b/R/ee_install.R index 463fb051..6179fe09 100755 --- a/R/ee_install.R +++ b/R/ee_install.R @@ -28,8 +28,8 @@ #' @family ee_install functions #' @examples #' \dontrun{ -#' library(rgee) -#' ee_install() #It is just necessary once +#' # library(rgee) +#' # ee_install() #It is just necessary once #' } #' @export ee_install <- function(py_env = "rgee", @@ -161,7 +161,7 @@ ee_install <- function(py_env = "rgee", repeat { ch <- tolower(substring(response, 1, 1)) if (ch == "y" || ch == "") { - ee_install_set_pyenv(py_path = py_path) + ee_install_set_pyenv(py_path = py_path, py_env = py_env) message( "\n", paste( @@ -244,10 +244,10 @@ ee_install <- function(py_env = "rgee", #' EARTHENGINE_PYTHON is saved into the file .Renviron. #' #' @param py_path The path to a Python interpreter. -#' @param py_env The name of the environment. By default "rgee". +#' @param py_env The name of the environment #' @family ee_install functions #' @export -ee_install_set_pyenv <- function(py_path, py_env = "rgee") { +ee_install_set_pyenv <- function(py_path = NULL, py_env = NULL) { ee_clean_pyenv() # Trying to get the env from the py_path home <- Sys.getenv("HOME") @@ -275,11 +275,19 @@ ee_install_set_pyenv <- function(py_path, py_env = "rgee") { ii <- ii + 1 } - # Set EARTHENGINE_PYTHON in .Renviron - ret_python <- sprintf('EARTHENGINE_PYTHON="%s"', py_path) - ret_env <- sprintf('EARTHENGINE_ENV="%s"', py_env) - system_vars <- c(lines, ret_python, ret_env) + # Set EARTHENGINE_PYTHON and EARTHENGINE_ENV in .Renviron if + # exists. + to_remote <- as.character() + if (!is.null(py_path)) { + ret_python <- sprintf('EARTHENGINE_PYTHON="%s"', py_path) + to_remote <- c(to_remote, ret_python) + } + if (!is.null(py_env)) { + ret_env <- sprintf('EARTHENGINE_ENV="%s"', py_env) + to_remote <- c(to_remote, ret_python) + } + system_vars <- c(lines, ret_python, ret_env) writeLines(system_vars, con) close(con) invisible(TRUE) @@ -376,9 +384,19 @@ is_windows <- function() { #' #' @param version Character. The Earth Engine Python API version to upgrade. #' By default \code{rgee::ee_version()}. +#' @param earthengine_env Character. The name, or full path, of the +#' environment in which the earthengine-api packages are to be installed. #' @family ee_install functions #' @export -ee_install_upgrade <- function(version = NULL) { +ee_install_upgrade <- function(version = NULL, + earthengine_env = Sys.getenv("EARTHENGINE_ENV")) { + if (earthengine_env == "") { + stop( + "ee_install_upgrade needs that global env EARTHENGINE_ENV", + " is defined to work. Run ee_install_set_pyenv(py_env = \"YOUR_ENV\")", + " to set a Python environment." + ) + } if (is.null(version)) { version <- rgee::ee_version() } diff --git a/R/ee_manage.R b/R/ee_manage.R index ed415444..beb50468 100755 --- a/R/ee_manage.R +++ b/R/ee_manage.R @@ -17,6 +17,7 @@ #' details. #' @param strict Character vector. If TRUE, the existence of the asset will be #' evaluate before to perform the task. +#' @param owner Character vector. Define owner user in the IAM Policy. #' @param editor Character vector. Define editor users in the IAM Policy. #' @param viewer Character vector. Define viewer users in the IAM Policy. #' @param all_users_can_read Logical. All users can see the asset element. @@ -32,58 +33,58 @@ #' @author Samapriya Roy, adapted to R by csaybar. #' @examples #' \dontrun{ -#' #' library(rgee) #' #' ee_Initialize() #' ee_user_info() #' #' # Change datacolecfbf by your EE user to be able to reproduce -#' +#' user <- ee_get_assethome() +#' addm <- function(x) sprintf("%s/%s",user, x) #' # 1. Create a folder or Image Collection #' # Change path asset according to your specific user -#' ee_manage_create("users/datacolecfbf/rgee") +#' ee_manage_create(addm("rgee")) #' #' # 1. List all the elements inside a folder or a ImageCollection -#' ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee") +#' ee_manage_assetlist(path_asset = addm("rgee")) #' #' # 2. Create a Folder or a ImageCollection #' ee_manage_create( -#' path_asset = "users/datacolecfbf/rgee/rgee_folder", +#' path_asset = addm("rgee/rgee_folder"), #' asset_type = "Folder" #' ) #' #' ee_manage_create( -#' path_asset = "users/datacolecfbf/rgee/rgee_ic", +#' path_asset = addm("rgee/rgee_ic"), #' asset_type = "ImageCollection" #' ) #' -#' ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee") +#' ee_manage_assetlist(path_asset = addm("rgee")) #' #' # 3. Shows Earth Engine quota #' ee_manage_quota() #' #' # 4. Move an EE object to another folder #' ee_manage_move( -#' path_asset = "users/datacolecfbf/rgee/rgee_ic", -#' final_path = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved" +#' path_asset = addm("rgee/rgee_ic"), +#' final_path = addm("rgee/rgee_folder/rgee_ic_moved") #' ) #' -#' ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee/rgee_folder") +#' ee_manage_assetlist(path_asset = addm("rgee/rgee_folder")) #' #' # 5. Set properties to an EE object. #' ee_manage_set_properties( -#' path_asset = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved", +#' path_asset = addm("rgee/rgee_folder/rgee_ic_moved"), #' add_properties = list(message = "hello-world", language = "R") #' ) #' -#' ic_id <- "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved" +#' ic_id <- addm("rgee/rgee_folder/rgee_ic_moved") #' test_ic <- ee$ImageCollection(ic_id) #' test_ic$getInfo() #' #' # 6. Delete properties #' ee_manage_delete_properties( -#' path_asset = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved", +#' path_asset = addm("rgee/rgee_folder/rgee_ic_moved"), #' del_properties = c("message", "language") #' ) #' test_ic$getInfo() @@ -96,7 +97,7 @@ #' ee_manage_cancel_all_running_task() #' #' # 9. Delete EE objects or folders -#' ee_manage_delete("users/datacolecfbf/rgee/") +#' ee_manage_delete(addm("rgee/")) #' } #' @export ee_manage_create <- function(path_asset, asset_type = "Folder", quiet = FALSE) { @@ -208,12 +209,12 @@ ee_manage_delete <- function(path_asset, quiet = FALSE, strict = TRUE) { #' @name ee_manage-tools #' @export -ee_manage_assetlist <- function(path_asset, quiet = FALSE) { +ee_manage_assetlist <- function(path_asset, quiet = FALSE, strict = TRUE) { if (missing(path_asset)) { - stop("path_asset was not specified") + path_asset <- ee_get_assethome() } # path_asset exist? - path_asset <- ee_verify_filename(path_asset, strict = TRUE) + path_asset <- ee_verify_filename(path_asset, strict = strict) response <- ee$data$getInfo(path_asset) if (is.null(response)) stop("path_asset does not exist!") @@ -259,9 +260,9 @@ ee_manage_quota <- function(quiet = FALSE) { #' @name ee_manage-tools #' @export -ee_manage_copy <- function(path_asset, final_path, quiet = FALSE) { +ee_manage_copy <- function(path_asset, final_path, strict = TRUE, quiet = FALSE) { # path_asset exist? - path_asset <- ee_verify_filename(path_asset, strict = TRUE) + path_asset <- ee_verify_filename(path_asset, strict = strict) # Fix typos in final_path final_path <- ee_verify_filename(final_path, strict = FALSE) @@ -333,9 +334,9 @@ ee_manage_copy <- function(path_asset, final_path, quiet = FALSE) { #' @name ee_manage-tools #' @export -ee_manage_move <- function(path_asset, final_path, quiet = FALSE) { +ee_manage_move <- function(path_asset, final_path, strict = TRUE, quiet = FALSE) { # path_asset exist? - path_asset <- ee_verify_filename(path_asset, strict = TRUE) + path_asset <- ee_verify_filename(path_asset, strict = strict) # Fix typos in final_path final_path <- ee_verify_filename(final_path, strict = FALSE) @@ -406,9 +407,9 @@ ee_manage_move <- function(path_asset, final_path, quiet = FALSE) { #' @name ee_manage-tools #' @export -ee_manage_set_properties <- function(path_asset, add_properties) { +ee_manage_set_properties <- function(path_asset, add_properties, strict = TRUE) { # path_asset exist? - path_asset <- ee_verify_filename(path_asset, strict = TRUE) + path_asset <- ee_verify_filename(path_asset, strict = strict) # Is a Folder? header <- ee$data$getInfo(path_asset)[["type"]] @@ -425,9 +426,10 @@ ee_manage_set_properties <- function(path_asset, add_properties) { #' @name ee_manage-tools #' @export ee_manage_delete_properties <- function(path_asset, - del_properties = "ALL") { + del_properties = "ALL", + strict = TRUE) { # path_asset exist? - path_asset <- ee_verify_filename(path_asset, strict = TRUE) + path_asset <- ee_verify_filename(path_asset, strict = strict) # Is a Folder? header <- ee$data$getInfo(path_asset)[["type"]] @@ -451,30 +453,51 @@ ee_manage_delete_properties <- function(path_asset, #' @name ee_manage-tools #' @export ee_manage_asset_access <- function(path_asset, + owner = NULL, editor = NULL, viewer = NULL, all_users_can_read = TRUE, quiet = FALSE) { - bindings_template <- getOption('rgee.manage.setIamPolicy') - bindings_template$bindings[[2]]$members <- paste0('user:', editor) + if (is.null(owner)) { + current_policy <- ee$data$getIamPolicy(ee_get_assethome()) + owner <- tryCatch( + expr = gsub("user:","",current_policy$bindings[[1]]$members), + error = function(e){ + stop( + "Imposible to determine the email owner. ", + "You must be the owner of the asset to change the IAM policy. ", + "Please define your google email address manually", + " (e.g. owner = \"data.colec.fbf@gmail.com)\"" + ) + }) + } + + bindings_template <- list(bindings = list( + list( + role = "roles/owner", + members = paste0("user:", owner) + ), + list( + role = "roles/editor", + members = NULL + ), + list( + role = "roles/viewer", + members = NULL + ) + )) + + if (!is.null(editor)) { + bindings_template$bindings[[2]]$members <- paste0('user:', editor) + } if (isTRUE(all_users_can_read)) { bindings_template$bindings[[3]]$members <- c(viewer,'allUsers') } else { - bindings_template$bindings[[3]]$members <- paste0('user:', viewer) + if (!is.null(viewer)) { + bindings_template$bindings[[3]]$members <- paste0('user:', viewer) + } } - - # Error arise when users use ee_Initialize without email argument. - # Unfortunately, email argument needs to be in the form of - # ee_Initialize(email = "xxx@gmail.com") if it is not realize the - # code will not work. Enhance in future versions of rgee. - tryCatch( - expr = ee$data$setIamPolicy(path_asset, bindings_template), - error = function(e) stop( - "Please run again ee_Initialize specifying", - " the full address of your email", - " (e.g. ee_Initialize(email = data.colec.fbf@gmail.com))" - ) - ) + ee$data$setIamPolicy(path_asset, bindings_template) invisible(TRUE) } diff --git a/R/ee_module.R b/R/ee_module.R index 4eceb4fe..f768719d 100644 --- a/R/ee_module.R +++ b/R/ee_module.R @@ -10,7 +10,7 @@ #' \dontrun{ #' library(rgee) #' -#' ee$Initialize() +#' ee_Initialize() #' #' ee_img <- ee$Image(0) #' ee_ic <- ee$ImageCollection(ee_img) diff --git a/R/ee_utils.R b/R/ee_utils.R index 4d0ae037..df2adc08 100644 --- a/R/ee_utils.R +++ b/R/ee_utils.R @@ -32,35 +32,7 @@ ee_utils_py_to_r <- function(x) { #' #' # Create sf object #' nc <- st_read(system.file("shape/nc.shp", package="sf")) -#' assetId <- sprintf("%s/%s",ee_get_assethome(),'sf_nc') -#' -#' # Method 1 -#' # 1. Pass the sf to a zip file #' zipfile <- ee_utils_shp_to_zip(nc) -#' -#' # 2. From local to gcs -#' gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') -#' -#' # 3. Pass the sf to a zip file -#' gcs_to_ee_table( -#' gs_uri = gs_uri, -#' assetId = assetId -#' ) -#' -#' # OPTIONAL: Monitoring progress -#' ee_monitoring() -#' -#' # OPTIONAL: Display results -#' ee_sf_01 <- ee$FeatureCollection(assetId) -#' Map$centerObject(ee_sf_01) -#' Map$addLayer(ee_sf_01) -#' -#' # Method 2 -#' ee_sf_02 <- sf_as_ee(x = nc, -#' assetId = assetId, -#' bucket = "rgee_dev") -#' Map$centerObject(ee_sf_02) -#' Map$addLayer(ee_sf_02) #' } #' @export ee_utils_shp_to_zip <- function(x, diff --git a/R/print.R b/R/print.R index 5f2f42bc..9988f0fc 100644 --- a/R/print.R +++ b/R/print.R @@ -1,11 +1,11 @@ #' print Earth Engine object #' @param x Earth Engine spatial object. +#' @param ... ignored #' @param type Character. What to show about the x object?. Three options are #' supported: "json", "simply", "ee_print". By default "simply". -#' @param ... ignored -#' @method print ee.computedobject.ComputedObject +#' @export print.ee.computedobject.ComputedObject <- - function(x, type = getOption("rgee.print.option"), ...) { + function(x, ..., type = getOption("rgee.print.option")) { if (type == "json") { str(x) } else if (type == "simply") { @@ -15,13 +15,6 @@ print.ee.computedobject.ComputedObject <- } } -#' Method for printing EarthEngineMap objects -#' @param x a EarthEngineMap object -setMethod('print', signature(x = "EarthEngineMap"), - function(x) { - print(methods::slot(x, "map")) - } -) #' Method for printing EarthEngineMap objects (show) #' @param object a EarthEngineMap object diff --git a/R/raster_as_ee.R b/R/raster_as_ee.R new file mode 100644 index 00000000..fd87f93e --- /dev/null +++ b/R/raster_as_ee.R @@ -0,0 +1,163 @@ +#' Convert a stars or stars-proxy object into an EE Image object +#' +#' @param x stars or stars-proxy object to be converted into an ee$Image. +#' @param assetId Character. Destination asset ID for the uploaded file. +#' @param command_line_tool_path Character. Path to the Earth Engine command line +#' tool. If NULL, rgee assumes is saved in the same path that your Python +#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). +#' @param overwrite Logical. If TRUE, the assetId will be overwritten. +#' @param bucket Character. Name of the GCS bucket. +#' @param monitoring Logical. If TRUE the exportation task will be monitored. +#' @param quiet Logical. Suppress info message. +#' +#' @return An ee$Image object +#' @family image upload functions +#' @examples +#' \dontrun{ +#' library(rgee) +#' library(stars) +#' ee_Initialize(gcs = TRUE) +#' +#' # Get the filename of a image +#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") +#' x <- read_stars(tif) +#' assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') +#' +#' # # Method 1 +#' # # 1. Move from local to gcs +#' # gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +#' +#' # # 2. Pass from gcs to asset +#' # gcs_to_ee_image( +#' # x = x, +#' # overwrite = TRUE, +#' # gs_uri = gs_uri, +#' # assetId = assetId +#' #) +#' +#' # # OPTIONAL: Monitoring progress +#' # ee_monitoring() +#' +#' # # OPTIONAL: Display results +#' # ee_stars_01 <- ee$Image(assetId) +#' # Map$centerObject(ee_stars_01) +#' # Map$addLayer(ee_stars_01) +#' +#' # # Method 2 +#' # ee_stars_02 <- stars_as_ee( +#' # x = x, +#' # overwrite = TRUE, +#' # assetId = assetId, +#' # bucket = "rgee_dev" +#' # ) +#' # Map$centerObject(ee_stars_02) +#' # Map$addLayer(ee_stars_02) +#' } +#' @export +stars_as_ee <- function(x, + assetId, + command_line_tool_path = NULL, + overwrite = FALSE, + monitoring = TRUE, + bucket = NULL, + quiet = FALSE) { + # Folder to save intermediate upload files + ee_temp <- tempdir() + + if (is.null(command_line_tool_path)) { + command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + } + + message("1. Converting stars (raster) object to GeoTIFF ... saving in /tmp") + stars_proxy <- ee_as_proxystars(x, temp_dir = ee_temp) + + message("2. From local to GCS") + gcs_filename <- local_to_gcs( + x = stars_proxy[[1]], + bucket = bucket, + quiet = quiet + ) + + message("3. From GCS to Earth Engine") + # Verify is the EE assets path is valid + assetId <- ee_verify_filename( + path_asset = assetId, + strict = FALSE + ) + + gcs_to_ee_image( + x = x, + gs_uri = gcs_filename, + overwrite = overwrite, + assetId = assetId, + command_line_tool_path = command_line_tool_path + ) + + if (isTRUE(monitoring)) { + ee_monitoring() + ee$Image(assetId) + } else { + assetId + } +} + + +#' Convert a Raster* object into an EE Image object +#' +#' @param x RasterLayer, RasterStack or RasterBrick object to be converted into +#' an ee$Image. +#' @param assetId Character. Destination asset ID for the uploaded file. +#' @param command_line_tool_path Character. Path to the Earth Engine command line +#' tool. If NULL, rgee assumes is saved in the same path that your Python +#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). +#' @param overwrite Logical. If TRUE, the assetId will be overwritten. +#' @param bucket Character. Name of the GCS bucket. +#' @param monitoring Logical. If TRUE the exportation task will be monitored. +#' @param quiet Logical. Suppress info message. +#' +#' @return An ee$Image object +#' @family image upload functions +#' +#' @examples +#' \dontrun{ +#' library(rgee) +#' library(stars) +#' ee_Initialize(gcs = TRUE) +#' +#' # Get the filename of a image +#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") +#' x <- read_stars(tif) +#' assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') +#' +#' # # Method 1 +#' # # 1. Move from local to gcs +#' # gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +#' # +#' # # 2. Pass from gcs to asset +#' # gcs_to_ee_image( +#' # x = x, +#' # overwrite = TRUE, +#' # gs_uri = gs_uri, +#' # assetId = assetId +#' # ) +#' # +#' # # OPTIONAL: Monitoring progress +#' # ee_monitoring() +#' # +#' # # OPTIONAL: Display results +#' # ee_raster_01 <- ee$Image(assetId) +#' # Map$centerObject(ee_raster_01) +#' # Map$addLayer(ee_raster_01) +#' # +#' # # Method 2 +#' # ee_raster_02 <- raster_as_ee( +#' # x = x, +#' # assetId = assetId, +#' # overwrite = TRUE, +#' # bucket = "rgee_dev" +#' # ) +#' # Map$centerObject(ee_raster_02) +#' # Map$addLayer(ee_raster_02) +#' } +#' @export +raster_as_ee <- stars_as_ee diff --git a/R/sf_as_ee.R b/R/sf_as_ee.R index cca11de0..28d5c9c9 100755 --- a/R/sf_as_ee.R +++ b/R/sf_as_ee.R @@ -1,10 +1,10 @@ #' Convert an sf object to an EE object #' -#' Convert an sf object to an ee$FeatureCollection +#' Load an sf object to Earth Engine. #' -#' @param x sf object to be converted into an ee$FeatureCollection. +#' @param x object of class sf, sfc or sfg. #' @param via Character. Upload method for sf objects. Three methods are -#' implemented 'getInfo', 'getInfo_to_asset' and 'gcs_to_asset'. See details. +#' implemented: 'getInfo', 'getInfo_to_asset' and 'gcs_to_asset'. See details. #' @param monitoring Logical. Ignore if via is not set as #' \code{getInfo_to_asset} or \code{gcs_to_asset}. If TRUE the exportation task #' will be monitored. @@ -29,32 +29,36 @@ #' tool. If NULL, rgee assumes is saved in the same path that your Python #' environment (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite A boolean argument which indicates indicating -#' whether "filename" should be overwritten. By default TRUE. +#' whether "filename" should be overwritten. Ignore if \code{via} argument +#' is "getInfo". By default TRUE. #' @param quiet Logical. Suppress info message. #' @param ... \code{st_read} arguments might be included. #' -#' @return An ee$FeatureCollection object +#' @return +#' When \code{via} is "getInfo" and \code{x} is either an sf or sfc object +#' with multiple geometries will return an \code{ee$FeatureCollection}. For +#' single sfc and sfg objects will return an \code{ee$Geometry$...}. +#' +#' If \code{via} is either "getInfo_to_asset" or "gcs_to_asset" and +#' monitoring is TRUE will return an \code{ee$FeatureCollection} otherwise +#' will return an unstarted task. #' #' @details #' \code{sf_as_ee} supports the upload of \code{sf} objects by three different -#' options: "getInfo", "getInfo_to_asset", and "gcs_to_asset". -#' When "getInfo" is set in the \code{via} argument the sf object is -#' transformed to GeoJSON (using \code{geojsonio::geojson_json}) and then -#' encrusted in an HTTP request using the server-side objects that are -#' implemented in the Earth Engine API (ee$Geometry). If the sf object is too -#' large (~ >1Mb) it is likely to cause bottlenecks since it is a temporary -#' file that will save in the request message not in their EE Assets. See -#' \href{https://developers.google.com/earth-engine/client_server}{Client -#' vs Server} documentation for more details. The second method implemented is -#' 'getInfo_to_asset'. It is similar to the previous one, with the difference -#' that the result will be saved in your Earth Engine Asset. For dealing -#' with very large spatial objects is preferable to use the third option -#' 'gcs_to_asset'. This option firstly saves the sf object as a *.shp file -#' in the /temp directory. Secondly, using the function \code{local_to_gcs} +#' options: "getInfo" (default), "getInfo_to_asset", and "gcs_to_asset". \code{getInfo} +#' transforms sf objects (sfg, sfc, or sf) to GeoJSON (using \code{geojsonio::geojson_json}) +#' and then encrusted them in an HTTP request using the server-side objects that are +#' implemented in the Earth Engine API (i.e. ee$Geometry$...). If the sf object is too +#' large (~ >1Mb) is likely to cause bottlenecks since it is a temporary +#' file that is not saved in your EE Assets (server-side). The second option implemented +#' is 'getInfo_to_asset'. It is similar to the previous one, with the difference +#' that after create the server-side object will save it in your Earth Engine +#' Assets. For dealing with very large spatial objects is preferable to use the +#' third option 'gcs_to_asset'. This option firstly saves the sf object as a *.shp +#' file in the /temp directory. Secondly, using the function \code{local_to_gcs} #' will move the shapefile from local to Google Cloud Storage. Finally, using #' the function \code{gcs_to_ee_table} the ESRI shapefile will be loaded -#' to their EE Assets. -#' See \href{https://developers.google.com/earth-engine/importing}{Importing +#' to their EE Assets. See \href{https://developers.google.com/earth-engine/importing}{Importing #' table data} documentation for more details. #' #' @examples @@ -76,9 +80,7 @@ #' ncol = 2, #' byrow = TRUE) %>% #' list() %>% -#' st_polygon() %>% -#' st_sfc(crs="EPSG:4326") %>% -#' st_sf() +#' st_polygon() #' #' holePoly <- sf_as_ee(x = toy_poly, evenOdd = FALSE) #' @@ -89,30 +91,30 @@ #' pt <- ee$Geometry$Point(c(1.5, 1.5)) #' #' # Check insideness with a contains operator. -#' print(holePoly$geometry()$contains(pt)$getInfo() %>% ee_utils_py_to_r()) -#' print(evenOddPoly$geometry()$contains(pt)$getInfo() %>% ee_utils_py_to_r()) -#' -#' # 2. Upload small geometries to EE asset -#' assetId <- sprintf("%s/%s", ee_get_assethome(), 'toy_poly') -#' eex <- sf_as_ee( -#' x = toy_poly, -#' overwrite = TRUE, -#' assetId = assetId, -#' via = "getInfo_to_asset") -#' -#' # 3. Upload large geometries to EE asset -#' ee_Initialize("data.colec.fbf", gcs = TRUE) +#' print(holePoly$contains(pt)$getInfo() %>% ee_utils_py_to_r()) +#' print(evenOddPoly$contains(pt)$getInfo() %>% ee_utils_py_to_r()) #' -#' assetId <- sprintf("%s/%s", ee_get_assethome(), 'toy_poly_gcs') -#' eex <- sf_as_ee( -#' x = toy_poly, -#' overwrite = TRUE, -#' assetId = assetId, -#' bucket = 'rgee_dev', -#' monitoring = FALSE, -#' via = 'gcs_to_asset' -#' ) -#' ee_monitoring() +#' # # 2. Upload small geometries to EE asset +#' # assetId <- sprintf("%s/%s", ee_get_assethome(), 'toy_poly') +#' # eex <- sf_as_ee( +#' # x = toy_poly, +#' # overwrite = TRUE, +#' # assetId = assetId, +#' # via = "getInfo_to_asset") + +#' # # 3. Upload large geometries to EE asset +#' # ee_Initialize(gcs = TRUE) + +#' # assetId <- sprintf("%s/%s", ee_get_assethome(), 'toy_poly_gcs') +#' # eex <- sf_as_ee( +#' # x = toy_poly, +#' # overwrite = TRUE, +#' # assetId = assetId, +#' # bucket = 'rgee_dev', +#' # monitoring = FALSE, +#' # via = 'gcs_to_asset' +#' # ) +#' # ee_monitoring() #' } #' @export sf_as_ee <- function(x, @@ -132,8 +134,12 @@ sf_as_ee <- function(x, stop("package sf required, please install it first") } - if (!any(class(x) %in% "sf")) { - stop("x needs to be an object of class sf") + if (!any(class(x) %in% c("sf", "sfc", "sfg"))) { + stop("x needs to be an object of class sf, sfc, sfg") + } + + if (any(class(x) %in% "sfg")) { + x <- sf::st_sfc(x, crs = proj) } # geodesic is null? @@ -151,9 +157,22 @@ sf_as_ee <- function(x, } # Transform x according to proj argument - x_proj <- sf::st_crs(proj)$epsg - x <- sf::st_transform(x, x_proj) - x_proj <- sprintf("EPSG:%s", sf::st_crs(x)$epsg) + proj <- sf::st_crs(proj)$epsg + if (is.na(proj)) { + stop( + "sf_as_ee only supports uploading sf objects with an SRC linked to", + " an EPSG code. Use rgee::local_to_gcs and rgee::gcs_to_ee_table to solve." + ) + } + # x_proj represent the projection of x + x_proj <- sf::st_crs(x)$epsg + if (x_proj == proj) { + x_proj <- sprintf("EPSG:%s", sf::st_crs(x)$epsg) + } else { + message(sprintf("Transforming current coordinates to EPSG:%s", proj)) + x <- sf::st_transform(x, proj) + x_proj <- sprintf("EPSG:%s", sf::st_crs(x)$epsg) + } if (via == "getInfo") { # sf to geojson @@ -189,11 +208,12 @@ sf_as_ee <- function(x, # geojson to assset ee_task <- ee_table_to_asset( - collection = sf_fc, + collection = ee$FeatureCollection(sf_fc), overwrite = overwrite, description = ee_description, assetId = assetId ) + if (isTRUE(monitoring)) { ee_task$start() ee_monitoring(ee_task) @@ -225,12 +245,19 @@ sf_as_ee <- function(x, ) message("3. From GCS to Earth Engine") + # Verify is the EE assets path is valid + assetId <- ee_verify_filename( + path_asset = assetId, + strict = FALSE + ) + gcs_to_ee_table( gs_uri = gcs_filename, overwrite = overwrite, assetId = assetId, command_line_tool_path = command_line_tool_path ) + if (isTRUE(monitoring)) { ee_monitoring() ee$FeatureCollection(assetId) diff --git a/R/utils-upload.R b/R/utils-upload.R index 313e74dd..998da7b7 100755 --- a/R/utils-upload.R +++ b/R/utils-upload.R @@ -15,14 +15,11 @@ #' #' # Initialize a specific Earth Engine account and #' # Google Cloud Storage credentials -#' ee_Initialize( -#' email = "data.colec.fbf@gmail.com", -#' gcs = TRUE -#' ) +#' ee_Initialize(gcs = TRUE) #' -#' # Define an image. -#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") -#' local_to_gcs(x = tif, bucket = 'rgee_dev') +#' # # Define an image. +#' # tif <- system.file("tif/L7_ETMs.tif", package = "stars") +#' # local_to_gcs(x = tif, bucket = 'rgee_dev') #' } #' @export local_to_gcs <- function(x, @@ -38,11 +35,11 @@ local_to_gcs <- function(x, if (is.null(bucket)) { stop("Cloud Storage bucket was not defined") } - ee_user <- ee_exist_credentials() - if (is.na(ee_user$gcs_cre)) { + + if (is.na(getOption("rgee.gcs.auth"))) { stop( "Google Cloud Storage credentials were not loaded.", - ' Run ee_Initialize(email = "myemail", gcs = TRUE)', + ' Run ee_Initialize(..., gcs = TRUE)', " to fix it" ) } @@ -113,30 +110,32 @@ local_to_gcs <- function(x, #' # 1. Pass the sf to a zip file #' zipfile <- ee_utils_shp_to_zip(nc) #' -#' # 2. From local to gcs -#' gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') -#' -#' # 3. Pass the sf to a zip file -#' gcs_to_ee_table( -#' gs_uri = gs_uri, -#' assetId = assetId -#' ) -#' -#' # OPTIONAL: Monitoring progress -#' ee_monitoring() -#' -#' # OPTIONAL: Display results -#' ee_sf_01 <- ee$FeatureCollection(assetId) -#' Map$centerObject(ee_sf_01) -#' Map$addLayer(ee_sf_01) -#' -#' # Method 2 -#' ee_sf_02 <- sf_as_ee(x = nc, -#' assetId = assetId, -#' bucket = "rgee_dev", -#' via = 'gcs') -#' Map$centerObject(ee_sf_02) -#' Map$addLayer(ee_sf_02) +#' # # 2. From local to gcs +#' # gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') +#' # +#' # # 3. Pass the sf to a zip file +#' # gcs_to_ee_table( +#' # gs_uri = gs_uri, +#' # overwrite = TRUE, +#' # assetId = assetId +#' # ) +#' # +#' # # OPTIONAL: Monitoring progress +#' # ee_monitoring() +#' # +#' # # OPTIONAL: Display results +#' # ee_sf_01 <- ee$FeatureCollection(assetId) +#' # Map$centerObject(ee_sf_01) +#' # Map$addLayer(ee_sf_01) +#' # +#' # # Method 2 +#' # ee_sf_02 <- sf_as_ee(x = nc, +#' # assetId = assetId, +#' # overwrite = TRUE, +#' # bucket = "rgee_dev", +#' # via = 'gcs') +#' # Map$centerObject(ee_sf_02) +#' # Map$addLayer(ee_sf_02) #' } #' @export gcs_to_ee_table <- function(gs_uri, @@ -148,10 +147,17 @@ gcs_to_ee_table <- function(gs_uri, command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) } - command = sprintf( - "%s/earthengine upload table --asset_id %s %s", - command_line_tool_path, assetId, gs_uri - ) + if (command_line_tool_path == "") { + command = sprintf( + "earthengine upload table --asset_id %s %s", + assetId, gs_uri + ) + } else { + command = sprintf( + "%s/earthengine upload table --asset_id %s %s", + command_line_tool_path, assetId, gs_uri + ) + } if (!quiet) { message("Running the OS command:", command) @@ -186,11 +192,12 @@ gcs_to_ee_table <- function(gs_uri, #' Move a GeoTIFF image from GCS to their EE assets #' -#' Move a GeoTIFF image from GCS to their EE assets -#' -#' @param x stars object. +#' @param x An object of class stars or stars-proxy. #' @param gs_uri Character. It represents the full name of the -#' GeoTIFF file in a GCS bucket. +#' GeoTIFF file of \code{x} in a GCS bucket. +#' @param command_line_tool_path Character. Path to the Earth Engine command line +#' tool. If NULL, rgee assumes is saved in the same path that your Python +#' environment. #' @param assetId Character. How to call the file once uploaded #' to the Earth Engine Asset. e.g. users/datacolecfbf/mydatacollection. #' @param overwrite Logical. If TRUE, the assetId will be overwritten if @@ -209,39 +216,43 @@ gcs_to_ee_table <- function(gs_uri, #' @examples #' \dontrun{ #' library(rgee) -#' library(stars) +#' library(sf) #' ee_Initialize(gcs = TRUE) #' -#' # Get the filename of a image -#' tif <- system.file("tif/L7_ETMs.tif", package = "stars") -#' x <- read_stars(tif) -#' assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') +#' # Create sf object +#' nc <- st_read(system.file("shape/nc.shp", package="sf")) +#' assetId <- sprintf("%s/%s",ee_get_assethome(),'sf_nc') #' #' # Method 1 -#' # 1. Move from local to gcs -#' gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +#' # 1. Pass the sf to a zip file +#' zipfile <- ee_utils_shp_to_zip(nc) +#' +#' # # 2. From local to gcs +#' # gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') #' -#' # 2. Pass from gcs to asset -#' gcs_to_ee_image( -#' x = x, -#' gs_uri = gs_uri, -#' assetId = assetId -#' ) +#' # # 3. Pass the sf to a zip file +#' # gcs_to_ee_table( +#' # gs_uri = gs_uri, +#' # overwrite = TRUE, +#' # assetId = assetId +#' # ) #' -#' # OPTIONAL: Monitoring progress -#' ee_monitoring() +#' # # OPTIONAL: Monitoring progress +#' # ee_monitoring() #' -#' # OPTIONAL: Display results -#' ee_stars_01 <- ee$Image(assetId) -#' Map$centerObject(ee_stars_01) -#' Map$addLayer(ee_stars_01) +#' # # OPTIONAL: Display results +#' # ee_sf_01 <- ee$FeatureCollection(assetId) +#' # Map$centerObject(ee_sf_01) +#' # Map$addLayer(ee_sf_01) #' -#' # Method 2 -#' ee_sf_02 <- stars_as_ee(x = x, -#' assetId = assetId, -#' bucket = "rgee_dev") -#' Map$centerObject(ee_sf_02) -#' Map$addLayer(ee_sf_02) +#' # # Method 2 +#' # ee_sf_02 <- sf_as_ee(x = nc, +#' # assetId = assetId, +#' # overwrite = TRUE, +#' # bucket = "rgee_dev", +#' # via = 'gcs') +#' # Map$centerObject(ee_sf_02) +#' # Map$addLayer(ee_sf_02) #' } #' @export gcs_to_ee_image <- function(x, @@ -249,13 +260,26 @@ gcs_to_ee_image <- function(x, assetId, overwrite = FALSE, properties = NULL, + command_line_tool_path = NULL, start_time = "1970-01-01", end_time = "1970-01-01", pyramiding_policy = 'MEAN', quiet = FALSE) { + # Folder to save upload temporary files. tempdir_gee <- tempdir() - # Load python module + # If the command_line_tool_path does not exist + if (is.null(command_line_tool_path)) { + command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + } + + # Verify is the EE assets path is valid, if not try to fix + assetId <- ee_verify_filename( + path_asset = assetId, + strict = FALSE + ) + + # Load utils python module oauth_func_path <- system.file( "python/ee_utils.py", package = "rgee" @@ -326,14 +350,17 @@ gcs_to_ee_image <- function(x, manifest = manifest ) + #Command to run in console + if (command_line_tool_path == "") { + command <- sprintf("earthengine upload image --manifest '%s'", + json_path) + } else { + command <- sprintf("%s/earthengine upload image --manifest '%s'", + command_line_tool_path, json_path) + } + if (!quiet) { - cat( - blue('Uploading'), - green(gs_uri), - blue('to'), - green(assetId), - blue('... please wait\n') - ) + message("Running the OS command:", command) } if (isTRUE(overwrite)) { @@ -343,18 +370,29 @@ gcs_to_ee_image <- function(x, ) } - system( - command = sprintf("earthengine upload image --manifest '%s'", json_path), + upload_state <- system( + command = command, ignore.stdout = TRUE, ignore.stderr = TRUE ) + + if (upload_state != 0) { + stop( + sprintf( + "An error occurs when %s try to upload %s to %s", + bold("gcs_to_ee_image"), bold(gs_uri), bold(assetId) + ), + ". Please make sure that you set the ", + bold("command_line_tool_path"), + " argument correctly." + ) + } assetId } #' From sf object to Earth Engine FeatureCollection #' @noRd - ee_sf_to_fc <- function(x, proj, geodesic, evenOdd) { - +ee_sf_to_fc <- function(x, proj, geodesic, evenOdd) { if (!requireNamespace("sf", quietly = TRUE)) { stop("package sf required, please install it first") } @@ -366,25 +404,57 @@ gcs_to_ee_image <- function(x, oauth_func_path <- system.file("python/sf_as_ee.py", package = "rgee") sf_as_ee <- ee_source_python(oauth_func_path) - fc <- list() - for (index in seq_len(nrow(x))) { - feature <- x[index,] - sfc_feature <- sf::st_geometry(feature) - py_geometry <- geojsonio::geojson_json(sfc_feature,type = 'skip') - wkt_type <- class(sfc_feature)[1] # wkt type identifier + ngeometries <- if (any(class(x) %in% "sf")) nrow(x) else length(x) + + if (ngeometries == 1 & !any(class(x) %in% "sf")) { + sfc_feature <- sf::st_geometry(x) + py_geometry <- geojsonio::geojson_json(sfc_feature, type = 'skip') + sf_obj_class <- class(sfc_feature) + wkt_type <- sf_obj_class[sf_obj_class %in% ee_sf_comp()] + if (length(wkt_type) == 0) { + stop( + "sf_as_ee does not support objects of class ", + paste0(sf_obj_class,collapse = ", ") + ) + } ee_geometry <- sf_as_ee$sfg_as_ee_py(x = py_geometry, sfc_class = wkt_type, opt_proj = proj, opt_geodesic = geodesic, opt_evenOdd = evenOdd) - if (isFALSE(ee_geometry)) { - stop("rgee does not support the upload of GEOMETRYCOLLECTION", - " (sfg object).") + return(ee_geometry) + } else { + fc <- list() + for (index in seq_len(ngeometries)) { + feature <- x[index,] + sfc_feature <- sf::st_geometry(feature) + py_geometry <- geojsonio::geojson_json(sfc_feature, type = 'skip') + sf_obj_class <- class(sfc_feature) + wkt_type <- sf_obj_class[sf_obj_class %in% ee_sf_comp()] + ee_geometry <- sf_as_ee$sfg_as_ee_py(x = py_geometry, + sfc_class = wkt_type, + opt_proj = proj, + opt_geodesic = geodesic, + opt_evenOdd = evenOdd) + if (isFALSE(ee_geometry)) { + stop( + "sf_as_ee only support the upload of geometries of type: ", + paste0(ee_sf_comp(), collapse = ", "), + ". If you are using a GEOMETRYCOLLECTION geometry, please simplify ", + "it first or use the argument ", + "via = \"gcs_to_asset\"." + ) + } + + if (any(class(x) %in% "sf")) { + sf::st_geometry(feature) <- NULL + fc[[index]] <- ee$Feature(ee_geometry, as.list(feature)) + } else { + fc[[index]] <- ee$Feature(ee_geometry, list()) + } } - sf::st_geometry(feature) <- NULL - fc[[index]] <- ee$Feature(ee_geometry, as.list(feature)) + ee$FeatureCollection(fc) } - ee$FeatureCollection(fc) } #' Pass a character or stars object to stars-proxy @@ -416,3 +486,11 @@ ee_as_proxystars <- function(x, temp_dir = tempdir()) { stop('x argument not defined properly.') } } + +#' sf classes that ee supports +#' @noRd +ee_sf_comp <- function(){ + c("sfc_MULTIPOLYGON", "sfc_POLYGON", "sfc_LINESTRING", "sfc_MULTILINESTRING", + "sfc_POINT", "sfc_MULTIPOINT", "MULTIPOLYGON", "POLYGON", "LINESTRING", + "MULTILINESTRING", "POINT", "MULTIPOINT") +} diff --git a/README.md b/README.md index 4da6c463..2aebac73 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Additionally, several functions have been implemented to make simple the connect **Note that access to Google Earth Engine is only available to [registered users](https://earthengine.google.com/)**. -### More than 250+ examples using Google Earth Engine with R are available [here](http://csaybar.github.io/rgee-examples/) +### More than 250+ examples using GEE with R are available [here](http://csaybar.github.io/rgee-examples/) @@ -41,6 +41,16 @@ Additionally, several functions have been implemented to make simple the connect +### Quick Start User's Guide for rgee in Portuguese and Spanish. + + + +**Created by:** +- POR: Andres Luiz Lima Costa +- SPA: Antony Barja Ingaruca +### + + ## What is Google Earth Engine? [Google Earth Engine](https://earthengine.google.com/) is a cloud-based platform that allows users to have an easy access to a petabyte-scale archive of remote sensing data and run geospatial analysis on Google’s infrastructure. Currently, Google offers support only for Python and JavaScript. `rgee` will fill the gap **starting to provide support to R\!**. Below you will find the comparison between the syntax of `rgee` and the two Google-supported client libraries. diff --git a/inst/WORDLIST b/inst/WORDLIST index e7fbde45..3fb6e819 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -251,3 +251,5 @@ joss geojsonio bucles JOSS +EarthEnginemap +linetool diff --git a/inst/python/sf_as_ee.py b/inst/python/sf_as_ee.py index 469e8d07..2e52b026 100755 --- a/inst/python/sf_as_ee.py +++ b/inst/python/sf_as_ee.py @@ -7,22 +7,22 @@ >>> sfg_as_ee_py(x) -These functions are using in R/sf_as_ee.R +These functions are used in R/sf_as_ee.R """ - def sfg_as_ee_py(x, sfc_class, opt_proj, opt_geodesic, opt_evenOdd): x = re.sub(r':null', ':None', x) + sfc_class = re.sub('sfc_','',sfc_class) # sfg to ee - if sfc_class in ["sfc_MULTIPOLYGON","sfc_POLYGON"]: + if sfc_class in ["MULTIPOLYGON","POLYGON"]: return ee.Geometry(geo_json = eval(x), opt_proj = opt_proj, opt_geodesic = opt_geodesic, opt_evenOdd = opt_evenOdd) - elif sfc_class in ["sfc_MULTILINESTRING","sfc_LINESTRING"]: + elif sfc_class in ["MULTILINESTRING","LINESTRING"]: return ee.Geometry(geo_json = eval(x), opt_proj = opt_proj, opt_geodesic = opt_geodesic) - elif sfc_class in ["sfc_MULTIPOINT","sfc_POINT"]: + elif sfc_class in ["MULTIPOINT","POINT"]: return ee.Geometry(geo_json = eval(x), opt_proj = opt_proj) else: diff --git a/man/Map.Rd b/man/Map.Rd index 928ca18d..8bfef267 100644 --- a/man/Map.Rd +++ b/man/Map.Rd @@ -146,7 +146,7 @@ m2 + mapview(nc) # Case 6: mapedit library(mapedit) -my_geometry <- m2 \%>\% ee_as_mapview() \%>\% editMap() +# my_geometry <- m2 \%>\% ee_as_mapview() \%>\% editMap() } } \keyword{datasets} diff --git a/man/ee.Rd b/man/ee.Rd index 1a60f54c..d8ba49a5 100644 --- a/man/ee.Rd +++ b/man/ee.Rd @@ -19,7 +19,7 @@ and functions as well as sub-modules (e.g. \code{ee$Image}, \dontrun{ library(rgee) -ee$Initialize() +ee_Initialize() ee_img <- ee$Image(0) ee_ic <- ee$ImageCollection(ee_img) diff --git a/man/ee_Initialize.Rd b/man/ee_Initialize.Rd index a143fb64..c7a6fa36 100644 --- a/man/ee_Initialize.Rd +++ b/man/ee_Initialize.Rd @@ -8,15 +8,14 @@ ee_Initialize(email = NULL, drive = FALSE, gcs = FALSE, quiet = FALSE) } \arguments{ \item{email}{Character (optional, e.g. \code{data.colec.fbf@gmail.com}). The email -name is used as a folder inside the path \code{rgee::ee_get_earthengine_path()}. -This enable a multi-user support allowing to target a specific -Google identity.} +argument is used to create a folder inside the path \code{~/.config/earthengine/} +that save all credentials for a specific Google identity.} \item{drive}{Logical (optional). If TRUE, the drive credential -will be cached in the path \code{rgee::ee_get_earthengine_path()}.} +will be cached in the path \code{~/.config/earthengine/}.} -\item{gcs}{Logical. If TRUE, the Google Cloud Storage -credential will be cached in the path \code{rgee::ee_get_earthengine_path()}.} +\item{gcs}{Logical (optional). If TRUE, the Google Cloud Storage +credential will be cached in the path \code{~/.config/earthengine/}.} \item{quiet}{Logical. Suppress info messages.} } @@ -28,10 +27,10 @@ allows you to grant permission to manage resources. This function is a wrapper around \code{rgee::ee$Initialize()}. } \details{ -\code{ee_Initialize(...)} can also manage Google drive and Google +\code{ee_Initialize(...)} can manage Google drive and Google Cloud Storage resources using the R packages googledrive and googlecloudStorageR, respectively. By default, rgee does not require -them, these are only necessary for exporting and importing tasks. +them, these are only necessary to enable rgee I/O functionality. All user credentials are saved in the directory \code{~/.config/earthengine/}, if a user does not specify the email argument all user credentials will be saved in a subdirectory @@ -45,11 +44,11 @@ library(rgee) ee_Initialize() # Advanced init - Load full credentials -ee_Initialize( - email = "your_email@gmail.com", - drive = TRUE, - gcs = TRUE -) +# ee_Initialize( +# email = "your_email@gmail.com", +# drive = TRUE, +# gcs = TRUE +# ) ee_user_info() } diff --git a/man/ee_as_raster.Rd b/man/ee_as_raster.Rd index 6be5bb4b..f57146cb 100644 --- a/man/ee_as_raster.Rd +++ b/man/ee_as_raster.Rd @@ -6,7 +6,7 @@ \usage{ ee_as_raster( image, - region, + region = NULL, dsn = NULL, via = "getInfo", scale = NULL, @@ -19,15 +19,15 @@ ee_as_raster( \arguments{ \item{image}{ee$Image to be converted into a raster object} -\item{region}{EE Geometry Rectangle (ee$Geometry$Rectangle). The -CRS needs to be the same that the x argument otherwise it will be -forced. If not specified, image bounds will be taken.} +\item{region}{EE Geometry (ee$Geometry$Polygon) which specify the region +to export. CRS needs to be the same that the x argument otherwise it will be +forced. If not specified image bounds will be taken.} \item{dsn}{Character. Output filename. If missing, \code{ee_as_raster} will create a temporary file.} -\item{via}{Character. Method to fetch data about the object. Multiple -options supported. See details.} +\item{via}{Character. Method to fetch data about the object. Three methods +are implemented: "getInfo", "drive", "gcs". See details.} \item{scale}{Numeric. The resolution in meters per pixel. Defaults to the native resolution of the image asset.} @@ -107,22 +107,16 @@ img_02 <- ee_as_raster( ) ## gcs - Method 03 -img_03 <- ee_as_raster( - image = img, - region = geometry, - container = "rgee_dev", - via = "gcs" -) +# img_03 <- ee_as_raster( +# image = img, +# region = geometry, +# container = "rgee_dev", +# via = "gcs" +# ) # OPTIONAL: Delete containers -ee_clean_container( - name = "rgee_backup", - type = "drive" -) -ee_clean_container( - name = "rgee_dev", - type = "gcs" -) +# ee_clean_container(name = "rgee_backup", type = "drive") +# ee_clean_container(name = "rgee_dev", type = "gcs") } } \seealso{ diff --git a/man/ee_as_sf.Rd b/man/ee_as_sf.Rd index e6bdaf54..fc21db16 100644 --- a/man/ee_as_sf.Rd +++ b/man/ee_as_sf.Rd @@ -104,11 +104,11 @@ sf_randomPoints_drive <- ee_as_sf( ) # Download via GCS -sf_randomPoints_gcs <- ee_as_sf( - x = subset, - via = 'gcs', - container = 'rgee_dev' #GCS bucket name -) +# sf_randomPoints_gcs <- ee_as_sf( +# x = subset, +# via = 'gcs', +# container = 'rgee_dev' #GCS bucket name +# ) } } \concept{vector download functions} diff --git a/man/ee_as_stars.Rd b/man/ee_as_stars.Rd index cf90ea90..9b437305 100644 --- a/man/ee_as_stars.Rd +++ b/man/ee_as_stars.Rd @@ -6,7 +6,7 @@ \usage{ ee_as_stars( image, - region, + region = NULL, dsn = NULL, via = "getInfo", scale = NULL, @@ -19,15 +19,15 @@ ee_as_stars( \arguments{ \item{image}{ee$Image to be converted into a stars object} -\item{region}{EE Geometry Rectangle (ee$Geometry$Rectangle). The -CRS needs to be the same that the x argument otherwise it will be -forced. If not specified, image bounds will be taken.} +\item{region}{EE Geometry (ee$Geometry$Polygon) which specify the region +to export. CRS needs to be the same that the x argument otherwise it will be +forced. If not specified image bounds will be taken.} \item{dsn}{Character. Output filename. If missing, \code{ee_as_stars} will create a temporary file.} -\item{via}{Character. Method to fetch data about the object. Multiple -options supported. See details.} +\item{via}{Character. Method to fetch data about the object. Three methods +are implemented: "getInfo", "drive", "gcs". See details.} \item{scale}{Numeric. The resolution in meters per pixel. Defaults to the native resolution of the image asset.} @@ -107,22 +107,16 @@ img_02 <- ee_as_stars( ) ## gcs - Method 03 -img_03 <- ee_as_stars( - image = img, - region = geometry, - container = "rgee_dev", - via = "gcs" -) +# img_03 <- ee_as_stars( +# image = img, +# region = geometry, +# container = "rgee_dev", +# via = "gcs" +#) # OPTIONAL: Delete containers -ee_clean_container( - name = "rgee_backup", - type = "drive" -) -ee_clean_container( - name = "rgee_dev", - type = "gcs" -) +# ee_clean_container(name = "rgee_backup", type = "drive") +# ee_clean_container(name = "rgee_dev", type = "gcs") } } \seealso{ diff --git a/man/ee_as_thumbnail.Rd b/man/ee_as_thumbnail.Rd index 9224e5f6..2c843cd9 100644 --- a/man/ee_as_thumbnail.Rd +++ b/man/ee_as_thumbnail.Rd @@ -111,8 +111,7 @@ plot( arequipa_region <- nc \%>\% st_bbox() \%>\% st_as_sfc() \%>\% - sf_as_ee() \%>\% - ee$FeatureCollection$geometry() + sf_as_ee() arequipa_dem <- ee_as_thumbnail( image = image, diff --git a/man/ee_clean_credentials.Rd b/man/ee_clean_credentials.Rd index 984bec1f..a91a8b28 100644 --- a/man/ee_clean_credentials.Rd +++ b/man/ee_clean_credentials.Rd @@ -21,8 +21,8 @@ the path \code{rgee::ee_get_earthengine_path()}. \dontrun{ library(rgee) -ee_clean_credentials() -ee_clean_credentials('data.colec.fbf@gmail.com') +# ee_clean_credentials() +# ee_clean_credentials('data.colec.fbf@gmail.com') } } diff --git a/man/ee_clean_pyenv.Rd b/man/ee_clean_pyenv.Rd index d243fba5..d71c205d 100644 --- a/man/ee_clean_pyenv.Rd +++ b/man/ee_clean_pyenv.Rd @@ -11,8 +11,8 @@ Remove rgee system variables from .Renviron } \examples{ \dontrun{ -library(rgee) -ee_clean_pyenv() +# library(rgee) +# ee_clean_pyenv() } } \seealso{ diff --git a/man/ee_drive_to_local.Rd b/man/ee_drive_to_local.Rd index 73f936c5..1045e31e 100644 --- a/man/ee_drive_to_local.Rd +++ b/man/ee_drive_to_local.Rd @@ -4,13 +4,7 @@ \alias{ee_drive_to_local} \title{Move results from Google Drive to a local directory} \usage{ -ee_drive_to_local( - task, - dsn, - overwrite = TRUE, - consider = interactive(), - quiet = FALSE -) +ee_drive_to_local(task, dsn, overwrite = TRUE, consider = TRUE, quiet = FALSE) } \arguments{ \item{task}{List generated after finished correctly a EE task. See details.} @@ -80,7 +74,7 @@ cloudMaskL457 <- function(image) { } ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_ROI)$ + filterBounds(ee$FeatureCollection(ee_ROI))$ filterDate("2011-01-01", "2011-12-31")$ map(cloudMaskL457) @@ -94,7 +88,7 @@ task_img <- ee_image_to_drive( image = mean_l5_Amarakaeri, folder = "Amarakaeri", fileFormat = "GEO_TIFF", - region = ee_ROI$geometry(), + region = ee_ROI, fileNamePrefix = paste0("my_image", Sys.time()) ) diff --git a/man/ee_gcs_to_local.Rd b/man/ee_gcs_to_local.Rd index 4dcbc06d..63b78eae 100644 --- a/man/ee_gcs_to_local.Rd +++ b/man/ee_gcs_to_local.Rd @@ -66,7 +66,7 @@ cloudMaskL457 <- function(image) { } ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_ROI)$ + filterBounds(ee$FeatureCollection(ee_ROI))$ filterDate("2011-01-01", "2011-12-31")$ map(cloudMaskL457) @@ -75,20 +75,20 @@ mean_l5 <- ic_l5$mean()$rename("NDVI") mean_l5 <- mean_l5$reproject(crs = "EPSG:4326", scale = 500) mean_l5_Amarakaeri <- mean_l5$clip(ee_ROI) -# Move results from Earth Engine to Drive -task_img <- ee_image_to_gcs( - image = mean_l5_Amarakaeri, - bucket = "rgee_dev", - fileFormat = "GEO_TIFF", - region = ee_ROI$geometry(), - fileNamePrefix = paste0("my_image", Sys.time()) -) +## Move results from Earth Engine to Drive +# task_img <- ee_image_to_gcs( +# image = mean_l5_Amarakaeri, +# bucket = "rgee_dev", +# fileFormat = "GEO_TIFF", +# region = ee_ROI, +# fileNamePrefix = paste0("my_image", Sys.time()) +#) -task_img$start() -ee_monitoring(task_img) +# task_img$start() +# ee_monitoring(task_img) -# Move results from Drive to local -img <- ee_gcs_to_local(task = task_img) +## Move results from Drive to local +# img <- ee_gcs_to_local(task = task_img) } } \seealso{ diff --git a/man/ee_image_info.Rd b/man/ee_image_info.Rd index 95d7b61b..2e046ef7 100644 --- a/man/ee_image_info.Rd +++ b/man/ee_image_info.Rd @@ -7,7 +7,7 @@ ee_image_info(image, getsize = TRUE, compression_ratio = 20, quiet = FALSE) } \arguments{ -\item{image}{EE Image object.} +\item{image}{Single-band EE Image object.} \item{getsize}{Logical. If TRUE, the size of the object will be estimated.} @@ -24,7 +24,7 @@ number of columns (ncol), total number of pixels (total_pixel), and image size (image_size). } \description{ -Get the approximate number of rows, cols, and size of an +Get the approximate number of rows, cols, and size of an single-band Earth Engine Image. } \examples{ @@ -37,7 +37,7 @@ srtm <- ee$Image("CGIAR/SRTM90_V4") ee_image_info(srtm) # Landast8 -l8 <- ee$Image("LANDSAT/LC08/C01/T1_SR/LC08_038029_20180810") +l8 <- ee$Image("LANDSAT/LC08/C01/T1_SR/LC08_038029_20180810")$select("B4") ee_image_info(l8) } } diff --git a/man/ee_image_to_asset.Rd b/man/ee_image_to_asset.Rd index 15b3439d..0d771ff6 100644 --- a/man/ee_image_to_asset.Rd +++ b/man/ee_image_to_asset.Rd @@ -103,7 +103,7 @@ cloudMaskL457 <- function(image) { } ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_ROI)$ + filterBounds(ee$FeatureCollection(ee_ROI))$ filterDate("2011-01-01", "2011-12-31")$ map(cloudMaskL457) @@ -117,8 +117,9 @@ assetid <- paste0(ee_get_assethome(), '/l5_Amarakaeri') task_img <- ee_image_to_asset( image = mean_l5_Amarakaeri, assetId = assetid, + overwrite = TRUE, scale = 500, - region = ee_ROI$geometry() + region = ee_ROI ) task_img$start() diff --git a/man/ee_image_to_drive.Rd b/man/ee_image_to_drive.Rd index 3b23c036..0e3cdef0 100644 --- a/man/ee_image_to_drive.Rd +++ b/man/ee_image_to_drive.Rd @@ -103,6 +103,7 @@ ROI <- c(rlist$xmin, rlist$ymin, rlist$xmax, rlist$ymax, rlist$xmin, rlist$ymax, rlist$xmin, rlist$ymin) + ee_ROI <- matrix(ROI, ncol = 2, byrow = TRUE) \%>\% list() \%>\% st_polygon() \%>\% @@ -123,7 +124,7 @@ cloudMaskL457 <- function(image) { } ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_ROI)$ + filterBounds(ee$FeatureCollection(ee_ROI))$ filterDate("2011-01-01", "2011-12-31")$ map(cloudMaskL457) @@ -136,7 +137,7 @@ mean_l5_Amarakaeri <- mean_l5$clip(ee_ROI) task_img <- ee_image_to_drive( image = mean_l5_Amarakaeri, fileFormat = "GEO_TIFF", - region = ee_ROI$geometry(), + region = ee_ROI, fileNamePrefix = "my_image" ) diff --git a/man/ee_image_to_gcs.Rd b/man/ee_image_to_gcs.Rd index 6d839405..0134bfbd 100644 --- a/man/ee_image_to_gcs.Rd +++ b/man/ee_image_to_gcs.Rd @@ -124,7 +124,7 @@ cloudMaskL457 <- function(image) { } ic_l5 <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_ROI)$ + filterBounds(ee$FeatureCollection(ee_ROI))$ filterDate("2011-01-01", "2011-12-31")$ map(cloudMaskL457) @@ -133,21 +133,21 @@ mean_l5 <- ic_l5$mean()$rename("NDVI") mean_l5 <- mean_l5$reproject(crs = "EPSG:4326", scale = 500) mean_l5_Amarakaeri <- mean_l5$clip(ee_ROI) -# Move results from Earth Engine to Drive -task_img <- ee_image_to_gcs( - image = mean_l5_Amarakaeri, - bucket = "rgee_dev", - fileFormat = "GEO_TIFF", - region = ee_ROI$geometry(), - fileNamePrefix = "my_image" -) - -task_img$start() -ee_monitoring(task_img) - -# Move results from Drive to local -ee_gcs_to_local(task = task_img) -plot(img) +# Move results from Earth Engine to GCS +# task_img <- ee_image_to_gcs( +# image = mean_l5_Amarakaeri, +# bucket = "rgee_dev", +# fileFormat = "GEO_TIFF", +# region = ee_ROI, +# fileNamePrefix = "my_image" +# ) +# +# task_img$start() +# ee_monitoring(task_img) + +# Move results from GCS to local +# ee_gcs_to_local(task = task_img) +# plot(img) } } \seealso{ diff --git a/man/ee_install.Rd b/man/ee_install.Rd index 257cdea9..e899f0da 100644 --- a/man/ee_install.Rd +++ b/man/ee_install.Rd @@ -42,8 +42,8 @@ changes.} } \examples{ \dontrun{ -library(rgee) -ee_install() #It is just necessary once +# library(rgee) +# ee_install() #It is just necessary once } } \seealso{ diff --git a/man/ee_install_set_pyenv.Rd b/man/ee_install_set_pyenv.Rd index aa2bc347..2cbe7969 100644 --- a/man/ee_install_set_pyenv.Rd +++ b/man/ee_install_set_pyenv.Rd @@ -4,12 +4,12 @@ \alias{ee_install_set_pyenv} \title{Set the Python environment to be used by rgee} \usage{ -ee_install_set_pyenv(py_path, py_env = "rgee") +ee_install_set_pyenv(py_path = NULL, py_env = NULL) } \arguments{ \item{py_path}{The path to a Python interpreter.} -\item{py_env}{The name of the environment. By default "rgee".} +\item{py_env}{The name of the environment} } \description{ This function create a new environment variable called 'EARTHENGINE_PYTHON'. diff --git a/man/ee_install_upgrade.Rd b/man/ee_install_upgrade.Rd index 98fe3ee9..6bdb6a45 100644 --- a/man/ee_install_upgrade.Rd +++ b/man/ee_install_upgrade.Rd @@ -4,11 +4,17 @@ \alias{ee_install_upgrade} \title{Upgrade the Earth Engine Python API} \usage{ -ee_install_upgrade(version = NULL) +ee_install_upgrade( + version = NULL, + earthengine_env = Sys.getenv("EARTHENGINE_ENV") +) } \arguments{ \item{version}{Character. The Earth Engine Python API version to upgrade. By default \code{rgee::ee_version()}.} + +\item{earthengine_env}{Character. The name, or full path, of the +environment in which the earthengine-api packages are to be installed.} } \description{ Upgrade the Earth Engine Python API diff --git a/man/ee_manage-tools.Rd b/man/ee_manage-tools.Rd index e35a0e9b..6a167273 100644 --- a/man/ee_manage-tools.Rd +++ b/man/ee_manage-tools.Rd @@ -20,20 +20,21 @@ ee_manage_create(path_asset, asset_type = "Folder", quiet = FALSE) ee_manage_delete(path_asset, quiet = FALSE, strict = TRUE) -ee_manage_assetlist(path_asset, quiet = FALSE) +ee_manage_assetlist(path_asset, quiet = FALSE, strict = TRUE) ee_manage_quota(quiet = FALSE) -ee_manage_copy(path_asset, final_path, quiet = FALSE) +ee_manage_copy(path_asset, final_path, strict = TRUE, quiet = FALSE) -ee_manage_move(path_asset, final_path, quiet = FALSE) +ee_manage_move(path_asset, final_path, strict = TRUE, quiet = FALSE) -ee_manage_set_properties(path_asset, add_properties) +ee_manage_set_properties(path_asset, add_properties, strict = TRUE) -ee_manage_delete_properties(path_asset, del_properties = "ALL") +ee_manage_delete_properties(path_asset, del_properties = "ALL", strict = TRUE) ee_manage_asset_access( path_asset, + owner = NULL, editor = NULL, viewer = NULL, all_users_can_read = TRUE, @@ -67,6 +68,8 @@ of an EE object. See details.} \item{del_properties}{Character. Names of properties to be deleted. See details.} +\item{owner}{Character vector. Define owner user in the IAM Policy.} + \item{editor}{Character vector. Define editor users in the IAM Policy.} \item{viewer}{Character vector. Define viewer users in the IAM Policy.} @@ -88,58 +91,58 @@ the properties. } \examples{ \dontrun{ - library(rgee) ee_Initialize() ee_user_info() # Change datacolecfbf by your EE user to be able to reproduce - +user <- ee_get_assethome() +addm <- function(x) sprintf("\%s/\%s",user, x) # 1. Create a folder or Image Collection # Change path asset according to your specific user -ee_manage_create("users/datacolecfbf/rgee") +ee_manage_create(addm("rgee")) # 1. List all the elements inside a folder or a ImageCollection -ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee") +ee_manage_assetlist(path_asset = addm("rgee")) # 2. Create a Folder or a ImageCollection ee_manage_create( - path_asset = "users/datacolecfbf/rgee/rgee_folder", + path_asset = addm("rgee/rgee_folder"), asset_type = "Folder" ) ee_manage_create( - path_asset = "users/datacolecfbf/rgee/rgee_ic", + path_asset = addm("rgee/rgee_ic"), asset_type = "ImageCollection" ) -ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee") +ee_manage_assetlist(path_asset = addm("rgee")) # 3. Shows Earth Engine quota ee_manage_quota() # 4. Move an EE object to another folder ee_manage_move( - path_asset = "users/datacolecfbf/rgee/rgee_ic", - final_path = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved" + path_asset = addm("rgee/rgee_ic"), + final_path = addm("rgee/rgee_folder/rgee_ic_moved") ) -ee_manage_assetlist(path_asset = "users/datacolecfbf/rgee/rgee_folder") +ee_manage_assetlist(path_asset = addm("rgee/rgee_folder")) # 5. Set properties to an EE object. ee_manage_set_properties( - path_asset = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved", + path_asset = addm("rgee/rgee_folder/rgee_ic_moved"), add_properties = list(message = "hello-world", language = "R") ) -ic_id <- "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved" +ic_id <- addm("rgee/rgee_folder/rgee_ic_moved") test_ic <- ee$ImageCollection(ic_id) test_ic$getInfo() # 6. Delete properties ee_manage_delete_properties( - path_asset = "users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved", + path_asset = addm("rgee/rgee_folder/rgee_ic_moved"), del_properties = c("message", "language") ) test_ic$getInfo() @@ -152,7 +155,7 @@ ee_manage_task() ee_manage_cancel_all_running_task() # 9. Delete EE objects or folders -ee_manage_delete("users/datacolecfbf/rgee/") +ee_manage_delete(addm("rgee/")) } } \author{ diff --git a/man/ee_table_to_asset.Rd b/man/ee_table_to_asset.Rd index b42b6093..621475c9 100644 --- a/man/ee_table_to_asset.Rd +++ b/man/ee_table_to_asset.Rd @@ -54,12 +54,13 @@ ee_ROI <- matrix(ROI, ncol = 2, byrow = TRUE) \%>\% sf_as_ee() amk_fc <- ee$FeatureCollection( - list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) + list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) ) assetid <- paste0(ee_get_assethome(), '/geom_Amarakaeri') task_vector <- ee_table_to_asset( collection = amk_fc, + overwrite = TRUE, assetId = assetid ) task_vector$start() diff --git a/man/ee_table_to_drive.Rd b/man/ee_table_to_drive.Rd index 2b505736..e2a5223f 100644 --- a/man/ee_table_to_drive.Rd +++ b/man/ee_table_to_drive.Rd @@ -69,7 +69,7 @@ ee_ROI <- matrix(ROI, ncol = 2, byrow = TRUE) \%>\% sf_as_ee() amk_fc <- ee$FeatureCollection( - list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) + list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) ) task_vector <- ee_table_to_drive( diff --git a/man/ee_table_to_gcs.Rd b/man/ee_table_to_gcs.Rd index b1995cc8..b4c99df5 100644 --- a/man/ee_table_to_gcs.Rd +++ b/man/ee_table_to_gcs.Rd @@ -67,19 +67,19 @@ ee_ROI <- matrix(ROI, ncol = 2, byrow = TRUE) \%>\% sf_as_ee() amk_fc <- ee$FeatureCollection( - list(ee$Feature(ee_ROI$geometry(), list(name = "Amarakaeri"))) + list(ee$Feature(ee_ROI, list(name = "Amarakaeri"))) ) -task_vector <- ee_table_to_gcs( - collection = amk_fc, - bucket = "rgee_dev", - fileFormat = "SHP", - fileNamePrefix = "geom_Amarakaeri" -) -task_vector$start() -ee_monitoring(task_vector) # optional -amk_geom <- ee_gcs_to_local(task = task_vector) -plot(amk_geom$geometry, border = "red", lwd = 10) +#task_vector <- ee_table_to_gcs( +# collection = amk_fc, +# bucket = "rgee_dev", +# fileFormat = "SHP", +# fileNamePrefix = "geom_Amarakaeri" +#) +#task_vector$start() +#ee_monitoring(task_vector) # optional +#amk_geom <- ee_gcs_to_local(task = task_vector) +#plot(sf::read_sf(amk_geom[3]), border = "red", lwd = 10) } } \seealso{ diff --git a/man/ee_utils_shp_to_zip.Rd b/man/ee_utils_shp_to_zip.Rd index 4ed8593f..eb1c6062 100644 --- a/man/ee_utils_shp_to_zip.Rd +++ b/man/ee_utils_shp_to_zip.Rd @@ -32,35 +32,7 @@ ee_Initialize(gcs = TRUE) # Create sf object nc <- st_read(system.file("shape/nc.shp", package="sf")) -assetId <- sprintf("\%s/\%s",ee_get_assethome(),'sf_nc') - -# Method 1 -# 1. Pass the sf to a zip file zipfile <- ee_utils_shp_to_zip(nc) - -# 2. From local to gcs -gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') - -# 3. Pass the sf to a zip file -gcs_to_ee_table( - gs_uri = gs_uri, - assetId = assetId -) - -# OPTIONAL: Monitoring progress -ee_monitoring() - -# OPTIONAL: Display results -ee_sf_01 <- ee$FeatureCollection(assetId) -Map$centerObject(ee_sf_01) -Map$addLayer(ee_sf_01) - -# Method 2 -ee_sf_02 <- sf_as_ee(x = nc, - assetId = assetId, - bucket = "rgee_dev") -Map$centerObject(ee_sf_02) -Map$addLayer(ee_sf_02) } } \seealso{ diff --git a/man/gcs_to_ee_image.Rd b/man/gcs_to_ee_image.Rd index 86e96639..6646d39b 100644 --- a/man/gcs_to_ee_image.Rd +++ b/man/gcs_to_ee_image.Rd @@ -10,6 +10,7 @@ gcs_to_ee_image( assetId, overwrite = FALSE, properties = NULL, + command_line_tool_path = NULL, start_time = "1970-01-01", end_time = "1970-01-01", pyramiding_policy = "MEAN", @@ -17,10 +18,10 @@ gcs_to_ee_image( ) } \arguments{ -\item{x}{stars object.} +\item{x}{An object of class stars or stars-proxy.} \item{gs_uri}{Character. It represents the full name of the -GeoTIFF file in a GCS bucket.} +GeoTIFF file of \code{x} in a GCS bucket.} \item{assetId}{Character. How to call the file once uploaded to the Earth Engine Asset. e.g. users/datacolecfbf/mydatacollection.} @@ -31,6 +32,10 @@ it exists.} \item{properties}{List. Set of parameters to be set up as properties of the EE object.} +\item{command_line_tool_path}{Character. Path to the Earth Engine command line +tool. If NULL, rgee assumes is saved in the same path that your Python +environment.} + \item{start_time}{Character. Sets the start time property to a number or date.} @@ -50,38 +55,42 @@ Move a GeoTIFF image from GCS to their EE assets \examples{ \dontrun{ library(rgee) -library(stars) +library(sf) ee_Initialize(gcs = TRUE) -# Get the filename of a image -tif <- system.file("tif/L7_ETMs.tif", package = "stars") -x <- read_stars(tif) -assetId <- sprintf("\%s/\%s",ee_get_assethome(),'stars_l7') +# Create sf object +nc <- st_read(system.file("shape/nc.shp", package="sf")) +assetId <- sprintf("\%s/\%s",ee_get_assethome(),'sf_nc') # Method 1 -# 1. Move from local to gcs -gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +# 1. Pass the sf to a zip file +zipfile <- ee_utils_shp_to_zip(nc) -# 2. Pass from gcs to asset -gcs_to_ee_image( - x = x, - gs_uri = gs_uri, - assetId = assetId -) +# # 2. From local to gcs +# gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') + +# # 3. Pass the sf to a zip file +# gcs_to_ee_table( +# gs_uri = gs_uri, +# overwrite = TRUE, +# assetId = assetId +# ) -# OPTIONAL: Monitoring progress -ee_monitoring() +# # OPTIONAL: Monitoring progress +# ee_monitoring() -# OPTIONAL: Display results -ee_stars_01 <- ee$Image(assetId) -Map$centerObject(ee_stars_01) -Map$addLayer(ee_stars_01) +# # OPTIONAL: Display results +# ee_sf_01 <- ee$FeatureCollection(assetId) +# Map$centerObject(ee_sf_01) +# Map$addLayer(ee_sf_01) -# Method 2 -ee_sf_02 <- stars_as_ee(x = x, - assetId = assetId, - bucket = "rgee_dev") -Map$centerObject(ee_sf_02) -Map$addLayer(ee_sf_02) +# # Method 2 +# ee_sf_02 <- sf_as_ee(x = nc, +# assetId = assetId, +# overwrite = TRUE, +# bucket = "rgee_dev", +# via = 'gcs') +# Map$centerObject(ee_sf_02) +# Map$addLayer(ee_sf_02) } } diff --git a/man/gcs_to_ee_table.Rd b/man/gcs_to_ee_table.Rd index 2ba17062..81903b0f 100644 --- a/man/gcs_to_ee_table.Rd +++ b/man/gcs_to_ee_table.Rd @@ -48,29 +48,31 @@ assetId <- sprintf("\%s/\%s",ee_get_assethome(),'sf_nc') # 1. Pass the sf to a zip file zipfile <- ee_utils_shp_to_zip(nc) -# 2. From local to gcs -gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') - -# 3. Pass the sf to a zip file -gcs_to_ee_table( - gs_uri = gs_uri, - assetId = assetId -) - -# OPTIONAL: Monitoring progress -ee_monitoring() - -# OPTIONAL: Display results -ee_sf_01 <- ee$FeatureCollection(assetId) -Map$centerObject(ee_sf_01) -Map$addLayer(ee_sf_01) - -# Method 2 -ee_sf_02 <- sf_as_ee(x = nc, - assetId = assetId, - bucket = "rgee_dev", - via = 'gcs') -Map$centerObject(ee_sf_02) -Map$addLayer(ee_sf_02) +# # 2. From local to gcs +# gs_uri <- local_to_gcs(x = zipfile, bucket = 'rgee_dev') +# +# # 3. Pass the sf to a zip file +# gcs_to_ee_table( +# gs_uri = gs_uri, +# overwrite = TRUE, +# assetId = assetId +# ) +# +# # OPTIONAL: Monitoring progress +# ee_monitoring() +# +# # OPTIONAL: Display results +# ee_sf_01 <- ee$FeatureCollection(assetId) +# Map$centerObject(ee_sf_01) +# Map$addLayer(ee_sf_01) +# +# # Method 2 +# ee_sf_02 <- sf_as_ee(x = nc, +# assetId = assetId, +# overwrite = TRUE, +# bucket = "rgee_dev", +# via = 'gcs') +# Map$centerObject(ee_sf_02) +# Map$addLayer(ee_sf_02) } } diff --git a/man/local_to_gcs.Rd b/man/local_to_gcs.Rd index bf52cb47..3906165e 100644 --- a/man/local_to_gcs.Rd +++ b/man/local_to_gcs.Rd @@ -27,14 +27,11 @@ library(stars) # Initialize a specific Earth Engine account and # Google Cloud Storage credentials -ee_Initialize( - email = "data.colec.fbf@gmail.com", - gcs = TRUE -) +ee_Initialize(gcs = TRUE) -# Define an image. -tif <- system.file("tif/L7_ETMs.tif", package = "stars") -local_to_gcs(x = tif, bucket = 'rgee_dev') +# # Define an image. +# tif <- system.file("tif/L7_ETMs.tif", package = "stars") +# local_to_gcs(x = tif, bucket = 'rgee_dev') } } \concept{generic upload functions} diff --git a/man/print-EarthEngineMap-method.Rd b/man/print-EarthEngineMap-method.Rd deleted file mode 100644 index 0aae5ed0..00000000 --- a/man/print-EarthEngineMap-method.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/print.R -\name{print,EarthEngineMap-method} -\alias{print,EarthEngineMap-method} -\title{Method for printing EarthEngineMap objects} -\usage{ -\S4method{print}{EarthEngineMap}(x) -} -\arguments{ -\item{x}{a EarthEngineMap object} -} -\description{ -Method for printing EarthEngineMap objects -} diff --git a/man/print.ee.computedobject.ComputedObject.Rd b/man/print.ee.computedobject.ComputedObject.Rd index dc6307c2..b64ee586 100644 --- a/man/print.ee.computedobject.ComputedObject.Rd +++ b/man/print.ee.computedobject.ComputedObject.Rd @@ -4,15 +4,15 @@ \alias{print.ee.computedobject.ComputedObject} \title{print Earth Engine object} \usage{ -\method{print}{ee.computedobject.ComputedObject}(x, type = getOption("rgee.print.option"), ...) +\method{print}{ee.computedobject.ComputedObject}(x, ..., type = getOption("rgee.print.option")) } \arguments{ \item{x}{Earth Engine spatial object.} +\item{...}{ignored} + \item{type}{Character. What to show about the x object?. Three options are supported: "json", "simply", "ee_print". By default "simply".} - -\item{...}{ignored} } \description{ print Earth Engine object diff --git a/man/raster_as_ee.Rd b/man/raster_as_ee.Rd index 606fc101..30e37908 100644 --- a/man/raster_as_ee.Rd +++ b/man/raster_as_ee.Rd @@ -1,12 +1,13 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_image.R +% Please edit documentation in R/raster_as_ee.R \name{raster_as_ee} \alias{raster_as_ee} -\title{Convert a Raster* object into an EE Image} +\title{Convert a Raster* object into an EE Image object} \usage{ raster_as_ee( x, assetId, + command_line_tool_path = NULL, overwrite = FALSE, monitoring = TRUE, bucket = NULL, @@ -19,6 +20,10 @@ an ee$Image.} \item{assetId}{Character. Destination asset ID for the uploaded file.} +\item{command_line_tool_path}{Character. Path to the Earth Engine command line +tool. If NULL, rgee assumes is saved in the same path that your Python +environment (ignore if \code{via} is not defined as "gcs_to_asset").} + \item{overwrite}{Logical. If TRUE, the assetId will be overwritten.} \item{monitoring}{Logical. If TRUE the exportation task will be monitored.} @@ -31,7 +36,7 @@ an ee$Image.} An ee$Image object } \description{ -Convert a Raster* object into an ee$Image. +Convert a Raster* object into an EE Image object } \examples{ \dontrun{ @@ -44,33 +49,35 @@ tif <- system.file("tif/L7_ETMs.tif", package = "stars") x <- read_stars(tif) assetId <- sprintf("\%s/\%s",ee_get_assethome(),'stars_l7') -# Method 1 -# 1. Move from local to gcs -gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') - -# 2. Pass from gcs to asset -gcs_to_ee_image( - x = x, - gs_uri = gs_uri, - assetId = assetId -) - -# OPTIONAL: Monitoring progress -ee_monitoring() - -# OPTIONAL: Display results -ee_raster_01 <- ee$Image(assetId) -Map$centerObject(ee_raster_01) -Map$addLayer(ee_raster_01) - -# Method 2 -ee_raster_02 <- raster_as_ee( - x = x, - assetId = assetId, - bucket = "rgee_dev" -) -Map$centerObject(ee_raster_02) -Map$addLayer(ee_raster_02) +# # Method 1 +# # 1. Move from local to gcs +# gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +# +# # 2. Pass from gcs to asset +# gcs_to_ee_image( +# x = x, +# overwrite = TRUE, +# gs_uri = gs_uri, +# assetId = assetId +# ) +# +# # OPTIONAL: Monitoring progress +# ee_monitoring() +# +# # OPTIONAL: Display results +# ee_raster_01 <- ee$Image(assetId) +# Map$centerObject(ee_raster_01) +# Map$addLayer(ee_raster_01) +# +# # Method 2 +# ee_raster_02 <- raster_as_ee( +# x = x, +# assetId = assetId, +# overwrite = TRUE, +# bucket = "rgee_dev" +# ) +# Map$centerObject(ee_raster_02) +# Map$addLayer(ee_raster_02) } } \seealso{ diff --git a/man/rgee-package.Rd b/man/rgee-package.Rd index d4befdec..82ebe848 100644 --- a/man/rgee-package.Rd +++ b/man/rgee-package.Rd @@ -9,7 +9,7 @@ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} Earth Engine client library for R. All of the 'Earth Engine' API classes, - modules, and functions are made available. Additional functions implemented include importing (exporting) of R (Earth Engine) + modules, and functions are made available. Additional functions implemented include importing (exporting) of R Earth Engine spatial objects, extraction of time series, interactive map display, assets management interface, and metadata display. See for further details. } diff --git a/man/sf_as_ee.Rd b/man/sf_as_ee.Rd index 90c6c187..ac6f4ec3 100644 --- a/man/sf_as_ee.Rd +++ b/man/sf_as_ee.Rd @@ -20,10 +20,10 @@ sf_as_ee( ) } \arguments{ -\item{x}{sf object to be converted into an ee$FeatureCollection.} +\item{x}{object of class sf, sfc or sfg.} \item{via}{Character. Upload method for sf objects. Three methods are -implemented 'getInfo', 'getInfo_to_asset' and 'gcs_to_asset'. See details.} +implemented: 'getInfo', 'getInfo_to_asset' and 'gcs_to_asset'. See details.} \item{assetId}{Character. Destination asset ID for the uploaded file. Ignore if \code{via} argument is "getInfo".} @@ -36,7 +36,8 @@ tool. If NULL, rgee assumes is saved in the same path that your Python environment (ignore if \code{via} is not defined as "gcs_to_asset").} \item{overwrite}{A boolean argument which indicates indicating -whether "filename" should be overwritten. By default TRUE.} +whether "filename" should be overwritten. Ignore if \code{via} argument +is "getInfo". By default TRUE.} \item{monitoring}{Logical. Ignore if via is not set as \code{getInfo_to_asset} or \code{gcs_to_asset}. If TRUE the exportation task @@ -63,31 +64,33 @@ in the specified CRS. If absent, defaults to TRUE if the CRS is geographic \item{...}{\code{st_read} arguments might be included.} } \value{ -An ee$FeatureCollection object +When \code{via} is "getInfo" and \code{x} is either an sf or sfc object +with multiple geometries will return an \code{ee$FeatureCollection}. For +single sfc and sfg objects will return an \code{ee$Geometry$...}. + +If \code{via} is either "getInfo_to_asset" or "gcs_to_asset" and +monitoring is TRUE will return an \code{ee$FeatureCollection} otherwise +will return an unstarted task. } \description{ -Convert an sf object to an ee$FeatureCollection +Load an sf object to Earth Engine. } \details{ \code{sf_as_ee} supports the upload of \code{sf} objects by three different -options: "getInfo", "getInfo_to_asset", and "gcs_to_asset". -When "getInfo" is set in the \code{via} argument the sf object is -transformed to GeoJSON (using \code{geojsonio::geojson_json}) and then -encrusted in an HTTP request using the server-side objects that are -implemented in the Earth Engine API (ee$Geometry). If the sf object is too -large (~ >1Mb) it is likely to cause bottlenecks since it is a temporary -file that will save in the request message not in their EE Assets. See -\href{https://developers.google.com/earth-engine/client_server}{Client -vs Server} documentation for more details. The second method implemented is -'getInfo_to_asset'. It is similar to the previous one, with the difference -that the result will be saved in your Earth Engine Asset. For dealing -with very large spatial objects is preferable to use the third option -'gcs_to_asset'. This option firstly saves the sf object as a *.shp file -in the /temp directory. Secondly, using the function \code{local_to_gcs} +options: "getInfo" (default), "getInfo_to_asset", and "gcs_to_asset". \code{getInfo} +transforms sf objects (sfg, sfc, or sf) to GeoJSON (using \code{geojsonio::geojson_json}) +and then encrusted them in an HTTP request using the server-side objects that are +implemented in the Earth Engine API (i.e. ee$Geometry$...). If the sf object is too +large (~ >1Mb) is likely to cause bottlenecks since it is a temporary +file that is not saved in your EE Assets (server-side). The second option implemented +is 'getInfo_to_asset'. It is similar to the previous one, with the difference +that after create the server-side object will save it in your Earth Engine +Assets. For dealing with very large spatial objects is preferable to use the +third option 'gcs_to_asset'. This option firstly saves the sf object as a *.shp +file in the /temp directory. Secondly, using the function \code{local_to_gcs} will move the shapefile from local to Google Cloud Storage. Finally, using the function \code{gcs_to_ee_table} the ESRI shapefile will be loaded -to their EE Assets. -See \href{https://developers.google.com/earth-engine/importing}{Importing +to their EE Assets. See \href{https://developers.google.com/earth-engine/importing}{Importing table data} documentation for more details. } \examples{ @@ -109,9 +112,7 @@ toy_poly <- matrix(data = c(-35,-10,-35,10,35,10,35,-10,-35,-10), ncol = 2, byrow = TRUE) \%>\% list() \%>\% - st_polygon() \%>\% - st_sfc(crs="EPSG:4326") \%>\% - st_sf() + st_polygon() holePoly <- sf_as_ee(x = toy_poly, evenOdd = FALSE) @@ -122,29 +123,27 @@ evenOddPoly <- sf_as_ee(toy_poly, evenOdd = TRUE) pt <- ee$Geometry$Point(c(1.5, 1.5)) # Check insideness with a contains operator. -print(holePoly$geometry()$contains(pt)$getInfo() \%>\% ee_utils_py_to_r()) -print(evenOddPoly$geometry()$contains(pt)$getInfo() \%>\% ee_utils_py_to_r()) - -# 2. Upload small geometries to EE asset -assetId <- sprintf("\%s/\%s", ee_get_assethome(), 'toy_poly') -eex <- sf_as_ee( - x = toy_poly, - overwrite = TRUE, - assetId = assetId, - via = "getInfo_to_asset") - -# 3. Upload large geometries to EE asset -ee_Initialize("data.colec.fbf", gcs = TRUE) - -assetId <- sprintf("\%s/\%s", ee_get_assethome(), 'toy_poly_gcs') -eex <- sf_as_ee( - x = toy_poly, - overwrite = TRUE, - assetId = assetId, - bucket = 'rgee_dev', - monitoring = FALSE, - via = 'gcs_to_asset' -) -ee_monitoring() +print(holePoly$contains(pt)$getInfo() \%>\% ee_utils_py_to_r()) +print(evenOddPoly$contains(pt)$getInfo() \%>\% ee_utils_py_to_r()) + +# # 2. Upload small geometries to EE asset +# assetId <- sprintf("\%s/\%s", ee_get_assethome(), 'toy_poly') +# eex <- sf_as_ee( +# x = toy_poly, +# overwrite = TRUE, +# assetId = assetId, +# via = "getInfo_to_asset") +# # 3. Upload large geometries to EE asset +# ee_Initialize(gcs = TRUE) +# assetId <- sprintf("\%s/\%s", ee_get_assethome(), 'toy_poly_gcs') +# eex <- sf_as_ee( +# x = toy_poly, +# overwrite = TRUE, +# assetId = assetId, +# bucket = 'rgee_dev', +# monitoring = FALSE, +# via = 'gcs_to_asset' +# ) +# ee_monitoring() } } diff --git a/man/stars_as_ee.Rd b/man/stars_as_ee.Rd index c1f7efb4..c97dbd46 100644 --- a/man/stars_as_ee.Rd +++ b/man/stars_as_ee.Rd @@ -1,12 +1,13 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_image.R +% Please edit documentation in R/raster_as_ee.R \name{stars_as_ee} \alias{stars_as_ee} -\title{Convert a stars or stars-proxy object into an EE Image} +\title{Convert a stars or stars-proxy object into an EE Image object} \usage{ stars_as_ee( x, assetId, + command_line_tool_path = NULL, overwrite = FALSE, monitoring = TRUE, bucket = NULL, @@ -18,6 +19,10 @@ stars_as_ee( \item{assetId}{Character. Destination asset ID for the uploaded file.} +\item{command_line_tool_path}{Character. Path to the Earth Engine command line +tool. If NULL, rgee assumes is saved in the same path that your Python +environment (ignore if \code{via} is not defined as "gcs_to_asset").} + \item{overwrite}{Logical. If TRUE, the assetId will be overwritten.} \item{monitoring}{Logical. If TRUE the exportation task will be monitored.} @@ -30,7 +35,7 @@ stars_as_ee( An ee$Image object } \description{ -Convert a stars or stars-proxy object into an ee$Image. +Convert a stars or stars-proxy object into an EE Image object } \examples{ \dontrun{ @@ -43,33 +48,35 @@ tif <- system.file("tif/L7_ETMs.tif", package = "stars") x <- read_stars(tif) assetId <- sprintf("\%s/\%s",ee_get_assethome(),'stars_l7') -# Method 1 -# 1. Move from local to gcs -gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') +# # Method 1 +# # 1. Move from local to gcs +# gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') -# 2. Pass from gcs to asset -gcs_to_ee_image( - x = x, - gs_uri = gs_uri, - assetId = assetId -) +# # 2. Pass from gcs to asset +# gcs_to_ee_image( +# x = x, +# overwrite = TRUE, +# gs_uri = gs_uri, +# assetId = assetId +#) -# OPTIONAL: Monitoring progress -ee_monitoring() +# # OPTIONAL: Monitoring progress +# ee_monitoring() -# OPTIONAL: Display results -ee_stars_01 <- ee$Image(assetId) -Map$centerObject(ee_stars_01) -Map$addLayer(ee_stars_01) +# # OPTIONAL: Display results +# ee_stars_01 <- ee$Image(assetId) +# Map$centerObject(ee_stars_01) +# Map$addLayer(ee_stars_01) -# Method 2 -ee_stars_02 <- stars_as_ee( - x = x, - assetId = assetId, - bucket = "rgee_dev" -) -Map$centerObject(ee_stars_02) -Map$addLayer(ee_stars_02) +# # Method 2 +# ee_stars_02 <- stars_as_ee( +# x = x, +# overwrite = TRUE, +# assetId = assetId, +# bucket = "rgee_dev" +# ) +# Map$centerObject(ee_stars_02) +# Map$addLayer(ee_stars_02) } } \seealso{ diff --git a/paper/paper.md b/paper/paper.md index 54bb23c2..abde932c 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -42,7 +42,7 @@ rgee is an Earth Engine (EE) client library for R that allows users to leverage ## Enhanced I/O -rgee implements several functions to support download/upload of spatial objects (Table \ref{table:1} and Table \ref{table:2}). For instance, to download vector (image) files one can use `ee_as_sf` (`ee_as_raster` or `ee_as_stars`). In rgee, all the functions from server to local side have the option to fetch data using an intermediate container (Google Drive or Google Cloud Storage) or through a REST call ("$getInfo"). Although the latter option performs a quick download, there is a request limit of 262144 pixels for ee.Image and 5000 elements for ee.FeatureCollection which makes it unsuitable for large objects. Other download functions, from server-side to others (see Table \ref{table:1}), are implemented to enable more customized download workflows. For example, using `ee_image_to_drive` and `ee_drive_to_local` users could create scripts which save results in the `.TFRecord` rather than the `.GeoTIFF` format. The upload process follows the same logic (Table \ref{table:2}). rgee includes `raster_as_ee` and `stars_as_ee` for uploading images and `sf_as_ee` for uploading vector data. Large uploads are only possible with an active Google Cloud Storage account. +rgee implements several functions to support download/upload of spatial objects (Table \ref{table:1} and Table \ref{table:2}). For instance, to download vector (image) files one can use `ee_as_sf` (`ee_as_raster` or `ee_as_stars`). In rgee, all the functions from server to local side have the option to fetch data using an intermediate container (Google Drive or Google Cloud Storage) or through a REST call ("$getInfo"). Although the latter option performs a quick download, there is a request limit of 262144 pixels for `ee$Image` and 5000 elements for `ee$FeatureCollection` which makes it unsuitable for large objects. Other download functions, from server-side to others (see Table \ref{table:1}), are implemented to enable more customized download workflows. For example, using `ee_image_to_drive` and `ee_drive_to_local` users could create scripts which save results in the `.TFRecord` rather than the `.GeoTIFF` format. The upload process follows the same logic (Table \ref{table:2}). rgee includes `raster_as_ee` and `stars_as_ee` for uploading images and `sf_as_ee` for uploading vector data. Large uploads are only possible with an active Google Cloud Storage account. | | | FROM | TO | RETURN | @@ -115,13 +115,13 @@ rgee offers interactive map display through `Map$addLayer`, an R function mimic library(rgee) ee_Initialize() -# Load an ee.Image +# Load an ee$Image image <- ee$Image("LANDSAT/LC08/C01/T1/LC08_044034_20140318") # Centers the map view Map$centerObject(image) -# Display the ee.Image +# Display the ee$Image Map$addLayer( eeObject = image, visParams = list(bands = c("B4", "B3", "B2"), max = 10000), @@ -133,7 +133,7 @@ Map$addLayer( ## Extraction of time series -rgee can extract values from `ee.Image` and `ee.ImageCollection` objects at a certain location based on `ee.Geometry`, `ee.Feature`, `ee.FeatureCollection` and `sf` objects. If the geometry is a polygon, users can summarize the values using built-in Earth Engine reducer functions. The code below explains how to extract the average areal rainfall from North Carolina counties using the [TerraClimate](https://developers.google.com/earth-engine/datasets/catalog/IDAHO_EPSCOR_TERRACLIMATE) dataset. +rgee can extract values from `ee$Image` and `ee$ImageCollection` objects at a certain location based on `ee$Geometry`, `ee$Feature`, `ee$FeatureCollection` and `sf` objects. If the geometry is a polygon, users can summarize the values using built-in Earth Engine reducer functions. The code below explains how to extract the average areal rainfall from North Carolina counties using the [TerraClimate](https://developers.google.com/earth-engine/datasets/catalog/IDAHO_EPSCOR_TERRACLIMATE) dataset. ```r library(ggplot2) @@ -144,7 +144,8 @@ library(sf) ee_Initialize() -# Image or ImageCollection (mean composite) +# Filter the terraclimate dataset by dates, reproject +# and select only the band "pr". terraclimate <- ee$ImageCollection("IDAHO_EPSCOR/TERRACLIMATE")$ filterDate("2001-01-01", "2002-01-01")$ map(function(x) x$reproject("EPSG:4326")$select("pr")) @@ -157,6 +158,7 @@ ee_nc_rain <- ee_extract(terraclimate, nc, sf = FALSE) colnames(ee_nc_rain) <- sprintf("%02d", 1:12) ee_nc_rain$name <- nc$NAME +# Create a data frame in a tidy format and display rainfall values ee_nc_rain %>% pivot_longer(-name, names_to = "month", values_to = "pr") %>% ggplot(aes(x = month, y = pr, group = name, color = pr)) + @@ -166,12 +168,12 @@ ee_nc_rain %>% theme_minimal() ``` -![Average areal rainfall in counties of North Carolina for the year 2011 according to the TerraClimate dataset.](rgee_paper_01.png){ width=80% } +![Average areal rainfall in counties of North Carolina for the year 2001 according to the TerraClimate dataset.](rgee_paper_01.png){ width=80% } ## Asset Management Interface -rgee implements an interface to batch actions on assets extending capabilities of the existing EE data module (`ee.data.*`). The interface allows users to create and eliminate folders, move and copy assets, set and delete properties, handle access control lists, and manage or cancel tasks. For example, users can copy a Landsat 8 image to their personal EE assets as follows: +rgee implements an interface to batch actions on assets extending capabilities of the existing EE data module (`ee$data$*`). The interface allows users to create and eliminate folders, move and copy assets, set and delete properties, handle access control lists, and manage or cancel tasks. For example, users can copy a Landsat 8 image to their personal EE assets as follows: ```r library(rgee) @@ -187,7 +189,7 @@ ee_manage_copy( ``` ## Metadata display -The `ee_print` function can save and display all metadata related to EE spatial objects. With `ee_print`, users can retrieve information about the number of images or features, number of bands or geometries, number of pixels, geotransform, datatype, properties and approximate object size. `ee_print` can be used inside debugging pipelines (e.g. linking with `ee.Image.aside`). +The `ee_print` function can save and display all metadata related to EE spatial objects. With `ee_print`, users can retrieve information about the number of images or features, number of bands or geometries, number of pixels, geotransform, datatype, properties and approximate object size. `ee_print` can be used inside debugging pipelines (e.g. linking with `ee$Image$aside`). ```r library(rgee) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index ae25ecb7..082d7cea 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -80,6 +80,8 @@ reference: - +,mapview,EarthEngineMap-method - print,EarthEngineMap-method - show,EarthEngineMap-method + - mapview-class + - +,EarthEngineMap,mapview-method - title: "Image download" contents: diff --git a/rgee.Rproj b/rgee.Rproj index d5969924..0167accc 100755 --- a/rgee.Rproj +++ b/rgee.Rproj @@ -18,4 +18,4 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source -PackageCheckArgs: --as-cran +PackageCheckArgs: --as-cran --run-dontrun diff --git a/tests/testthat.R b/tests/testthat.R index 536390a2..7000b6c1 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,4 +1,51 @@ library(testthat) +library(mapview) +library(raster) +library(stars) library(rgee) +library(sf) + +# Pre-checking ------------------------------------------------------ +# Google credentials were loaded in the system? +skip_if_no_credentials <- function() { + ee_path <- ee_get_earthengine_path() + credentials <- list.files( + path = ee_path, + pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" + ) + if (length(credentials) != 3) { + skip("All google credentials were not found") + } +} + +# Necessary Python packages were loaded? +skip_if_no_pypkg <- function() { + have_ee <- reticulate::py_module_available("ee") + have_numpy <- reticulate::py_module_available("numpy") + if (isFALSE(have_ee)) { + skip("ee not available for testing") + } + if (isFALSE(have_numpy)) { + skip("numpy not available for testing") + } +} + +# Define your Drive folder to save intermediate files +# ALERT!!: After tests finished all the files inside the folder +# will be deleted. +drive_folder_f <- function(){ + "rgee_backup" +} + +# Define your own GCS bucket to save intermediate files. +# ALERT!!: After test finished all the files inside the bucket +# will be deleted. +gcs_bucket_f <- function(){ + "rgee_dev" +} + +# Initialize credentials +# If you do not count with GCS credentials the test will be skipped +ee_Initialize(drive = TRUE, gcs = TRUE) test_check("rgee") diff --git a/tests/testthat/test-Initialize.R b/tests/testthat/test-Initialize.R index 33710adc..ffae2213 100755 --- a/tests/testthat/test-Initialize.R +++ b/tests/testthat/test-Initialize.R @@ -1,51 +1,7 @@ context("rgee: ee_Initialize() test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - test_that("get_authorization_url is working well?",{ oauth_func_path <- system.file("python/ee_utils.py", package = "rgee") utils_py <- rgee:::ee_source_python(oauth_func_path) @@ -56,8 +12,7 @@ test_that("get_authorization_url is working well?",{ test_that("complex init",{ out <- tryCatch( - expr = ee_Initialize(email = 'data.colec.fbf@gmail.com', - drive = TRUE, + expr = ee_Initialize(drive = TRUE, gcs = TRUE), error = function(e) e) expect_equal(any("error" %in% class(out)), FALSE) diff --git a/tests/testthat/test-check.R b/tests/testthat/test-check.R index de52bd88..a8076a8c 100755 --- a/tests/testthat/test-check.R +++ b/tests/testthat/test-check.R @@ -1,50 +1,7 @@ context("rgee: ee_check test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() - # ------------------------------------------------------------------------- + test_that("simple ee_check ",{ expect_true(ee_check_python()) expect_true(ee_check_python_packages()) diff --git a/tests/testthat/test-clean.R b/tests/testthat/test-clean.R index 5d8ce797..4e16aad3 100644 --- a/tests/testthat/test-clean.R +++ b/tests/testthat/test-clean.R @@ -1,62 +1,12 @@ context("rgee: ee_clean_credentials test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() - # ------------------------------------------------------------------------- -test_that("ee_clean_credentials", { - result_True <- ee_clean_credentials('test') - expect_true(result_True) -}) - -test_that("ee_clean_credentials not-defined", { - result_True <- ee_clean_pyenv() - expect_true(result_True) -}) - -ee_Initialize( - email = user, - drive = TRUE, - gcs = TRUE -) +#test_that("ee_clean_credentials", { + #result_True <- ee_clean_credentials('test') + #expect_true(result_True) +#}) + +# test_that("ee_clean_credentials not-defined", { +# result_True <- ee_clean_pyenv() +# expect_true(result_True) +# }) diff --git a/tests/testthat/test-date.R b/tests/testthat/test-date.R index 8011d39e..11a106c8 100644 --- a/tests/testthat/test-date.R +++ b/tests/testthat/test-date.R @@ -1,51 +1,6 @@ context("rgee: ee_date test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - - test_that("rdate_to_eedate I", { eedate <- rdate_to_eedate('2000-01-01') expect_true('ee.ee_date.Date' %in% class(eedate)) diff --git a/tests/testthat/test-download.R b/tests/testthat/test-download.R index 2700a459..9194a070 100755 --- a/tests/testthat/test-download.R +++ b/tests/testthat/test-download.R @@ -1,58 +1,9 @@ context("rgee: ee_download test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() +drive_folder <- drive_folder_f() +gcs_bucket <- gcs_bucket_f() # ------------------------------------------------------------------------- - - -library(rgee) -library(sf) - -drive_folder <- 'rgee_backup' -gcs_bucket <- 'rgee_dev' - # Communal Reserve Amarakaeri - Peru xmin <- -71.13 xmax <- -70.95 @@ -67,8 +18,7 @@ ee_geom <- matrix(ROI, ncol = 2, byrow = TRUE) %>% sf::st_polygon() %>% sf::st_sfc() %>% sf::st_set_crs(4326) %>% - sf_as_ee(check_ring_dir = TRUE) %>% - ee$FeatureCollection$geometry() + sf_as_ee() # Elevation map ic_srtm <- ee$Image("CGIAR/SRTM90_V4") %>% @@ -93,8 +43,13 @@ imageExportFormatOptions_2 <- list( vectorExportFormatOptions_1 <- list(compressed = TRUE) vectorExportFormatOptions_2 <- list(compressed = FALSE) -ee_clean_container(name = drive_folder, type = 'drive') -ee_clean_container(name = gcs_bucket, type = 'gcs') +suppressWarnings( + try(ee_clean_container(name = drive_folder, type = 'drive'), silent = TRUE) +) + +suppressWarnings( + try(ee_clean_container(name = gcs_bucket, type = 'gcs'), silent = TRUE) +) # ### IMAGES # # 1. GEOTIFF - DRIVE @@ -158,6 +113,7 @@ test_that("GEOTIFF_DRIVE", { # # # 4. GEOTIFF - GCS test_that("GEOTIFF_GCS", { + skip_if_no_credentials() task_img <- ee_image_to_gcs( image = image_test, bucket = gcs_bucket, @@ -364,6 +320,7 @@ test_that("GEOJSON_VECTOR_DRIVE",{ # # 17. KMZ_VECTOR - GCS test_that("KMZ_VECTOR_GCS",{ + skip_if_no_credentials() task_vector <- ee_table_to_gcs( collection = fc_test, bucket = gcs_bucket, @@ -383,6 +340,7 @@ test_that("KMZ_VECTOR_GCS",{ # # 18. GEOJSON_VECTOR - GCS test_that("GEOJSON_VECTOR_GCS",{ + skip_if_no_credentials() task_vector <- ee_table_to_gcs( collection = fc_test, bucket = gcs_bucket, diff --git a/tests/testthat/test-ee_as_sf.R b/tests/testthat/test-ee_as_sf.R index 40c4329e..1036a924 100755 --- a/tests/testthat/test-ee_as_sf.R +++ b/tests/testthat/test-ee_as_sf.R @@ -1,51 +1,7 @@ context("rgee: ee_as_sf test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - # Load and filter watersheds from a data table. sheds <- ee$FeatureCollection('USGS/WBD/2017/HUC06')$ filterBounds(ee$Geometry$Rectangle(-127.18, 19.39, -62.75, 51.29))$ @@ -76,8 +32,9 @@ test_that("sf - drive",{ }) test_that("sf - gcs",{ + skip_if_no_credentials() mysheds <- ee_as_sf(ee$Feature(sheds$first()),via = "gcs", - container = "rgee_dev") + container = gcs_bucket_f()) expect_s3_class(mysheds,"sf") }) diff --git a/tests/testthat/test-ee_extract.R b/tests/testthat/test-ee_extract.R index ca63cf18..8fe5d3f8 100755 --- a/tests/testthat/test-ee_extract.R +++ b/tests/testthat/test-ee_extract.R @@ -1,51 +1,6 @@ context("rgee: ee_extract test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - - filename <- system.file("external/lux.shp", package="raster") # data -------------------------------------------------------------------- @@ -72,8 +27,6 @@ test_that("ee_extract ee$Image",{ expect_equal(mean(ee_nc_rain$X200012_pr), 50.47) }) - - test_that("ee_extract - error ",{ demo <- environment() demo$name <- function() 1 diff --git a/tests/testthat/test-ee_help.R b/tests/testthat/test-ee_help.R index e79298d2..93543714 100644 --- a/tests/testthat/test-ee_help.R +++ b/tests/testthat/test-ee_help.R @@ -1,51 +1,7 @@ context("rgee: ee_help test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - # clean TRUE test_that("simple ee_help - py class", { return_message <- ee$Image %>% ee_help() diff --git a/tests/testthat/test-ee_image.R b/tests/testthat/test-ee_image.R index ee0cd448..47fde788 100644 --- a/tests/testthat/test-ee_image.R +++ b/tests/testthat/test-ee_image.R @@ -1,55 +1,7 @@ context("rgee: sf_as_stars test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- -library(rgee) -library(raster) -library(stars) - -# Define data ------------------------------------------------------------- img <- ee$Image("LANDSAT/LC08/C01/T1_SR/LC08_038029_20180810")$ select(c("B4", "B3", "B2"))$ divide(10000) @@ -97,6 +49,7 @@ test_that("ee_as_proxystars ", { }) test_that("ee_as_stars - simple ", { + skip_if_no_credentials() #getinfo img_stars_01 <- ee_as_stars( image = img, @@ -183,6 +136,7 @@ test_that("ee_as_thumbnail world", { # clean containers --------------------------------------------------- test_that("ee_clean_container", { + skip_if_no_credentials() drive <- ee_clean_container() gcs <- ee_clean_container(name = 'rgee_dev',type = 'gcs') expect_true(gcs) diff --git a/tests/testthat/test-eeprint.R b/tests/testthat/test-eeprint.R index b39ff46a..38967f63 100644 --- a/tests/testthat/test-eeprint.R +++ b/tests/testthat/test-eeprint.R @@ -1,51 +1,7 @@ context("rgee: ee_print test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - # clean TRUE test_that("simple ee_print test - ImageCollection", { eeobject <- ee$ImageCollection("LANDSAT/LC08/C01/T1_TOA")$ @@ -68,12 +24,12 @@ test_that("simple ee_print test - 2 - ImageCollection", { ROI <- c(xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax, xmin, ymin) ROI_polygon <- matrix(ROI, ncol = 2, byrow = TRUE) %>% list() %>% - st_polygon() %>% - st_sfc() %>% - st_set_crs(4326) + sf::st_polygon() %>% + sf::st_sfc() %>% + sf::st_set_crs(4326) ee_geom <- sf_as_ee(ROI_polygon) eeobject <- ee$ImageCollection("LANDSAT/LT05/C01/T1_SR")$ - filterBounds(ee_geom)$ + filterBounds(ee$FeatureCollection(ee_geom))$ filterDate("2011-01-01", "2011-12-31") ee_print_obj <- ee_print( eeobject = eeobject, @@ -95,8 +51,8 @@ test_that("simple ee_print test - Image", { }) test_that("simple ee_print test - FeatureCollection", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% - st_transform(4326) %>% + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% "["(1:10, ) ee_nc <- sf_as_ee(nc) ee_print_obj <- ee_print( @@ -107,10 +63,9 @@ test_that("simple ee_print test - FeatureCollection", { expect_equal(ee_print_obj$name, "FeatureCollection") }) - test_that("simple ee_print test - Feature", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% - st_transform(4326) %>% + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% "["(1, ) ee_nc <- ee$Feature(sf_as_ee(nc)$first()) ee_print_obj <- ee_print( @@ -123,23 +78,23 @@ test_that("simple ee_print test - Feature", { test_that("simple ee_print test - Geometry", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% - st_transform(4326) %>% + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% "["(1, ) %>% - st_geometry() + sf::st_geometry() ee_nc <- sf_as_ee(nc) ee_print_obj <- ee_print( - eeobject = ee_nc$geometry(), + eeobject = ee_nc, clean = TRUE ) - ee_print_obj <- ee_print(eeobject = ee_nc$geometry()) + ee_print_obj <- ee_print(eeobject = ee_nc) expect_equal(ee_print_obj$name, "Geometry") }) # Testing create table test_that("simple ee_print test - FeatureCollection", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% - st_transform(4326) %>% + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% "["(1:10, ) ee_nc <- sf_as_ee(nc) ee_print_obj <- ee_print( @@ -152,16 +107,16 @@ test_that("simple ee_print test - FeatureCollection", { # Testing print test_that("different typ ee_print test - json", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% - st_transform(4326) %>% + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% "["(1, ) %>% - st_geometry() + sf::st_geometry() ee_nc <- sf_as_ee(nc) expect_equal(print(ee_nc, type = "json"), NULL) expect_equal(print(ee_nc, type = "simply"), NULL) ee_print_obj <- print(ee_nc, type = "ee_print") - expect_equal(ee_print_obj$name, "FeatureCollection") + expect_equal(ee_print_obj$name, "Geometry") }) test_that("simple ee_print test - ImageCollection not EPSG", { diff --git a/tests/testthat/test-imagecollection.R b/tests/testthat/test-imagecollection.R index 9c909dc8..51cae801 100644 --- a/tests/testthat/test-imagecollection.R +++ b/tests/testthat/test-imagecollection.R @@ -1,55 +1,7 @@ -## Not run: -library(rgee) -library(raster) - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) +context("rgee: ee_imagecollection_to_local test") skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - -# Dataset -# --------------------------- loc <- ee$Geometry$Point(-99.2222, 46.7816) collection <- ee$ImageCollection('USDA/NAIP/DOQQ')$ filterBounds(loc)$ diff --git a/tests/testthat/test-install.R b/tests/testthat/test-install.R index 5c4544f8..df614f29 100755 --- a/tests/testthat/test-install.R +++ b/tests/testthat/test-install.R @@ -1,51 +1,7 @@ context("rgee: ee_install test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - ### Instalation module # test_that("ee_install_create_pyenv ",{ # result <- ee_install_create_pyenv('earthengine_test') diff --git a/tests/testthat/test-manage.R b/tests/testthat/test-manage.R index dd80c0f3..0d3cace3 100755 --- a/tests/testthat/test-manage.R +++ b/tests/testthat/test-manage.R @@ -1,132 +1,86 @@ context("rgee: ee_manage test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - -try(ee_manage_delete(path_asset = 'users/datacolecfbf/rgee/')) +try(ee_manage_delete(path_asset = sprintf("%s/rgee/",ee_get_assethome()))) test_that("ee_manage_create", { ee_manage_create( - path_asset = 'users/datacolecfbf/rgee/rgee_folder', + path_asset = sprintf("%s/rgee/rgee_folder",ee_get_assethome()), asset_type = 'Folder' ) ee_manage_copy( - path_asset = 'users/datacolecfbf/rgee/rgee_folder', - final_path = 'users/datacolecfbf/rgee/rgee_folder1' + path_asset = sprintf("%s/rgee/rgee_folder",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_folder1",ee_get_assethome()) ) msg <- ee_manage_create( - path_asset = 'users/datacolecfbf/rgee/rgee_ic', + path_asset = sprintf("%s/rgee/rgee_ic",ee_get_assethome()), asset_type = 'ImageCollection' ) expect_error(ee_manage_create(path_asset = 'users/pinkipie/rgee/rgee_ic')) expect_true(msg) }) - test_that("ee_manage_assetlist", { - data_01 <- ee_manage_assetlist(path_asset = 'users/datacolecfbf/rgee') + data_01 <- ee_manage_assetlist(path_asset = sprintf("%s/rgee",ee_get_assethome())) expect_s3_class(data_01,'data.frame') }) test_that("ee_manage_move", { ee_manage_move( - path_asset = 'users/datacolecfbf/rgee/rgee_ic', - final_path = 'users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved' + path_asset = sprintf("%s/rgee/rgee_ic",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_folder/rgee_ic_moved",ee_get_assethome()) ) - ee_manage_create(path_asset = 'users/datacolecfbf/rgee/rgee_ic', + ee_manage_create(path_asset = sprintf("%s/rgee/rgee_ic",ee_get_assethome()), asset_type = 'ImageCollection') - ret <- ee_manage_delete('users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved') + ret <- ee_manage_delete(sprintf("%s/rgee/rgee_folder/rgee_ic_moved",ee_get_assethome())) expect_true(ret) }) test_that("ee_manage_copy", { - ee_manage_create(path_asset = 'users/datacolecfbf/rgee/rgee_ic2', + ee_manage_create(path_asset = sprintf("%s/rgee/rgee_ic2",ee_get_assethome()), asset_type = 'ImageCollection') - ee_manage_copy(path_asset = 'users/datacolecfbf/rgee_test', - final_path = 'users/datacolecfbf/rgee/rgee_ic2/rgee_test') - ret <- ee_manage_delete('users/datacolecfbf/rgee/rgee_ic2') + ee_manage_copy(path_asset = sprintf("%s/rgee_test",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_ic2/rgee_test",ee_get_assethome())) + ret <- ee_manage_delete(sprintf("%s/rgee/rgee_ic2",ee_get_assethome())) expect_true(ret) }) test_that("ee_manage_move - II", { - ee_manage_create(path_asset = 'users/datacolecfbf/rgee/rgee_ic2', + ee_manage_create(path_asset = sprintf("%s/rgee/rgee_ic2",ee_get_assethome()), asset_type = 'ImageCollection') ee_manage_copy( - path_asset = 'users/datacolecfbf/rgee_test', - final_path = 'users/datacolecfbf/rgee/rgee_ic2/rgee_test' + path_asset = sprintf("%s/rgee_test",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_ic2/rgee_test",ee_get_assethome()) ) ee_manage_move( - path_asset = 'users/datacolecfbf/rgee/rgee_ic2', - final_path = 'users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved' + path_asset = sprintf("%s/rgee/rgee_ic2",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_folder/rgee_ic_moved",ee_get_assethome()) ) - ret <- ee_manage_delete('users/datacolecfbf/rgee/rgee_folder/rgee_ic_moved') + ret <- ee_manage_delete(sprintf("%s/rgee/rgee_folder/rgee_ic_moved",ee_get_assethome())) expect_true(ret) }) test_that("ee_manage_set_properties", { - ee_manage_copy(path_asset = 'users/datacolecfbf/rgee_test', - final_path = 'users/datacolecfbf/rgee/rgee_test') - ee_manage_set_properties(path_asset = 'users/datacolecfbf/rgee/rgee_test', + ee_manage_copy(path_asset = sprintf("%s/rgee_test",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_test",ee_get_assethome())) + ee_manage_set_properties(path_asset = sprintf("%s/rgee/rgee_test",ee_get_assethome()), add_properties = list(message = 'hello-world', language = 'R')) - ret <- ee_manage_delete(path_asset = 'users/datacolecfbf/rgee/rgee_test') + ret <- ee_manage_delete(path_asset = sprintf("%s/rgee/rgee_test",ee_get_assethome())) expect_true(ret) }) - test_that("ee_manage_delete_properties", { - ee_manage_copy(path_asset = 'users/datacolecfbf/rgee_test', - final_path = 'users/datacolecfbf/rgee/rgee_test') - ee_manage_set_properties(path_asset = 'users/datacolecfbf/rgee/rgee_test', + ee_manage_copy(path_asset = sprintf("%s/rgee_test",ee_get_assethome()), + final_path = sprintf("%s/rgee/rgee_test",ee_get_assethome())) + ee_manage_set_properties(path_asset = sprintf("%s/rgee/rgee_test",ee_get_assethome()), add_properties = list(message = 'hello-world', language = 'R')) #ee$data$getAsset('users/datacolecfbf/rgee/L7')$properties - ee_manage_delete_properties('users/datacolecfbf/rgee/rgee_test') - prop <- ee$data$getAsset('users/datacolecfbf/rgee/rgee_test')$properties - ee_manage_delete('users/datacolecfbf/rgee/rgee_test') + ee_manage_delete_properties(sprintf("%s/rgee/rgee_test",ee_get_assethome())) + prop <- ee$data$getAsset(sprintf("%s/rgee/rgee_test",ee_get_assethome()))$properties + ee_manage_delete(sprintf("%s/rgee/rgee_test",ee_get_assethome())) expect_null(prop) }) @@ -139,7 +93,7 @@ test_that("ee_manage_task", { test_that("ee_verify_filename - error message",{ expect_error( - ee_verify_filename(path_asset = "user/datacolecfbf/xxx", + ee_verify_filename(path_asset = sprintf("%s/xxx",ee_get_assethome()), strict = TRUE) ) } @@ -148,12 +102,12 @@ test_that("ee_verify_filename - error message",{ test_that("ee_manage_create - error message", { expect_error(object = ee_manage_create( - path_asset = 'users/datacolecfbf/to_c', + path_asset = sprintf("%s/to_c",ee_get_assethome()), asset_type = 'nekko') ) expect_error( object = ee_manage_create( - path_asset = 'users/datacolecfbf/ee_manage', + path_asset = sprintf("%s/ee_manage",ee_get_assethome()), asset_type = 'nekko'), expected = TRUE) } @@ -165,15 +119,9 @@ test_that("ee_manage_quota", { ) test_that("ee_manage_asset_access", { - ee_Initialize( - email = "data.colec.fbf@gmail.com", - drive = TRUE, - gcs = TRUE - ) - ee_manage_asset_access('users/datacolecfbf/rgee', - editor = 'data.colec.fbf@gmail.com') - message <- ee_manage_asset_access('users/datacolecfbf/rgee', - editor = 'data.colec.fbf@gmail.com', + ee_manage_asset_access(path_asset = sprintf("%s/rgee",ee_get_assethome())) + message <- ee_manage_asset_access(sprintf("%s/rgee",ee_get_assethome()), + editor = 'csaybar@gmail.com', all_users_can_read = TRUE) expect_true(message) } @@ -193,9 +141,8 @@ expect_type( ) }) - test_that("ee_manage_create - extra", { - ic_name <- 'users/datacolecfbf/rgee/cs/cs/rgee_folder' + ic_name <- sprintf("%s/rgee/cs/cs/rgee_folder",ee_get_assethome()) ic <- ee_manage_create( path_asset = ic_name, asset_type = 'ImageCollection' @@ -210,8 +157,8 @@ test_that("ee_manage_create - extra", { test_that("ee_manage_copy - ImageCollection", { - lesly_ic_in <- "users/datacolecfbf/rgee/lesly_ic" - lesly_ic_out <- "users/datacolecfbf/rgee/lesly/lesly_ic" + lesly_ic_in <- sprintf("%s/rgee/lesly_ic",ee_get_assethome()) + lesly_ic_out <- sprintf("%s/rgee/lesly/lesly_ic",ee_get_assethome()) ee_manage_create(lesly_ic_in, "ImageCollection") exp_01 <- ee_manage_copy( path_asset = lesly_ic_in, @@ -221,8 +168,8 @@ test_that("ee_manage_copy - ImageCollection", { }) test_that("ee_manage_move - Folder", { - lesly_f_in <- "users/datacolecfbf/rgee/lesly_folder/" - lesly_f_out <- "users/datacolecfbf/rgee/lesly/lesly_folder/" + lesly_f_in <- sprintf("%s/rgee/lesly_folder/",ee_get_assethome()) + lesly_f_out <- sprintf("%s/rgee/lesly/lesly_folder/",ee_get_assethome()) ee_manage_create(lesly_f_in) exp_02 <- ee_manage_move( path_asset = lesly_f_in, diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index e579309a..9a205f9c 100755 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -1,54 +1,7 @@ context("rgee: sf_as_ee test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -library(mapview) - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - -# data -------------------------------------------------------------------- geom <- ee$Geometry$Point(list(-73.53522, -15.75453)) eeobject_fc <- ee$FeatureCollection(geom) image <- ee$Image("LANDSAT/LC08/C01/T1/LC08_044034_20140318") @@ -196,6 +149,3 @@ test_that("messages 01", { eeObject = eeobject_fc$first()$geometry()) expect_type(message,"environment") }) - - - diff --git a/tests/testthat/test-search.R b/tests/testthat/test-search.R index 53ed7045..823b4a86 100644 --- a/tests/testthat/test-search.R +++ b/tests/testthat/test-search.R @@ -1,51 +1,7 @@ context("rgee: test-search test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - db <- paste0( "https://raw.githubusercontent.com/csaybar/Earth-Engine-Datasets-List/", "10c09b65e93d156c297628f035bf372b101867d3/eed-2020-03-30.csv", diff --git a/tests/testthat/test-sf_as_ee.R b/tests/testthat/test-sf_as_ee.R index ad438140..a8ec4557 100755 --- a/tests/testthat/test-sf_as_ee.R +++ b/tests/testthat/test-sf_as_ee.R @@ -1,153 +1,101 @@ context("rgee: sf_as_ee test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - filename <- system.file("external/lux.shp", package = "raster") nc <- system.file("shape/nc.shp", package = "sf") -test_that("sf_as_ee.character", { - p <- sf_as_ee(filename, check_ring_dir = TRUE, geodesic = TRUE) - centroid <- p$ - geometry()$ - centroid()$ - getInfo() %>% - "[["("coordinates") %>% - ee_utils_py_to_r() %>% - mean() - expect_equal(centroid, 27.93429, tolerance = 0.1) -}) - test_that("sf_as_ee.sf", { - p <- raster::shapefile(filename) %>% + p <- sf::read_sf(filename) %>% sf::st_as_sf() - suppressWarnings(st_crs(p) <- 4326) - p <- sf_as_ee(p, check_ring_dir = TRUE) - expect_is(p, "ee.featurecollection.FeatureCollection") + ee_p <- sf_as_ee(p) + expect_is(ee_p, "ee.featurecollection.FeatureCollection") }) test_that("sf_as_ee.sfc", { - p <- raster::shapefile(filename) %>% + p <- sf::read_sf(filename) %>% sf::st_as_sf() %>% sf::st_geometry() - suppressWarnings(st_crs(p) <- 4326) - p <- sf_as_ee(p, check_ring_dir = TRUE) - expect_is(p$geometry(), "ee.geometry.Geometry") + ee_p <- sf_as_ee(p) + expect_is(ee_p$geometry(), "ee.geometry.Geometry") }) test_that("sf_as_ee.sfg", { - p <- raster::shapefile(filename) %>% - sf::st_as_sf() %>% - st_geometry() %>% + p <- sf::read_sf(filename) %>% + sf::st_geometry() %>% "[["(1) %>% - sf_as_ee(check_ring_dir = TRUE) - expect_is(p$geometry(), "ee.geometry.Geometry") + sf_as_ee() + expect_is(p, "ee.geometry.Geometry") }) test_that("sf_as_ee - getInfo_to_asset", { remove_id <- "/users/datacolecfbf/sf_as_ee_test" - try(ee_manage_delete(remove_id), silent = TRUE) - p <- raster::shapefile(filename) %>% + p <- sf::read_sf(filename) %>% sf::st_as_sf() %>% - st_geometry() %>% + sf::st_geometry() %>% "[["(1) %>% sf_as_ee( - check_ring_dir = TRUE, + overwrite = TRUE, assetId = remove_id, - via = "getInfo_to_asset") + via = "getInfo_to_asset" + ) expect_is(p$geometry(), "ee.geometry.Geometry") }) -test_that("sf_as_ee - getInfo_to_asset", { +test_that("sf_as_ee - gcs_to_asset", { + skip_if_no_credentials() remove_id <- "/users/datacolecfbf/sf_as_ee_test" - try(ee_manage_delete(remove_id), silent = TRUE) - p <- raster::shapefile(filename) %>% + p <- sf::read_sf(filename) %>% sf::st_as_sf() %>% - st_geometry() %>% + sf::st_geometry() %>% "[["(1) %>% sf_as_ee( - check_ring_dir = TRUE, assetId = remove_id, - bucket = "rgee_dev", + overwrite = TRUE, + bucket = gcs_bucket_f(), via = "gcs_to_asset") expect_is(p$geometry(), "ee.geometry.Geometry") }) -test_that("sf_as_ee - getInfo_to_asset II", { +test_that("sf_as_ee - single", { remove_id <- "/users/datacolecfbf/sf_as_ee_test" - try(ee_manage_delete(remove_id), silent = TRUE) - p <- raster::shapefile(filename) %>% - sf::st_as_sf() %>% - st_geometry() %>% - "[["(1) %>% - sf_as_ee( - check_ring_dir = TRUE, - assetId = remove_id, - bucket = "rgee_dev", - monitoring = TRUE, - via = "gcs_to_asset") - expect_is(p$geometry(), "ee.geometry.Geometry") + p_sf <- sf::read_sf(filename)[1,] + p_sfc <- sf::read_sf(filename)[1,]$geometry + p_sfg <- sf::read_sf(filename)[1,]$geometry[[1]] + expect_is(sf_as_ee(p_sf)$geometry(), "ee.geometry.Geometry") + expect_is(sf_as_ee(p_sfc), "ee.geometry.Geometry") + expect_is(sf_as_ee(p_sfg), "ee.geometry.Geometry") }) test_that("ERROR 01", { expect_error( - raster::shapefile(filename) %>% + sf::read_sf(filename) %>% sf::st_as_sf() %>% sf::st_geometry() %>% - sf_as_ee(check_ring_dir = TRUE,via = "cesar") + sf_as_ee(via = "cesar") ) }) test_that("ERROR 02", { - ss_ff <- st_read(filename) - st_crs(ss_ff) <- NA + ss_ff <- sf::st_read(filename) + sf::st_crs(ss_ff) <- NA expect_error( - sf_as_ee(ss_ff, check_ring_dir = TRUE, geodesic = TRUE) + sf_as_ee(ss_ff, geodesic = TRUE) ) }) +test_that("ERROR 03", { + ss_ff <- "other_class" + expect_error( + sf_as_ee(ss_ff) + ) +}) + +test_that("ERROR 04", { + ss_ff <- "other_class" + expect_error( + sf_as_ee(ss_ff) + ) +}) diff --git a/tests/testthat/test-thumbnail.R b/tests/testthat/test-thumbnail.R index 23cf7a63..c5d18bbd 100644 --- a/tests/testthat/test-thumbnail.R +++ b/tests/testthat/test-thumbnail.R @@ -1,55 +1,7 @@ context("rgee: ee_as_thumbnail test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- - -library(raster) -library(rgee) -library(sf) - ### 1. Data dem_palette <- c( "#008435", "#1CAC17", "#48D00C", "#B3E34B", "#F4E467", @@ -63,6 +15,7 @@ sheds <- ee$FeatureCollection("USGS/WBD/2017/HUC06")$ num <- ee$Number$parse(feature$get("areasqkm")) return(feature$set("areasqkm", num)) }) + image <- ee$Image("CGIAR/SRTM90_V4") region <- nc$geometry[[1]] %>% st_bbox() %>% @@ -107,7 +60,6 @@ test_that("ee_as_thumbnail palette, min-max", { expect_equal(max(arequipa_dem[[1]]), 5000, tolerance = 1) }) - # RGB band ----------------------------------------------------------- test_that("ee_as_thumbnail region", { # PNG images @@ -137,13 +89,11 @@ test_that("ee_as_thumbnail error 02", { expect_error(ee_as_thumbnail(image, "ee$Geometry")) }) - test_that("ee_as_thumbnail error 03", { # PNG images expect_error(ee_as_thumbnail(image, region$centroid()$buffer(100))) }) - # large image ----------------------------------------------------------- test_that("ee_as_thumbnail large image", { # PNG images @@ -155,5 +105,3 @@ test_that("ee_as_thumbnail large image", { ) expect_s3_class(arequipa_dem, "stars") }) - - diff --git a/tests/testthat/test-upload.R b/tests/testthat/test-upload.R index adf1806a..b1905c68 100644 --- a/tests/testthat/test-upload.R +++ b/tests/testthat/test-upload.R @@ -1,70 +1,28 @@ context("rgee: ee_upload test") - -# Pre-checking ------------------------------------------------------ -# Google credentials were loaded in the system? -skip_if_no_credentials <- function(user) { - ee_path <- path.expand(sprintf("~/.config/earthengine/%s", user)) - credentials <- list.files( - path = ee_path, - pattern = "@gmail.com|credentials|GCS_AUTH_FILE.json" - ) - if (length(credentials) != 3) { - skip("All google credentials were not found") - } -} - -# Neccesary Python packages were loaded? -skip_if_no_pypkg <- function() { - have_ee <- reticulate::py_module_available("ee") - have_numpy <- reticulate::py_module_available("numpy") - if (isFALSE(have_ee)) { - skip("ee not available for testing") - } - if (isFALSE(have_numpy)) { - skip("numpy not available for testing") - } -} - -# Init Earth Engine just if it is necessary -init_rgee <- function() { - tryCatch( - expr = ee$Image()$getInfo(), - error = function(e) { - ee_Initialize( - email = 'data.colec.fbf@gmail.com', - drive = TRUE, - gcs = TRUE - ) - } - ) -} - -user <- "data.colec.fbf" -skip_if_no_credentials(user) skip_if_no_pypkg() -init_rgee() # ------------------------------------------------------------------------- test_that("local_to_gcs - character",{ + skip_if_no_credentials() # Define an image. tif <- system.file("tif/L7_ETMs.tif", package = "stars") - gcsuri <- local_to_gcs(x = tif, bucket = 'rgee_dev') - gcsuri <- local_to_gcs(x = tif, bucket = 'rgee_dev',quiet = TRUE) + gcsuri <- local_to_gcs(x = tif, bucket = gcs_bucket_f()) + gcsuri <- local_to_gcs(x = tif, bucket = gcs_bucket_f(), quiet = TRUE) expect_type(gcsuri,'character') }) # ee_upload with bucket ----------------------------------------------------- test_that("gcs_to_ee_table ", { - nc <- st_read(system.file("shape/nc.shp", package = "sf")) + skip_if_no_credentials() + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) assetId <- sprintf("%s/%s",ee_get_assethome(),'sf_nc') zipfile <- ee_utils_shp_to_zip(nc) gs_uri <- local_to_gcs(x = zipfile, - bucket = 'rgee_dev') + bucket = gcs_bucket_f()) gcs_to_ee_table( gs_uri = gs_uri, assetId = assetId, - overwrite = TRUE, - command_line_tool_path = dirname(Sys.getenv("EARTHENGINE_PYTHON")) + overwrite = TRUE ) ee_monitoring() @@ -75,15 +33,16 @@ test_that("gcs_to_ee_table ", { test_that("gcs_to_ee_image ", { + skip_if_no_credentials() # Get the filename of a image tif <- system.file("tif/L7_ETMs.tif", package = "stars") - x <- read_stars(tif) + x <- stars::read_stars(tif) st_crs(x) <- 4326 assetId <- sprintf("%s/%s",ee_get_assethome(),'stars_l7') # Method 1 # 1. Move from local to gcs - gs_uri <- local_to_gcs(x = tif, bucket = 'rgee_dev') + gs_uri <- local_to_gcs(x = tif, bucket = gcs_bucket_f()) # 2. Pass from gcs to asset result <- gcs_to_ee_image( From 7f2976478d5393f0c1a5b32fd841ae504a62620a Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sun, 5 Jul 2020 06:40:58 -0500 Subject: [PATCH 05/15] Possible bug: CLT is not in the same path in Linux and windows --- R/raster_as_ee.R | 10 +++++----- R/sf_as_ee.R | 6 +++--- R/utils-upload.R | 12 ++++++------ man/gcs_to_ee_image.Rd | 4 ++-- man/gcs_to_ee_table.Rd | 4 ++-- man/raster_as_ee.Rd | 4 ++-- man/sf_as_ee.Rd | 4 ++-- man/stars_as_ee.Rd | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/R/raster_as_ee.R b/R/raster_as_ee.R index fd87f93e..525c3763 100644 --- a/R/raster_as_ee.R +++ b/R/raster_as_ee.R @@ -3,8 +3,8 @@ #' @param x stars or stars-proxy object to be converted into an ee$Image. #' @param assetId Character. Destination asset ID for the uploaded file. #' @param command_line_tool_path Character. Path to the Earth Engine command line -#' tool. If NULL, rgee assumes is saved in the same path that your Python -#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). +#' tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +#' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite Logical. If TRUE, the assetId will be overwritten. #' @param bucket Character. Name of the GCS bucket. #' @param monitoring Logical. If TRUE the exportation task will be monitored. @@ -65,7 +65,7 @@ stars_as_ee <- function(x, ee_temp <- tempdir() if (is.null(command_line_tool_path)) { - command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + command_line_tool_path <- "" } message("1. Converting stars (raster) object to GeoTIFF ... saving in /tmp") @@ -108,8 +108,8 @@ stars_as_ee <- function(x, #' an ee$Image. #' @param assetId Character. Destination asset ID for the uploaded file. #' @param command_line_tool_path Character. Path to the Earth Engine command line -#' tool. If NULL, rgee assumes is saved in the same path that your Python -#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). +#' tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +#' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite Logical. If TRUE, the assetId will be overwritten. #' @param bucket Character. Name of the GCS bucket. #' @param monitoring Logical. If TRUE the exportation task will be monitored. diff --git a/R/sf_as_ee.R b/R/sf_as_ee.R index 28d5c9c9..b56f2fb9 100755 --- a/R/sf_as_ee.R +++ b/R/sf_as_ee.R @@ -26,8 +26,8 @@ #' @param bucket Character. Name of the bucket (GCS) to save intermediate files #' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param command_line_tool_path Character. Path to the Earth Engine command line -#' tool. If NULL, rgee assumes is saved in the same path that your Python -#' environment (ignore if \code{via} is not defined as "gcs_to_asset"). +#' tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +#' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite A boolean argument which indicates indicating #' whether "filename" should be overwritten. Ignore if \code{via} argument #' is "getInfo". By default TRUE. @@ -227,7 +227,7 @@ sf_as_ee <- function(x, } if (is.null(command_line_tool_path)) { - command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + command_line_tool_path <- "" } shp_dir <- sprintf("%s.shp", tempfile()) diff --git a/R/utils-upload.R b/R/utils-upload.R index 998da7b7..432231b5 100755 --- a/R/utils-upload.R +++ b/R/utils-upload.R @@ -88,8 +88,8 @@ local_to_gcs <- function(x, #' @param assetId Character. What to call the file once uploaded #' to their Earth Engine Assets #' @param command_line_tool_path Character. Path to the Earth Engine command line -#' tool. If NULL, rgee assumes is saved in the same path that your Python -#' environment. +#' tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +#' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param overwrite Logical. If TRUE, the assetId will be overwritten if #' it exists. #' @param quiet Logical. Suppress info message. @@ -144,7 +144,7 @@ gcs_to_ee_table <- function(gs_uri, overwrite = FALSE, quiet = FALSE) { if (is.null(command_line_tool_path)) { - command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + command_line_tool_path <- "" } if (command_line_tool_path == "") { @@ -196,8 +196,8 @@ gcs_to_ee_table <- function(gs_uri, #' @param gs_uri Character. It represents the full name of the #' GeoTIFF file of \code{x} in a GCS bucket. #' @param command_line_tool_path Character. Path to the Earth Engine command line -#' tool. If NULL, rgee assumes is saved in the same path that your Python -#' environment. +#' tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +#' (ignore if \code{via} is not defined as "gcs_to_asset"). #' @param assetId Character. How to call the file once uploaded #' to the Earth Engine Asset. e.g. users/datacolecfbf/mydatacollection. #' @param overwrite Logical. If TRUE, the assetId will be overwritten if @@ -270,7 +270,7 @@ gcs_to_ee_image <- function(x, # If the command_line_tool_path does not exist if (is.null(command_line_tool_path)) { - command_line_tool_path <- dirname(Sys.getenv("EARTHENGINE_PYTHON")) + command_line_tool_path <- "" } # Verify is the EE assets path is valid, if not try to fix diff --git a/man/gcs_to_ee_image.Rd b/man/gcs_to_ee_image.Rd index 6646d39b..2b5a9e36 100644 --- a/man/gcs_to_ee_image.Rd +++ b/man/gcs_to_ee_image.Rd @@ -33,8 +33,8 @@ it exists.} of the EE object.} \item{command_line_tool_path}{Character. Path to the Earth Engine command line -tool. If NULL, rgee assumes is saved in the same path that your Python -environment.} +tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +(ignore if \code{via} is not defined as "gcs_to_asset").} \item{start_time}{Character. Sets the start time property to a number or date.} diff --git a/man/gcs_to_ee_table.Rd b/man/gcs_to_ee_table.Rd index 81903b0f..0b6fd421 100644 --- a/man/gcs_to_ee_table.Rd +++ b/man/gcs_to_ee_table.Rd @@ -20,8 +20,8 @@ zipped shapefile in a GCS bucket.} to their Earth Engine Assets} \item{command_line_tool_path}{Character. Path to the Earth Engine command line -tool. If NULL, rgee assumes is saved in the same path that your Python -environment.} +tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +(ignore if \code{via} is not defined as "gcs_to_asset").} \item{overwrite}{Logical. If TRUE, the assetId will be overwritten if it exists.} diff --git a/man/raster_as_ee.Rd b/man/raster_as_ee.Rd index 30e37908..93ea26fd 100644 --- a/man/raster_as_ee.Rd +++ b/man/raster_as_ee.Rd @@ -21,8 +21,8 @@ an ee$Image.} \item{assetId}{Character. Destination asset ID for the uploaded file.} \item{command_line_tool_path}{Character. Path to the Earth Engine command line -tool. If NULL, rgee assumes is saved in the same path that your Python -environment (ignore if \code{via} is not defined as "gcs_to_asset").} +tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +(ignore if \code{via} is not defined as "gcs_to_asset").} \item{overwrite}{Logical. If TRUE, the assetId will be overwritten.} diff --git a/man/sf_as_ee.Rd b/man/sf_as_ee.Rd index ac6f4ec3..9528fe71 100644 --- a/man/sf_as_ee.Rd +++ b/man/sf_as_ee.Rd @@ -32,8 +32,8 @@ if \code{via} argument is "getInfo".} (ignore if \code{via} is not defined as "gcs_to_asset").} \item{command_line_tool_path}{Character. Path to the Earth Engine command line -tool. If NULL, rgee assumes is saved in the same path that your Python -environment (ignore if \code{via} is not defined as "gcs_to_asset").} +tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +(ignore if \code{via} is not defined as "gcs_to_asset").} \item{overwrite}{A boolean argument which indicates indicating whether "filename" should be overwritten. Ignore if \code{via} argument diff --git a/man/stars_as_ee.Rd b/man/stars_as_ee.Rd index c97dbd46..15413a35 100644 --- a/man/stars_as_ee.Rd +++ b/man/stars_as_ee.Rd @@ -20,8 +20,8 @@ stars_as_ee( \item{assetId}{Character. Destination asset ID for the uploaded file.} \item{command_line_tool_path}{Character. Path to the Earth Engine command line -tool. If NULL, rgee assumes is saved in the same path that your Python -environment (ignore if \code{via} is not defined as "gcs_to_asset").} +tool (CLT). If NULL, rgee assumes that CLT is set in the system PATH. +(ignore if \code{via} is not defined as "gcs_to_asset").} \item{overwrite}{Logical. If TRUE, the assetId will be overwritten.} From 15847d2bfd4d5b6c2a39c97c2b137a048d6b1b18 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sun, 5 Jul 2020 07:43:50 -0500 Subject: [PATCH 06/15] @gena comments - Fix a bug in GH actions --- .github/workflows/test-coverage.yaml | 2 +- README.md | 9 ++++----- inst/WORDLIST | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 53f01917..3a8c00f4 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -91,7 +91,7 @@ jobs: run: | earthengine -h ls $HOME/.config/earthengine/ - ls $HOME/.config/earthengine/data.colec.fbf + ls $HOME/.config/earthengine/ndef - name: INSTALL rgdal WITH CONFIGURE diff --git a/README.md b/README.md index 2aebac73..27b58e91 100755 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ Map$addLayer( ### 2. Extract precipitation values -Load `tidyverse` and `sf` and initialize the Earth Engine R API. +Install and load `tidyverse` and `sf` R package, after that, initialize the Earth Engine R API. ``` r library(tidyverse) @@ -234,7 +234,7 @@ dataset](https://developers.google.com/earth-engine/datasets/catalog/IDAHO_EPSCO ``` r terraclimate <- ee$ImageCollection("IDAHO_EPSCOR/TERRACLIMATE")$ - filterDate("2000-01-01", "2001-01-01")$ + filterDate("2001-01-01", "2002-01-01")$ map(function(x) x$reproject("EPSG:4326")$select("pr")) ``` @@ -243,7 +243,7 @@ similar to `raster::extract`, you just need to define: the ImageCollection objec summarize the values (fun). ``` r -ee_nc_rain <- ee_extract(x = terraclimate, y = nc, fun = ee$Reducer$max(), sf = FALSE) +ee_nc_rain <- ee_extract(x = terraclimate, y = nc, sf = FALSE) colnames(ee_nc_rain) <- sprintf("%02d", 1:12) ee_nc_rain$name <- nc$NAME ``` @@ -267,8 +267,7 @@ ee_nc_rain %>% ### 3. Create an NDVI-animation ([JS version](https://developers.google.com/earth-engine/tutorials/community/modis-ndvi-time-series-animation)) - -Load `sf` and initialize the Earth Engine R API. +Install and load `sf`, after that, initialize the Earth Engine R API. ``` r library(rgee) diff --git a/inst/WORDLIST b/inst/WORDLIST index 3fb6e819..c2a58ba2 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -253,3 +253,5 @@ bucles JOSS EarthEnginemap linetool +CLT +CMD From fa57b70e58f96ffc6ea2528b6b275b4ed2da904d Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sun, 5 Jul 2020 08:26:52 -0500 Subject: [PATCH 07/15] mapview problem in MacOS GH actions 2020-07-05T13:02:07.9910820Z dlopen(/Users/runner/work/_temp/Library/systemfonts/libs/systemfonts.so, 6): Library not loaded: /opt/X11/lib/libfreetype.6.dylib 2020-07-05T13:02:07.9911150Z Referenced from: /Users/runner/work/_temp/Library/systemfonts/libs/systemfonts.so 2020-07-05T13:02:07.9911350Z Reason: image not found 2020-07-05T13:02:07.9911510Z Backtrace: 2020-07-05T13:02:07.9911720Z 1. mapview::mapview(nc) 2020-07-05T13:02:07.9911910Z 5. mapview::mapview(...) 2020-07-05T13:02:07.9912110Z 7. mapview::mapView(...) 2020-07-05T13:02:07.9912310Z 8. mapview:::.local(x, ...) 2020-07-05T13:02:07.9912500Z 13. leafpop::popupTable 2020-07-05T13:02:07.9912710Z 14. base::getExportedValue(pkg, name) 2020-07-05T13:02:07.9912900Z 15. base::asNamespace(ns) 2020-07-05T13:02:07.9913100Z 16. base::getNamespace(ns) 2020-07-05T13:02:07.9913290Z 17. base::loadNamespace(name) 2020-07-05T13:02:07.9913590Z 19. base::loadNamespace(i, c(lib.loc, .libPaths()), versionCheck = vI[[i]]) 2020-07-05T13:02:07.9914620Z 22. base::loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]) 2020-07-05T13:02:07.9915600Z 25. base::loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]) 2020-07-05T13:02:07.9915870Z 26. base::library.dynam(lib, package, package.lib) 2020-07-05T13:02:07.9916130Z 27. base::dyn.load(file, DLLpath = DLLpath, ...) --- .github/workflows/R-CMD-check.yaml | 18 ++++++++---------- tests/testthat/test-map.R | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index da56bb0c..cacf0f86 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,10 +1,10 @@ on: push: branches: - - dev + - JOSS pull_request: branches: - - dev + - JOSS name: R-CMD-check @@ -18,14 +18,12 @@ jobs: fail-fast: false matrix: config: - - {os: windows-latest, r: '3.6'} - #- {os: macOS-latest, r: '3.6'} - #- {os: macOS-latest, r: 'devel'} - #- {os: ubuntu-16.04, r: '3.2', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} - #- {os: ubuntu-16.04, r: '3.3', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} - #- {os: ubuntu-16.04, r: '3.4', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} - #- {os: ubuntu-16.04, r: '3.5', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} - #- {os: ubuntu-16.04, r: '3.6', rspm: "https://demo.rstudiopm.com/all/__linux__/xenial/latest"} + - {os: windows-latest, r: '3.6', platform: windows-x86_64} + - {os: windows-latest, r: 'release', platform: windows-x86_64} + - {os: macOS-latest, r: '3.6', platform: osx-x86_64-clang} + - {os: macOS-latest, r: 'release', platform: osx-x86_64-clang} + - {os: ubuntu-latest, r: '3.6', platform: linux-x86_64-ubuntu-gcc} + - {os: ubuntu-latest, r: 'release', platform: linux-x86_64-ubuntu-gcc} env: # otherwise remotes::fun() errors cause the build to fail. Example: Unavailability of binaries diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index 9a205f9c..7d2d0a1c 100755 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -83,17 +83,17 @@ test_that("Map FeatureCollection", { }) # Case: Image -test_that("Map Image", { - nc <- sf::st_read(system.file("shp/arequipa.shp", package="rgee")) - m4 <- rgee:::ee_addLayer( - eeObject = image, - visParams = list(bands = c("B4", "B3", "B2"), max = 10000), - name = "SF" - ) - m6 <- m4 + mapview::mapview(nc) - m5 <- mapview::mapview(nc) + m4 - expect_equal(m4@object$names,"SF") -}) +#test_that("Map Image", { + # nc <- sf::st_read(system.file("shp/arequipa.shp", package="rgee")) + # m4 <- rgee:::ee_addLayer( + # eeObject = image, + # visParams = list(bands = c("B4", "B3", "B2"), max = 10000), + # name = "SF" + # ) + # m6 <- m4 + mapview::mapview(nc) + # m5 <- mapview::mapview(nc) + m4 + # expect_equal(m4@object$names,"SF") +#}) test_that("Map$centerObject", { From 72124b71f6df286e3005bff638ee9661a7f972b8 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sun, 5 Jul 2020 19:41:06 -0500 Subject: [PATCH 08/15] changes to pass rhub test --- DESCRIPTION | 2 +- R/plus.R | 12 +++--------- man/plus-EarthEngineMap-EarthEngineMap-method.Rd | 4 +--- man/plus-EarthEngineMap-mapview-method.Rd | 4 +--- man/plus-mapview-EarthEngineMap-method.Rd | 4 +--- man/rgee-package.Rd | 2 +- tests/testthat.R | 6 +++++- vignettes/BestPractices.Rmd | 7 ++++++- vignettes/BestPractices.md | 5 ++--- 9 files changed, 21 insertions(+), 25 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a8703bec..4f6db5f9 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -75,7 +75,7 @@ Authors@R: comment = "Appel reviewed the package for JOSS, see https://github.com/openjournals/joss-reviews/issues/2272")) Description: Earth Engine client library for R. All of the 'Earth Engine' API classes, - modules, and functions are made available. Additional functions implemented include importing (exporting) of R Earth Engine + modules, and functions are made available. Additional functions implemented include importing (exporting) of Earth Engine spatial objects, extraction of time series, interactive map display, assets management interface, and metadata display. See for further details. License: Apache License (>= 2.0) diff --git a/R/plus.R b/R/plus.R index 23263268..e36e7570 100644 --- a/R/plus.R +++ b/R/plus.R @@ -1,8 +1,6 @@ #' EarthEngineMap + EarthEngineMap; adds data from the second map to the first #' -#' @author Adapted from -#' \href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -#' tim-salabim} code. +#' @author tim-salabim Adapted from mapview code. #' @param e1 a EarthEngineMap map to which e2 should be added. #' @param e2 a EarthEngineMap map from which the objects should be added to e1. #' @@ -35,9 +33,7 @@ setMethod( #' EarthEngineMap + ANY; adds data from the second map to the first #' -#' @author Adapted from -#' \href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -#' tim-salabim} code. +#' @author tim-salabim Adapted from mapview code. #' @param e1 a EarthEngineMap map to which e2 should be added. #' @param e2 a EarthEngineMap map from which the objects should be added to e1. #' @@ -68,9 +64,7 @@ setMethod( #' ANY + EarthEngineMap; adds data from the second map to the first #' -#' @author Adapted from -#' \href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -#' tim-salabim} code. +#' @author tim-salabim Adapted from mapview code. #' @param e1 a EarthEngineMap map to which e2 should be added. #' @param e2 a EarthEngineMap map from which the objects should be added to e1. #' diff --git a/man/plus-EarthEngineMap-EarthEngineMap-method.Rd b/man/plus-EarthEngineMap-EarthEngineMap-method.Rd index 00d8a410..c4a73fb1 100644 --- a/man/plus-EarthEngineMap-EarthEngineMap-method.Rd +++ b/man/plus-EarthEngineMap-EarthEngineMap-method.Rd @@ -15,7 +15,5 @@ EarthEngineMap + EarthEngineMap; adds data from the second map to the first } \author{ -Adapted from -\href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -tim-salabim} code. +tim-salabim Adapted from mapview code. } diff --git a/man/plus-EarthEngineMap-mapview-method.Rd b/man/plus-EarthEngineMap-mapview-method.Rd index ebe71619..0b603ebf 100644 --- a/man/plus-EarthEngineMap-mapview-method.Rd +++ b/man/plus-EarthEngineMap-mapview-method.Rd @@ -15,7 +15,5 @@ EarthEngineMap + ANY; adds data from the second map to the first } \author{ -Adapted from -\href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -tim-salabim} code. +tim-salabim Adapted from mapview code. } diff --git a/man/plus-mapview-EarthEngineMap-method.Rd b/man/plus-mapview-EarthEngineMap-method.Rd index 83d29610..985d269c 100644 --- a/man/plus-mapview-EarthEngineMap-method.Rd +++ b/man/plus-mapview-EarthEngineMap-method.Rd @@ -15,7 +15,5 @@ ANY + EarthEngineMap; adds data from the second map to the first } \author{ -Adapted from -\href{https://github.com/r-spatial/mapview/blob/develop/R/plus.R}{ -tim-salabim} code. +tim-salabim Adapted from mapview code. } diff --git a/man/rgee-package.Rd b/man/rgee-package.Rd index 82ebe848..f73e0ac9 100644 --- a/man/rgee-package.Rd +++ b/man/rgee-package.Rd @@ -9,7 +9,7 @@ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} Earth Engine client library for R. All of the 'Earth Engine' API classes, - modules, and functions are made available. Additional functions implemented include importing (exporting) of R Earth Engine + modules, and functions are made available. Additional functions implemented include importing (exporting) of Earth Engine spatial objects, extraction of time series, interactive map display, assets management interface, and metadata display. See for further details. } diff --git a/tests/testthat.R b/tests/testthat.R index 7000b6c1..643ceae1 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -46,6 +46,10 @@ gcs_bucket_f <- function(){ # Initialize credentials # If you do not count with GCS credentials the test will be skipped -ee_Initialize(drive = TRUE, gcs = TRUE) +have_ee <- reticulate::py_module_available("ee") +have_numpy <- reticulate::py_module_available("numpy") +if (have_ee & have_numpy) { + ee_Initialize(drive = TRUE, gcs = TRUE) +} test_check("rgee") diff --git a/vignettes/BestPractices.Rmd b/vignettes/BestPractices.Rmd index 2b1450ed..7a138286 100644 --- a/vignettes/BestPractices.Rmd +++ b/vignettes/BestPractices.Rmd @@ -803,7 +803,12 @@ print(mappedFilter$first()$getInfo()) ## **reduceRegion() vs. reduceRegions()** vs. for-loop -Calling `reduceRegions()` with a very large or complex `FeatureCollection` as input may result in the dreaded "Computed value is too large" error. One potential solution is to map `reduceRegion()` over the `FeatureCollection` instead. Another potential solution is to use a (gasp) for-loop. Although this is strongly discouraged in Earth Engine as described [here](), [here]() and [here](), `reduceRegion()` can be implemented in a for-loop to perform large reductions. +Calling `reduceRegions()` with a very large or complex +`FeatureCollection` as input may result in the dreaded “Computed value +is too large” error. One potential solution is to map `reduceRegion()` +over the `FeatureCollection` instead. Another potential solution is to +use a (gasp) for-loop. Although this is strongly discouraged in Earth +Engine as described [here](https://developers.google.com/earth-engine/getstarted#mapping-what-to-do-instead-of-a-for-loop), [here](https://developers.google.com/earth-engine/tutorial_js_03#for-loops) and [here](https://developers.google.com/earth-engine/client_server#looping), `reduceRegion()` can be implemented in a for-loop to perform large reductions. Suppose your objective is to obtain the mean of pixels (or any statistic) in each feature in a `FeatureCollection` for each image in an `ImageCollection`. The following example compares the three approaches previously described: diff --git a/vignettes/BestPractices.md b/vignettes/BestPractices.md index cb8ae564..17e17a16 100644 --- a/vignettes/BestPractices.md +++ b/vignettes/BestPractices.md @@ -590,7 +590,7 @@ print(means$limit(10)$getInfo()) Note that other ways of reducing pixels of one image within zones of another include -[reduceConnectedCommponents()](https://developers.google.com/earth-engine/api_docs#ee.image.reduceconnectedcomponents) +[reduceConnectedCommponents](https://developers.google.com/earth-engine/api_docs#ee.image.reduceconnectedcomponents) and/or [grouping reducers](https://developers.google.com/earth-engine/api_docs#ee.image.reduceconnectedcomponents). @@ -932,8 +932,7 @@ Calling `reduceRegions()` with a very large or complex is too large” error. One potential solution is to map `reduceRegion()` over the `FeatureCollection` instead. Another potential solution is to use a (gasp) for-loop. Although this is strongly discouraged in Earth -Engine as described [here](), [here]() and [here](), `reduceRegion()` -can be implemented in a for-loop to perform large reductions. +Engine as described [here](https://developers.google.com/earth-engine/getstarted#mapping-what-to-do-instead-of-a-for-loop), [here](https://developers.google.com/earth-engine/tutorial_js_03#for-loops) and [here](https://developers.google.com/earth-engine/client_server#looping), `reduceRegion()` can be implemented in a for-loop to perform large reductions. Suppose your objective is to obtain the mean of pixels (or any statistic) in each feature in a `FeatureCollection` for each image in an From 93426269d600efcdf4cdaf9e54a2a8ddc4fba3d8 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Mon, 6 Jul 2020 12:14:52 -0500 Subject: [PATCH 09/15] Fix a bug in ee_get_date_ic --- NEWS.md | 1 + R/ee_Date.R | 46 +++++++++++++++++++++------------------------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/NEWS.md b/NEWS.md index f43d2012..258f35a0 100755 --- a/NEWS.md +++ b/NEWS.md @@ -35,6 +35,7 @@ to upload and download images. - `ee_extract` was adapted to work well with changes in `sf_as_ee`. - R CMD check is more friendly with users, the use of `--run-dontrun` is also available (#72 Thanks @appelmar). +- Fix a bug in `ee_get_date_ic`. # rgee 0.6.2 - Earth Engine Python API updated to 0.1.226. diff --git a/R/ee_Date.R b/R/ee_Date.R index dba3f6b4..ccd36b70 100644 --- a/R/ee_Date.R +++ b/R/ee_Date.R @@ -174,6 +174,16 @@ ee_get_date_ic <- function(x, time_end = FALSE) { ee_utils$eedate_to_rdate_ic(x, "system:time_start") ) + # Getting ID + image_id <- x$aggregate_array("system:id")$getInfo() + if (is.null(image_id)) { + image_id <- rep("no_id", x$size()$getInfo()) + } + if (length(image_id) == 0) { + image_id <- rep("no_id", x$size()$getInfo()) + } + + if (is.null(time_start)) { time_start <- NA } else { @@ -185,37 +195,23 @@ ee_get_date_ic <- function(x, time_end = FALSE) { } # Getting time_end - time_end <- ee_utils_py_to_r( - ee_utils$eedate_to_rdate_ic(x, "system:time_end") - ) - - if (is.null(time_end)) { - time_end <- NULL + if (!time_end) { + return( + data.frame( + id = image_id, + time_start = time_start, + stringsAsFactors = FALSE + ) + ) } else { + time_end <- ee_utils_py_to_r( + ee_utils$eedate_to_rdate_ic(x, "system:time_end") + ) time_end <- as.POSIXct( x = time_end / 1000, origin = "1970-01-01", tz = "GMT" ) - } - - # Getting ID - image_id <- x$aggregate_array("system:id")$getInfo() - if (is.null(image_id)) { - image_id <- rep("no_id", x$size()$getInfo()) - } - if (length(image_id) == 0) { - image_id <- rep("no_id", x$size()$getInfo()) - } - - # Create data.frame - if (is.null(time_end)) { - data.frame( - id = image_id, - time_start = time_start, - stringsAsFactors = FALSE - ) - } else { data.frame( id = image_id, time_start = time_start, From ff4a3f2f9e0f3007fc7d5361fbbddbb0df5bacff Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Thu, 9 Jul 2020 14:43:53 -0500 Subject: [PATCH 10/15] Map$addLayer now display a legend! Earth Engine Python API updated to 0.1.227 --- NEWS.md | 2 ++ R/Map.R | 75 ++++++++++++++++++++++++++++++++++++++++++++++---- R/ee_version.R | 2 +- R/plus.R | 20 ++++++++++++-- README.md | 2 +- man/Map.Rd | 4 ++- 6 files changed, 94 insertions(+), 11 deletions(-) diff --git a/NEWS.md b/NEWS.md index 258f35a0..85fea602 100755 --- a/NEWS.md +++ b/NEWS.md @@ -36,6 +36,8 @@ to upload and download images. - R CMD check is more friendly with users, the use of `--run-dontrun` is also available (#72 Thanks @appelmar). - Fix a bug in `ee_get_date_ic`. +- `Map$addLayer` now display a legend when `eeObject` is an one-band `ee$Image`. +- - Earth Engine Python API updated to 0.1.227. # rgee 0.6.2 - Earth Engine Python API updated to 0.1.226. diff --git a/R/Map.R b/R/Map.R index e5c68e91..4946c99a 100644 --- a/R/Map.R +++ b/R/Map.R @@ -7,7 +7,7 @@ #' following functions: #' \itemize{ #' \item \strong{addLayer(eeObject, visParams, name = NULL, shown = TRUE, -#' opacity = 1)}: Adds a given EE object to the map as a layer. \cr +#' opacity = 1, legend = TRUE)}: Adds a given EE object to the map as a layer. \cr #' \itemize{ #' \item \strong{eeObject:} The object to add to mapview.\cr #' \item \strong{visParams:} List of parameters for visualization. @@ -17,6 +17,8 @@ #' layer should be on by default. \cr #' \item \strong{opacity:} The layer's opacity represented as a number #' between 0 and 1. Defaults to 1. \cr +#' \item \strong{legend:} Should a legend be plotted?. Ignore if \code{eeObject} +#' is not a single-band ee$Image. #' } #' \item \strong{setCenter(lon = 0, lat = 0, zoom = NULL)}: Centers the map #' view at the given coordinates with the given zoom level. If no zoom level @@ -251,7 +253,8 @@ ee_addLayer <- function(eeObject, visParams = NULL, name = NULL, shown = TRUE, - opacity = 1) { + opacity = 1, + legend = TRUE) { if (!requireNamespace("jsonlite", quietly = TRUE)) { stop("package jsonlite required, please install it first") } @@ -277,6 +280,7 @@ ee_addLayer <- function(eeObject, " of ee$Image, ee$Geometry, ee$Feature, or ee$FeatureCollection." ) } + if (any(class(eeObject) %in% ee_get_spatial_objects("Table"))) { features <- ee$FeatureCollection(eeObject) @@ -299,7 +303,6 @@ ee_addLayer <- function(eeObject, fillColor = "00000000", width = width ) - image <- image_fill$blend(image_outline) } else { image <- do.call(eeObject$visualize, visParams) @@ -314,10 +317,14 @@ ee_addLayer <- function(eeObject, if (is.null(name)) name <- "untitled" } tile <- get_ee_image_url(image) - ee_addTile(tile, name = name, shown = shown, opacity = opacity) + map <- ee_addTile(tile = tile, name = name, shown = shown, opacity = opacity) + if (legend) { + map <- ee_add_legend(map, eeObject, visParams, name) + } else { + map + } } - #' Basic base mapview object #' @noRd ee_mapview <- function() { @@ -348,14 +355,69 @@ ee_addTile <- function(tile, name, shown, opacity) { ) %>% ee_mapViewLayersControl(names = name) %>% leaflet::hideGroup(if (!shown) name else NULL) + m@object$tokens <- tile m@object$names <- name m@object$opacity <- opacity m@object$shown <- shown + m@object$min <- shown + m@object$max <- shown + m@object$palette <- shown m <- new("EarthEngineMap", object = m@object, map = m@map) m } +#' Add legend to EarthEngineMap objects +#' @noRd +ee_add_legend <- function(m, eeObject, visParams, name) { + ee_obj_class <- class(eeObject) + type <- ee_obj_class[ee_obj_class %in% ee_get_spatial_objects("All")] + if (type == "ee.image.Image") { + # add legend only to one-band images + if (is.null(visParams$bands) | length(visParams$bands) == 1) { + if (is.null(visParams$max) | is.null(visParams$min)) { + eeimage_type <- eeObject$bandTypes()$getInfo() + eeimage_type_min <- eeimage_type[[1]]$min + eeimage_type_max <- eeimage_type[[1]]$max + } + if (is.null(visParams$max)) { + visParams$max <- eeimage_type_max + } + if (is.null(visParams$min)) { + visParams$min <- eeimage_type_min + } + if (is.null(visParams$palette)) { + visParams$palette <- c("000000","FFFFFF") + } + visParams$palette <- sprintf("#%s", gsub("#", "",visParams$palette)) + pal <- leaflet::colorNumeric(visParams$palette, c(visParams$min, visParams$max)) + map <- m@map %>% + leaflet::addLegend( + position = "bottomright", + pal = pal, + values = c(visParams$min, visParams$max), + opacity = 1, + title = name + ) + + # Extra parameters to EarthEngineMap objects that inherit from + # one single-band ee$Image and active legend = TRUE + m@object$name <- name + m@object$min <- visParams$min + m@object$max <- visParams$max + m@object$palette <- pal + m@object$legend <- TRUE + + m <- new("EarthEngineMap", object = m@object, map = map) + m + } else { + m + } + } else { + m + } +} + if (!isGeneric("+")) { setGeneric("+", function(x, y, ...) standardGeneric("+")) @@ -372,7 +434,7 @@ get_ee_image_url <- function(image) { #' Return R classes for Earth Engine Spatial Objects #' @noRd -ee_get_spatial_objects <- function(type = "all") { +ee_get_spatial_objects <- function(type = "All") { if (type == "Table") { ee_spatial_object <- c( "ee.geometry.Geometry", @@ -482,3 +544,4 @@ ee_as_mapview <- function(x) { # Create an Map env and set methods Map <- Map() ee_set_methods() + diff --git a/R/ee_version.R b/R/ee_version.R index 3000ed1b..8ae2370a 100644 --- a/R/ee_version.R +++ b/R/ee_version.R @@ -5,5 +5,5 @@ #' @return Character. Earth Engine Python API version used to build rgee. #' @export ee_version <- function() { - '0.1.226' + '0.1.227' } diff --git a/R/plus.R b/R/plus.R index e36e7570..129fc549 100644 --- a/R/plus.R +++ b/R/plus.R @@ -12,9 +12,13 @@ setMethod( ), function(e1, e2) { e2_token <- e2@object$tokens - e2_name <- e2@object$names + e2_name <- e2@object$name e2_opacity <- e2@object$opacity e2_shown <- e2@object$shown + e2_min <- e2@object$min + e2_max <- e2@object$max + e2_pal <- e2@object$palette + e2_legend <- e2@object$legend for (x in seq_len(length(e2_name))) { e1@map <- e1@map %>% @@ -26,7 +30,19 @@ setMethod( ee_mapViewLayersControl(names = e2_name[x]) %>% leaflet::hideGroup(if (!e2_shown[x]) e2_name[x] else NULL) } - return(e1) + + if (isTRUE(e2_legend)) { + e1@map <- e1@map %>% + leaflet::addLegend( + position = "bottomright", + pal = e2_pal, + values = c(e2_min, e2_max), + opacity = 1, + title = e2_name + ) + } else { + e1 + } } ) diff --git a/README.md b/README.md index 27b58e91..a8c3fc3d 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ status](https://www.r-pkg.org/badges/version/rgee)](https://cran.r-project.org/p `rgee` is a binding package for calling [Google Earth Engine API](https://developers.google.com/earth-engine/) from within R. Additionally, several functions have been implemented to make simple the connection with the R spatial ecosystem. The current version of rgee has been built considering the -[earthengine-api 0.1.226](https://pypi.org/project/earthengine-api/0.1.226/). +[earthengine-api 0.1.227](https://pypi.org/project/earthengine-api/0.1.227/). **Note that access to Google Earth Engine is only available to [registered users](https://earthengine.google.com/)**. diff --git a/man/Map.Rd b/man/Map.Rd index 8bfef267..0bc5090a 100644 --- a/man/Map.Rd +++ b/man/Map.Rd @@ -9,7 +9,7 @@ An object of class environment with the following functions: \itemize{ \item \strong{addLayer(eeObject, visParams, name = NULL, shown = TRUE, -opacity = 1)}: Adds a given EE object to the map as a layer. \cr +opacity = 1, legend = TRUE)}: Adds a given EE object to the map as a layer. \cr \itemize{ \item \strong{eeObject:} The object to add to mapview.\cr \item \strong{visParams:} List of parameters for visualization. @@ -19,6 +19,8 @@ See details.\cr layer should be on by default. \cr \item \strong{opacity:} The layer's opacity represented as a number between 0 and 1. Defaults to 1. \cr +\item \strong{legend:} Should a legend be plotted?. Ignore if \code{eeObject} +is not a single-band ee$Image. } \item \strong{setCenter(lon = 0, lat = 0, zoom = NULL)}: Centers the map view at the given coordinates with the given zoom level. If no zoom level From 163b955db8e4f4a4ef5c386d76083cb6bbdf3ecf Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Thu, 9 Jul 2020 14:59:21 -0500 Subject: [PATCH 11/15] changes in test-map.R --- R/Map.R | 6 +----- tests/testthat/test-map.R | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/R/Map.R b/R/Map.R index 4946c99a..3130e42b 100644 --- a/R/Map.R +++ b/R/Map.R @@ -357,12 +357,9 @@ ee_addTile <- function(tile, name, shown, opacity) { leaflet::hideGroup(if (!shown) name else NULL) m@object$tokens <- tile - m@object$names <- name + m@object$name <- name m@object$opacity <- opacity m@object$shown <- shown - m@object$min <- shown - m@object$max <- shown - m@object$palette <- shown m <- new("EarthEngineMap", object = m@object, map = m@map) m } @@ -402,7 +399,6 @@ ee_add_legend <- function(m, eeObject, visParams, name) { # Extra parameters to EarthEngineMap objects that inherit from # one single-band ee$Image and active legend = TRUE - m@object$name <- name m@object$min <- visParams$min m@object$max <- visParams$max m@object$palette <- pal diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index 7d2d0a1c..b73052a7 100755 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -52,8 +52,8 @@ test_that("Map geometry", { list(pointRadius = 10, color = "FF0000"), "Geometry-Arequipa-test") m1_noviz <- rgee:::ee_addLayer(geom,name = "Geometry-Arequipa") - expect_equal(m1@object$names, "Geometry-Arequipa-test") - expect_equal(m1_noviz@object$names, "Geometry-Arequipa") + expect_equal(m1@object$name, "Geometry-Arequipa-test") + expect_equal(m1_noviz@object$name, "Geometry-Arequipa") }) test_that("Map geometry", { @@ -61,8 +61,8 @@ test_that("Map geometry", { list(pointRadius = 10, color = "FF0000"), "Geometry-Arequipa-test") m1_noviz <- rgee:::ee_addLayer(geom,name = "Geometry-Arequipa") - expect_equal(m1@object$names, "Geometry-Arequipa-test") - expect_equal(m1_noviz@object$names, "Geometry-Arequipa") + expect_equal(m1@object$name, "Geometry-Arequipa-test") + expect_equal(m1_noviz@object$name, "Geometry-Arequipa") }) test_that("Map feature", { @@ -70,7 +70,7 @@ test_that("Map feature", { ee$Feature(geom), name = "Feature-Arequipa-test" ) - expect_equal(m2@object$names,"Feature-Arequipa-test") + expect_equal(m2@object$name,"Feature-Arequipa-test") }) # Case: FeatureCollection @@ -79,7 +79,7 @@ test_that("Map FeatureCollection", { eeObject = eeobject_fc, name = "FeatureCollection" ) - expect_equal(m3@object$names,"FeatureCollection") + expect_equal(m3@object$name,"FeatureCollection") }) # Case: Image From 7f7cda604b6907d115a5a8979b780be8c5ffbd5a Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Fri, 10 Jul 2020 09:30:00 -0500 Subject: [PATCH 12/15] Minor bug in Map$addLayer. Map$addLayer creates EarthEngineMap objects but not display them. --- R/Map.R | 2 +- rgee.Rproj | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/R/Map.R b/R/Map.R index 3130e42b..55e08f19 100644 --- a/R/Map.R +++ b/R/Map.R @@ -319,7 +319,7 @@ ee_addLayer <- function(eeObject, tile <- get_ee_image_url(image) map <- ee_addTile(tile = tile, name = name, shown = shown, opacity = opacity) if (legend) { - map <- ee_add_legend(map, eeObject, visParams, name) + ee_add_legend(map, eeObject, visParams, name) } else { map } diff --git a/rgee.Rproj b/rgee.Rproj index 0167accc..bc0be34a 100755 --- a/rgee.Rproj +++ b/rgee.Rproj @@ -1,8 +1,8 @@ Version: 1.0 -RestoreWorkspace: Default -SaveWorkspace: Default -AlwaysSaveHistory: Default +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: No EnableCodeIndexing: Yes UseSpacesForTab: Yes @@ -19,3 +19,6 @@ BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageCheckArgs: --as-cran --run-dontrun + +QuitChildProcessesOnExit: Yes +DisableExecuteRprofile: Yes From 0d6715789469180f943961a878cd2909e5e1d536 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Sat, 11 Jul 2020 18:16:16 -0500 Subject: [PATCH 13/15] Minor bug in Image PixelType --- R/Map.R | 9 +++++++-- tests/testthat/test-map.R | 3 --- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/R/Map.R b/R/Map.R index 55e08f19..41f885bc 100644 --- a/R/Map.R +++ b/R/Map.R @@ -269,8 +269,6 @@ ee_addLayer <- function(eeObject, visParams <- list() } - image <- NULL - # Earth Engine Spatial object ee_spatial_object <- ee_get_spatial_objects("Simple") @@ -375,7 +373,14 @@ ee_add_legend <- function(m, eeObject, visParams, name) { if (is.null(visParams$max) | is.null(visParams$min)) { eeimage_type <- eeObject$bandTypes()$getInfo() eeimage_type_min <- eeimage_type[[1]]$min + # Added to deal with PixelType Images + if (is.null(eeimage_type_min)) { + eeimage_type_min <- 0 + } eeimage_type_max <- eeimage_type[[1]]$max + if (is.null(eeimage_type_max)) { + eeimage_type_max <- 1 + } } if (is.null(visParams$max)) { visParams$max <- eeimage_type_max diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R index b73052a7..aa0ae961 100755 --- a/tests/testthat/test-map.R +++ b/tests/testthat/test-map.R @@ -32,7 +32,6 @@ test_that("Map ee_setZoom", { expect_type(eeCenter, "environment") }) - test_that("Map ee_centerObject", { eeCenter <- rgee:::ee_centerObject( eeObject = geom, @@ -94,8 +93,6 @@ test_that("Map FeatureCollection", { # m5 <- mapview::mapview(nc) + m4 # expect_equal(m4@object$names,"SF") #}) - - test_that("Map$centerObject", { rgee:::ee_centerObject(eeObject = image) expect_equal(Map$lat, 37.4716,tolerance = .001) From 13ee3a493e1e5c1d38b308b21238b03a9fd6f241 Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Tue, 14 Jul 2020 16:19:21 -0500 Subject: [PATCH 14/15] ee_get and Map$addLayer added --- NAMESPACE | 1 + NEWS.md | 4 +- R/Map.R | 101 ++++++++++++- R/ee_Date.R | 144 ------------------ R/ee_Initialize.R | 50 ------- R/ee_get.R | 259 +++++++++++++++++++++++++++++++++ README.md | 2 +- man/Map.Rd | 28 +++- man/ee_get.Rd | 41 ++++++ man/ee_get_assethome.Rd | 2 +- man/ee_get_date_ic.Rd | 2 +- man/ee_get_date_img.Rd | 2 +- man/ee_get_earthengine_path.Rd | 2 +- tests/testthat/test-ee_get.R | 29 ++++ 14 files changed, 463 insertions(+), 204 deletions(-) create mode 100644 R/ee_get.R create mode 100644 man/ee_get.Rd create mode 100644 tests/testthat/test-ee_get.R diff --git a/NAMESPACE b/NAMESPACE index 06412872..6e9da8bb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -25,6 +25,7 @@ export(ee_clean_pyenv) export(ee_drive_to_local) export(ee_extract) export(ee_gcs_to_local) +export(ee_get) export(ee_get_assethome) export(ee_get_date_ic) export(ee_get_date_img) diff --git a/NEWS.md b/NEWS.md index 85fea602..5494ebc8 100755 --- a/NEWS.md +++ b/NEWS.md @@ -36,7 +36,9 @@ to upload and download images. - R CMD check is more friendly with users, the use of `--run-dontrun` is also available (#72 Thanks @appelmar). - Fix a bug in `ee_get_date_ic`. -- `Map$addLayer` now display a legend when `eeObject` is an one-band `ee$Image`. +- `Map$addLayer` now could display a legend when `eeObject` is an one-band `ee$Image` (By default legend = TRUE). +- New function `ee_get`: Return the element at the specified position in a Earth Engine Collection. +- New function `Map$addLayers`: Create interactive visualizations of ImageCollections. - - Earth Engine Python API updated to 0.1.227. # rgee 0.6.2 diff --git a/R/Map.R b/R/Map.R index 41f885bc..e7c2877f 100644 --- a/R/Map.R +++ b/R/Map.R @@ -7,7 +7,7 @@ #' following functions: #' \itemize{ #' \item \strong{addLayer(eeObject, visParams, name = NULL, shown = TRUE, -#' opacity = 1, legend = TRUE)}: Adds a given EE object to the map as a layer. \cr +#' opacity = 1, legend = FALSE)}: Adds a given EE object to the map as a layer. \cr #' \itemize{ #' \item \strong{eeObject:} The object to add to mapview.\cr #' \item \strong{visParams:} List of parameters for visualization. @@ -20,6 +20,20 @@ #' \item \strong{legend:} Should a legend be plotted?. Ignore if \code{eeObject} #' is not a single-band ee$Image. #' } +#' \item \strong{addLayers(eeObject, visParams, name = NULL, shown = TRUE, +#' opacity = 1, legend = FALSE)}: Adds a given ee$ImageCollection to the map +#' as multiple layers. \cr +#' \itemize{ +#' \item \strong{eeObject:} The ee$ImageCollection to add to mapview.\cr +#' \item \strong{visParams:} List of parameters for visualization. +#' See details.\cr +#' \item \strong{name:} The name of layers.\cr +#' \item \strong{shown:} A flag indicating whether +#' layers should be on by default. \cr +#' \item \strong{opacity:} The layer's opacity represented as a number +#' between 0 and 1. Defaults to 1. \cr +#' \item \strong{legend:} Should a legend be plotted?. +#' } #' \item \strong{setCenter(lon = 0, lat = 0, zoom = NULL)}: Centers the map #' view at the given coordinates with the given zoom level. If no zoom level #' is provided, it uses 1 by default. @@ -138,6 +152,18 @@ #' # Case 6: mapedit #' library(mapedit) #' # my_geometry <- m2 %>% ee_as_mapview() %>% editMap() +#' +#' # Case 7: ImageCollection +#' nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% +#' st_transform(4326) %>% +#' sf_as_ee() +#' +#' ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ +#' filterDate("2016-01-01", "2016-01-31")$ +#' filterBounds(nc) %>% +#' ee_get(0:4) +#' Map$centerObject(nc$geometry()) +#' Map$addLayers(ee_s2) #' } #' @export Map <- function() { @@ -146,6 +172,7 @@ Map <- function() { ee_set_methods <- function() { Map$addLayer <- ee_addLayer + Map$addLayers <- ee_addLayers Map$setCenter <- ee_setCenter Map$setZoom <- ee_setZoom Map$centerObject <- ee_centerObject @@ -254,7 +281,7 @@ ee_addLayer <- function(eeObject, name = NULL, shown = TRUE, opacity = 1, - legend = TRUE) { + legend = FALSE) { if (!requireNamespace("jsonlite", quietly = TRUE)) { stop("package jsonlite required, please install it first") } @@ -323,6 +350,74 @@ ee_addLayer <- function(eeObject, } } +#' Adds a given ee$ImageCollection to the map as a layer. +#' https://developers.google.com/earth-engine/api_docs#map.addlayer +#' @noRd +ee_addLayers <- function(eeObject, + visParams = NULL, + nmax = 5, + name = NULL, + shown = TRUE, + opacity = 1, + legend = FALSE) { + + if (!requireNamespace("jsonlite", quietly = TRUE)) { + stop("package jsonlite required, please install it first") + } + + if (!requireNamespace("mapview", quietly = TRUE)) { + stop("package mapview required, please install it first") + } + + if (!requireNamespace("leaflet", quietly = TRUE)) { + stop("package leaflet required, please install it first") + } + + # is an ee.imagecollection.ImageCollection? + if (!any(class(eeObject) %in% "ee.imagecollection.ImageCollection")) { + stop("eeObject argument is not an ee$imagecollection$ImageCollection") + } + + if (is.null(visParams)) { + visParams <- list() + } + + # size of objects + eeObject_size <- eeObject$size()$getInfo() + m_img_list <- list() + + if (is.null(name)) { + name <- tryCatch( + expr = eeObject$aggregate_array("system:id")$getInfo(), + error = function(e) "untitled" + ) + if (is.null(name)) name <- "untitled" + } + + if (length(name) == 1) { + name <- sprintf("%s_%02d", name, seq_len(eeObject_size)) + } + + if (length(name) == length(eeObject_size)) { + stop("name does not have the same length than eeObject$size()$getInfo().") + } + + for (index in seq_len(eeObject_size)) { + py_index <- index - 1 + m_img <- Map$addLayer( + eeObject = ee_get(eeObject, index = py_index)$first(), + visParams = visParams, + name = name[index], + shown = shown, + opacity = opacity, + legend = legend + ) + m_img_list[[index]] <- m_img + } + Reduce('+',m_img_list) +} + + #' Basic base mapview object #' @noRd ee_mapview <- function() { @@ -389,7 +484,7 @@ ee_add_legend <- function(m, eeObject, visParams, name) { visParams$min <- eeimage_type_min } if (is.null(visParams$palette)) { - visParams$palette <- c("000000","FFFFFF") + visParams$palette <- c("000000", "FFFFFF") } visParams$palette <- sprintf("#%s", gsub("#", "",visParams$palette)) pal <- leaflet::colorNumeric(visParams$palette, c(visParams$min, visParams$max)) diff --git a/R/ee_Date.R b/R/ee_Date.R index ccd36b70..049523be 100644 --- a/R/ee_Date.R +++ b/R/ee_Date.R @@ -76,147 +76,3 @@ eedate_to_rdate <- function(ee_date, timestamp = FALSE) { as.POSIXct(x = date_numeric/1000, origin = "1970-01-01", tz = "GMT") } } - -#' Get the date of a EE Image -#' -#' @param x ee$Image or ee$ImageCollection object -#' @param time_end Logical. If TRUE, the \code{system:time_end} property is -#' also returned. See details. -#' -#' @details -#' \code{system:time_start} set the start period of data acquisition while -#' \code{system:time_end} does the same for the end period. See the -#' \href{https://developers.google.com/earth-engine/glossary}{Earth Engine glossary} -#' for getting more information. -#' -#' @return An List object with the elements: id, time_start and time_end -#' (if the \code{time_end} argument is TRUE). -#' @family date functions -#' @examples -#' \dontrun{ -#' library(rgee) -#' ee_Initialize() -#' -#' l8 <- ee$Image('LANDSAT/LC08/C01/T1_TOA/LC08_044034_20140318') -#' ee_get_date_img(l8) -#' srtm <- ee$Image('CGIAR/SRTM90_V4') -#' ee_get_date_img(srtm, time_end = TRUE) -#' } -#' @export -ee_get_date_img <- function(x, time_end = FALSE) { - time_start <- tryCatch( - expr = eedate_to_rdate(x$get("system:time_start")), - error = function(e) NA - ) - if (isTRUE(time_end)) { - time_end <- tryCatch( - expr = eedate_to_rdate(x$get("system:time_end")), - error = function(e) NA - ) - } else { - time_end <- NULL - } - # get id of the image - image_id <- x$get("system:id")$getInfo() - if (is.null(image_id)) { - image_id <- "no_id" - } - if (is.null(time_end)) { - list(image_id = image_id, time_start = time_start) - } else { - list(image_id = image_id, time_start = time_start, time_end = time_end) - } -} - -#' Get the date of a EE ImageCollection -#' -#' @param x ee$ImageCollection object -#' @param time_end Logical. If TRUE, the \code{system:time_end} property is -#' also returned. See details. -#' -#' @details -#' \code{system:time_start} set the start period of data acquisition while -#' \code{system:time_end} does the same for the end period. See the -#' \href{https://developers.google.com/earth-engine/glossary}{Earth Engine glossary} -#' for getting more information. -#' -#' @return A data.frame with the columns: \code{id} (ID of the image), -#' \code{time_start}, and \code{time_end} (If the argument \code{time_end} is -#' set as TRUE). The number of rows depends of the number of images -#' (\code{ee$ImageCollection$size}). -#' -#' @family date functions -#' -#' @examples -#' \dontrun{ -#' library(rgee) -#' library(sf) -#' ee_Initialize() -#' -#' nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% -#' st_transform(4326) %>% -#' sf_as_ee() -#' -#' ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ -#' filterDate("2016-01-01", "2016-01-31")$ -#' filterBounds(nc) -#' -#' ee_get_date_ic(ee_s2) -#' } -#' @export -ee_get_date_ic <- function(x, time_end = FALSE) { - # Call Python module - oauth_func_path <- system.file("python/ee_utils.py", package = "rgee") - ee_utils <- ee_source_python(oauth_func_path) - - # Fetch the time_start of each Image - time_start <- ee_utils_py_to_r( - ee_utils$eedate_to_rdate_ic(x, "system:time_start") - ) - - # Getting ID - image_id <- x$aggregate_array("system:id")$getInfo() - if (is.null(image_id)) { - image_id <- rep("no_id", x$size()$getInfo()) - } - if (length(image_id) == 0) { - image_id <- rep("no_id", x$size()$getInfo()) - } - - - if (is.null(time_start)) { - time_start <- NA - } else { - time_start <- as.POSIXct( - x = time_start / 1000, - origin = "1970-01-01", - tz = "GMT" - ) - } - - # Getting time_end - if (!time_end) { - return( - data.frame( - id = image_id, - time_start = time_start, - stringsAsFactors = FALSE - ) - ) - } else { - time_end <- ee_utils_py_to_r( - ee_utils$eedate_to_rdate_ic(x, "system:time_end") - ) - time_end <- as.POSIXct( - x = time_end / 1000, - origin = "1970-01-01", - tz = "GMT" - ) - data.frame( - id = image_id, - time_start = time_start, - time_end = time_end, - stringsAsFactors = FALSE - ) - } -} diff --git a/R/ee_Initialize.R b/R/ee_Initialize.R index 345cbd7f..c4733018 100644 --- a/R/ee_Initialize.R +++ b/R/ee_Initialize.R @@ -571,39 +571,6 @@ ee_sessioninfo <- function(email = NULL, write.table(df, sessioninfo, row.names = FALSE) } -#' Get the path where the credentials are stored -#' -#' @family path utils -#' @return A character which represents the path credential of a specific -#' user -#' -#' @export -ee_get_earthengine_path <- function() { - oauth_func_path <- system.file("python/ee_utils.py", package = "rgee") - utils_py <- ee_source_python(oauth_func_path) - ee_path <- ee_utils_py_to_r(utils_py$ee_path()) - - sessioninfo <- sprintf( - "%s/rgee_sessioninfo.txt", - ee_utils_py_to_r(utils_py$ee_path()) - ) - if (file.exists(sessioninfo)) { - user <- read.table(sessioninfo, - header = TRUE, - stringsAsFactors = FALSE - )[[1]] - if (is.na(user)) { - stop("rgee_sessioninfo.txt malformed") - } - } else { - stop( - "rgee_sessioninfo.txt does not exist, ", - "run rgee::ee_Initialize() to fixed." - ) - } - return(sprintf("%s/%s/", ee_path, user)) -} - #' Read and evaluate a python script #' @noRd ee_source_python <- function(oauth_func_path) { @@ -674,23 +641,6 @@ create_table <- function(user, wsc, quiet = FALSE) { } } -#' Get the Asset home name -#' @family path utils -#' @examples -#' \dontrun{ -#' library(rgee) -#' ee_Initialize() -#' -#' ee_get_assethome() -#' } -#' @return Character. The name of the Earth Engine Asset home -#' (e.g. users/datacolecfbf) -#' @export -ee_get_assethome <- function() { - options('rgee.ee_user')[[1]] -} - - #' Wrapper to create a EE Assets home #' @noRd ee_createAssetHome <- function() { diff --git a/R/ee_get.R b/R/ee_get.R new file mode 100644 index 00000000..e5541741 --- /dev/null +++ b/R/ee_get.R @@ -0,0 +1,259 @@ +#' Return the element at the specified position in a Earth Engine Collection +#' +#' @param ee_c ImageCollection or FeatureCollection. +#' @param index Numeric. Specified position. +#' @return Depending of \code{ee_c} can return either an ee$FeatureCollection +#' or ee$ImageCollection. +#' @examples +#' \dontrun{ +#' library(rgee) +#' library(sf) +#' +#' ee_Initialize() +#' +#' nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% +#' st_transform(4326) %>% +#' sf_as_ee() +#' +#' ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ +#' filterDate("2016-01-01", "2016-01-31")$ +#' filterBounds(nc) +#' +#' ee_s2$size()$getInfo() # 126 +#' +#' # Get the first 5 elements +#' ee_get(ee_s2, index = 0:4)$size()$getInfo() # 5 +#' } +#' @export +ee_get <- function(ee_c, index = 0) { + is_consecutive <- all(diff(index) == 1) + if (any(index < 0)) { + stop("index must be a positive value") + } + if (any(class(ee_c) %in% c("ee.imagecollection.ImageCollection"))) { + # Index is a single value? + if (length(index) == 1) { + ee$ImageCollection(ee_c$toList(count = 1, offset = index)) + } else { + # Index is a n-length vector and consecutive? + if (length(index) > 1 & is_consecutive) { + ee_c %>% + ee$ImageCollection$toList(count = length(index), offset = min(index)) %>% + ee$ImageCollection() + } else { + stop("ee_get only support ascending index order") + } + } + } else if (any(class(ee_c) %in% c("ee.featurecollection.FeatureCollection"))) { + # Index is a single value? + if (length(index) == 1) { + ee$FeatureCollection(ee_c$toList(count = 1, offset = index)) + } else { + # Index is a n-length vector and consecutive? + if (length(index) > 1 & is_consecutive) { + ee_c %>% + ee$FeatureCollection$toList(count = length(index), offset = min(index)) %>% + ee$FeatureCollection() + } else { + stop("ee_get only support ascending index order") + } + } + } else { + stop("ee_get only support objects of class FeatureCollection and ImageCollection.", + "\nEnter: ", class(ee_c)[1], + "\nExpected: ee$ImageCollection or ee$FeatureCollection") + } +} + +#' Get the Asset home name +#' @family path utils +#' @examples +#' \dontrun{ +#' library(rgee) +#' ee_Initialize() +#' +#' ee_get_assethome() +#' } +#' @return Character. The name of the Earth Engine Asset home +#' (e.g. users/datacolecfbf) +#' @export +ee_get_assethome <- function() { + options('rgee.ee_user')[[1]] +} + +#' Get the date of a EE Image +#' +#' @param x ee$Image or ee$ImageCollection object +#' @param time_end Logical. If TRUE, the \code{system:time_end} property is +#' also returned. See details. +#' +#' @details +#' \code{system:time_start} set the start period of data acquisition while +#' \code{system:time_end} does the same for the end period. See the +#' \href{https://developers.google.com/earth-engine/glossary}{Earth Engine glossary} +#' for getting more information. +#' +#' @return An List object with the elements: id, time_start and time_end +#' (if the \code{time_end} argument is TRUE). +#' @family date functions +#' @examples +#' \dontrun{ +#' library(rgee) +#' ee_Initialize() +#' +#' l8 <- ee$Image('LANDSAT/LC08/C01/T1_TOA/LC08_044034_20140318') +#' ee_get_date_img(l8) +#' srtm <- ee$Image('CGIAR/SRTM90_V4') +#' ee_get_date_img(srtm, time_end = TRUE) +#' } +#' @export +ee_get_date_img <- function(x, time_end = FALSE) { + time_start <- tryCatch( + expr = eedate_to_rdate(x$get("system:time_start")), + error = function(e) NA + ) + if (isTRUE(time_end)) { + time_end <- tryCatch( + expr = eedate_to_rdate(x$get("system:time_end")), + error = function(e) NA + ) + } else { + time_end <- NULL + } + # get id of the image + image_id <- x$get("system:id")$getInfo() + if (is.null(image_id)) { + image_id <- "no_id" + } + if (is.null(time_end)) { + list(image_id = image_id, time_start = time_start) + } else { + list(image_id = image_id, time_start = time_start, time_end = time_end) + } +} + +#' Get the date of a EE ImageCollection +#' +#' @param x ee$ImageCollection object +#' @param time_end Logical. If TRUE, the \code{system:time_end} property is +#' also returned. See details. +#' +#' @details +#' \code{system:time_start} set the start period of data acquisition while +#' \code{system:time_end} does the same for the end period. See the +#' \href{https://developers.google.com/earth-engine/glossary}{Earth Engine glossary} +#' for getting more information. +#' +#' @return A data.frame with the columns: \code{id} (ID of the image), +#' \code{time_start}, and \code{time_end} (If the argument \code{time_end} is +#' set as TRUE). The number of rows depends of the number of images +#' (\code{ee$ImageCollection$size}). +#' +#' @family date functions +#' +#' @examples +#' \dontrun{ +#' library(rgee) +#' library(sf) +#' ee_Initialize() +#' +#' nc <- st_read(system.file("shape/nc.shp", package = "sf")) %>% +#' st_transform(4326) %>% +#' sf_as_ee() +#' +#' ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ +#' filterDate("2016-01-01", "2016-01-31")$ +#' filterBounds(nc) +#' +#' ee_get_date_ic(ee_s2) +#' } +#' @export +ee_get_date_ic <- function(x, time_end = FALSE) { + # Call Python module + oauth_func_path <- system.file("python/ee_utils.py", package = "rgee") + ee_utils <- ee_source_python(oauth_func_path) + + # Fetch the time_start of each Image + time_start <- ee_utils_py_to_r( + ee_utils$eedate_to_rdate_ic(x, "system:time_start") + ) + + # Getting ID + image_id <- x$aggregate_array("system:id")$getInfo() + if (is.null(image_id)) { + image_id <- rep("no_id", x$size()$getInfo()) + } + if (length(image_id) == 0) { + image_id <- rep("no_id", x$size()$getInfo()) + } + + + if (is.null(time_start)) { + time_start <- NA + } else { + time_start <- as.POSIXct( + x = time_start / 1000, + origin = "1970-01-01", + tz = "GMT" + ) + } + + # Getting time_end + if (!time_end) { + return( + data.frame( + id = image_id, + time_start = time_start, + stringsAsFactors = FALSE + ) + ) + } else { + time_end <- ee_utils_py_to_r( + ee_utils$eedate_to_rdate_ic(x, "system:time_end") + ) + time_end <- as.POSIXct( + x = time_end / 1000, + origin = "1970-01-01", + tz = "GMT" + ) + data.frame( + id = image_id, + time_start = time_start, + time_end = time_end, + stringsAsFactors = FALSE + ) + } +} + +#' Get the path where the credentials are stored +#' +#' @family path utils +#' @return A character which represents the path credential of a specific +#' user +#' +#' @export +ee_get_earthengine_path <- function() { + oauth_func_path <- system.file("python/ee_utils.py", package = "rgee") + utils_py <- ee_source_python(oauth_func_path) + ee_path <- ee_utils_py_to_r(utils_py$ee_path()) + + sessioninfo <- sprintf( + "%s/rgee_sessioninfo.txt", + ee_utils_py_to_r(utils_py$ee_path()) + ) + if (file.exists(sessioninfo)) { + user <- read.table(sessioninfo, + header = TRUE, + stringsAsFactors = FALSE + )[[1]] + if (is.na(user)) { + stop("rgee_sessioninfo.txt malformed") + } + } else { + stop( + "rgee_sessioninfo.txt does not exist, ", + "run rgee::ee_Initialize() to fixed." + ) + } + return(sprintf("%s/%s/", ee_path, user)) +} diff --git a/README.md b/README.md index a8c3fc3d..77611e7a 100755 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Additionally, several functions have been implemented to make simple the connect ### Quick Start User's Guide for rgee in Portuguese and Spanish. - + **Created by:** - POR: Andres Luiz Lima Costa diff --git a/man/Map.Rd b/man/Map.Rd index 0bc5090a..18cc811b 100644 --- a/man/Map.Rd +++ b/man/Map.Rd @@ -9,7 +9,7 @@ An object of class environment with the following functions: \itemize{ \item \strong{addLayer(eeObject, visParams, name = NULL, shown = TRUE, -opacity = 1, legend = TRUE)}: Adds a given EE object to the map as a layer. \cr +opacity = 1, legend = FALSE)}: Adds a given EE object to the map as a layer. \cr \itemize{ \item \strong{eeObject:} The object to add to mapview.\cr \item \strong{visParams:} List of parameters for visualization. @@ -22,6 +22,20 @@ between 0 and 1. Defaults to 1. \cr \item \strong{legend:} Should a legend be plotted?. Ignore if \code{eeObject} is not a single-band ee$Image. } +\item \strong{addLayers(eeObject, visParams, name = NULL, shown = TRUE, +opacity = 1, legend = FALSE)}: Adds a given ee$ImageCollection to the map +as multiple layers. \cr +\itemize{ +\item \strong{eeObject:} The ee$ImageCollection to add to mapview.\cr +\item \strong{visParams:} List of parameters for visualization. +See details.\cr +\item \strong{name:} The name of layers.\cr +\item \strong{shown:} A flag indicating whether +layers should be on by default. \cr +\item \strong{opacity:} The layer's opacity represented as a number +between 0 and 1. Defaults to 1. \cr +\item \strong{legend:} Should a legend be plotted?. +} \item \strong{setCenter(lon = 0, lat = 0, zoom = NULL)}: Centers the map view at the given coordinates with the given zoom level. If no zoom level is provided, it uses 1 by default. @@ -149,6 +163,18 @@ m2 + mapview(nc) # Case 6: mapedit library(mapedit) # my_geometry <- m2 \%>\% ee_as_mapview() \%>\% editMap() + +# Case 7: ImageCollection +nc <- st_read(system.file("shape/nc.shp", package = "sf")) \%>\% + st_transform(4326) \%>\% + sf_as_ee() + +ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ + filterDate("2016-01-01", "2016-01-31")$ + filterBounds(nc) \%>\% + ee_get(0:4) +Map$centerObject(nc$geometry()) +Map$addLayers(ee_s2) } } \keyword{datasets} diff --git a/man/ee_get.Rd b/man/ee_get.Rd new file mode 100644 index 00000000..4510179c --- /dev/null +++ b/man/ee_get.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ee_get.R +\name{ee_get} +\alias{ee_get} +\title{Return the element at the specified position in a Earth Engine Collection} +\usage{ +ee_get(ee_c, index = 0) +} +\arguments{ +\item{ee_c}{ImageCollection or FeatureCollection.} + +\item{index}{Numeric. Specified position.} +} +\value{ +Depending of \code{ee_c} can return either an ee$FeatureCollection +or ee$ImageCollection. +} +\description{ +Return the element at the specified position in a Earth Engine Collection +} +\examples{ +\dontrun{ +library(rgee) +library(sf) + +ee_Initialize() + +nc <- st_read(system.file("shape/nc.shp", package = "sf")) \%>\% + st_transform(4326) \%>\% + sf_as_ee() + +ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ + filterDate("2016-01-01", "2016-01-31")$ + filterBounds(nc) + +ee_s2$size()$getInfo() # 126 + +# Get the first 5 elements +ee_get(ee_s2, index = 0:4)$size()$getInfo() # 5 +} +} diff --git a/man/ee_get_assethome.Rd b/man/ee_get_assethome.Rd index 7478702e..4c97ca2b 100644 --- a/man/ee_get_assethome.Rd +++ b/man/ee_get_assethome.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_Initialize.R +% Please edit documentation in R/ee_get.R \name{ee_get_assethome} \alias{ee_get_assethome} \title{Get the Asset home name} diff --git a/man/ee_get_date_ic.Rd b/man/ee_get_date_ic.Rd index 7bb8c764..43b0d75d 100644 --- a/man/ee_get_date_ic.Rd +++ b/man/ee_get_date_ic.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_Date.R +% Please edit documentation in R/ee_get.R \name{ee_get_date_ic} \alias{ee_get_date_ic} \title{Get the date of a EE ImageCollection} diff --git a/man/ee_get_date_img.Rd b/man/ee_get_date_img.Rd index cbb2bbc7..3a7b2e18 100644 --- a/man/ee_get_date_img.Rd +++ b/man/ee_get_date_img.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_Date.R +% Please edit documentation in R/ee_get.R \name{ee_get_date_img} \alias{ee_get_date_img} \title{Get the date of a EE Image} diff --git a/man/ee_get_earthengine_path.Rd b/man/ee_get_earthengine_path.Rd index 7bcb961b..090b14c8 100644 --- a/man/ee_get_earthengine_path.Rd +++ b/man/ee_get_earthengine_path.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ee_Initialize.R +% Please edit documentation in R/ee_get.R \name{ee_get_earthengine_path} \alias{ee_get_earthengine_path} \title{Get the path where the credentials are stored} diff --git a/tests/testthat/test-ee_get.R b/tests/testthat/test-ee_get.R new file mode 100644 index 00000000..2d947ec4 --- /dev/null +++ b/tests/testthat/test-ee_get.R @@ -0,0 +1,29 @@ +context("rgee: ee_extract test") +skip_if_no_pypkg() +# ------------------------------------------------------------------------- + +test_that("ee_get images",{ + nc <- sf::st_read(system.file("shape/nc.shp", package = "sf")) %>% + sf::st_transform(4326) %>% + sf_as_ee() + + ee_s2 <- ee$ImageCollection("COPERNICUS/S2")$ + filterDate("2016-01-01", "2016-01-31")$ + filterBounds(nc) + + expect_equal(ee_get(ee_s2, index = 0:4)$size()$getInfo(), 5) +}) + +test_that("ee_get features",{ + fc <- ee$FeatureCollection(lapply(1:10, function(x) ee$Feature(NULL,list(index=x)))) + expect_equal(ee_get(fc, index = 0:4)$size()$getInfo(), 5) +}) + +test_that("ee_get error",{ + expect_error(ee_get(0:4)) +}) + +test_that("ee_get error", { + fc <- ee$FeatureCollection(lapply(1:10, function(x) ee$Feature(NULL,list(index=x)))) + expect_error(ee_get(fc, index = 4:0)) +}) From 332f4fcd273b3069f86f627d70426ae8ae3f32eb Mon Sep 17 00:00:00 2001 From: Cesar Luis Aybar Camacho Date: Tue, 14 Jul 2020 16:22:28 -0500 Subject: [PATCH 15/15] GH actions changes --- .github/workflows/R-CMD-check.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index cacf0f86..da2a2d07 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,10 +1,10 @@ on: push: branches: - - JOSS + - master pull_request: branches: - - JOSS + - master name: R-CMD-check