-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCMEMS_interface.r
416 lines (372 loc) · 17.5 KB
/
CMEMS_interface.r
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#' Get configuration from CMEMS command-line script
#'
#' Extracts the available details from a download script provided by CMEMS and uses it
#' to create an CMEMS.config object
#'
#' @export
#' @param script A text string for a download script provided by the CMEMS website
#' @param parse.user A logical variable indicating whether the username and password should be parsed
#' or not. Typically, these are place holder values, so it's not really necessary to parse them.
#' @details The CMEMS website has subsetting functionality that can be used as a template generate a download script for use with the motu
#' client (usually accessed by clicking on the "View Script" button when preparing to download). This function
#' saves the hard work of having to figure out the parameters for use with the \code{RCMEMS} package by parsing this command and
#' extracting the relevant information.
#' @examples
#' #Using a script automatically generated by CMEMS
#' script <- 'python -m motuclient --motu http://my.cmems-du.eu/motu-web/Motu
#' --service-id SST_GLO_SST_L4_REP_OBSERVATIONS_010_011-TDS
#' --product-id METOFFICE-GLO-SST-L4-RAN-OBS-SST --longitude-min -179.97500610351562
#' --longitude-max 179.97500610351562 --latitude-min -89.9749984741211
#' --latitude-max 89.9749984741211 --date-min "2007-12-26 12:00:00"
#' --date-max "2007-12-31 12:00:00" --variable analysed_sst
#' --variable analysis_error --out-dir <OUTPUT_DIRECTORY>
#' --out-name <OUTPUT_FILENAME> --user <USERNAME> --pwd <PASSWORD> '
#' cfg <- parse.CMEMS.script(script)
#' cfg
parse.CMEMS.script <- function(script,parse.user=FALSE){
#Mapping between command line args and CMEMS.config
#Note that we only focus here on arguments supplied by a double "--", as this is easier to handle
arg.map <- list(module="-m",
user="--user",
pwd="--pwd",
motu="--motu",
service.id="--service-id",
product.id="--product-id",
longitude.min="--longitude-min",
longitude.max="--longitude-max",
latitude.min="--latitude-min",
latitude.max="--latitude-max",
date.min="--date-min",
date.max="--date-max",
depth.min="--depth-min",
depth.max="--depth-max",
out.dir="--out-dir",
out.name="--out-name")
#Break input script into its individual elements
script.atoms <- scan(text=script,what="character",quiet=TRUE)
#Now do the extraction by looping over the list of argument names that we want to extract
#Find their location in the list, then extract the next element as the argument
argl <- list()
for(a in names(arg.map)){
arg.idx <- which(script.atoms==arg.map[[a]])
if(length(arg.idx)==1) {
argl[[a]] <- script.atoms[arg.idx+1]
} else if(length(arg.idx)>1) warning(sprintf('Multiple matches for "%s" argument. None selected.',arg.map[[a]]))
}
#Handle the client configuration elements
argl$python <- gsub("^(.*?) .*$","\\1",script)
if(is.null(argl$module)) {
argl$script <- gsub("^.*? (.*?) .*$","\\1",script)
} else {
argl$module <- NULL
}
#Handle variables explicitly, to deal with cases where
var.idxs <- which(script.atoms=="--variable")
argl$variable <- script.atoms[var.idxs+1]
#Set the options
cfg <- do.call(CMEMS.config,argl)
#Set user and password to null
if(!parse.user) {
cfg@user <- as.character(NULL)
cfg@pwd <- as.character(NULL)
}
return(cfg)
}
#' Download from CMEMS
#'
#' Provides R interfaces to the Python-based Motu client developed to provide access to
#' CMEMS data. Two interfaces are provided - the \code{CMEMS.download} interface is a simple
#' interface taking advantage of R classes, while the \code{\link{CMEMS.download.advanced}}
#' interface provides access to the full functionality of the motu client.
#' @seealso Details and documentation on the Motu client, including releases of the software
#' to download, can be found on the associated GitHub site, \url{https://github.com/clstoulouse/motu-client-python}
#' @name CMEMS.download
#' @export
#' @param x An object of class \code{\link{CMEMS.config}} containing the configuration parameters
#' @param ROI An vector of length 4 specifying the region of interest in the following order:
#' minimum longitude, maximum longitude, minimum latitude, maximum latitude. Note that this
#' structure mirrors that of the \code{raster::extent()} class and these can also be used
#' directly here instead.
#' @param date.range The maximum and minimum dates (vector of length two of class "Date")
#' @param depth.range The maximum and minimum depths specified as a vector of length 2 (float in the interval [0 ; 2e31 ] -
#' does not accept 'Surface' as an argument)
#' @param ... Arguments to be passed on further to the \code{\link{CMEMS.download.advanced}} function. Overwrites any arguments automatically generated
#' in \code{\link{CMEMS.download}} function.
#' @param date.min The min date with optional hour resolution (string following format YYYY-MM-DD [HH:MM:SS])
#' @param date.max The max date with optional hour resolution (string following format YYYY-MM-DD [HH:MM:SS ])
#' @param latitude.min The min latitude (float in the interval [-90 ; 90 ])
#' @param latitude.max The max latitude (float in the interval [-90 ; 90 ])
#' @param longitude.min The min longitude (float in the interval [-180 ; 180 ])
#' @param longitude.max The max longitude (float in the interval [-180 ; 180 ])
#' @param depth.min The min depth (float in the interval [0 ; 2e31 ] or string 'Surface')
#' @param depth.max The max depth (float in the interval [0 ; 2e31 ] or string 'Surface')
#' @param out.dir The output dir (string)
#' @param out.name The output file name (string)
#' @param out.path The output path, including the directory and filename in one string.
#' @param quiet Logical value, indicating whether to supress output
#' @param debug Allows debugging of the motu client command - builds the command without running it (logical)
#' @details Arguments provided to \code{\link{CMEMS.download}} and \code{\link{CMEMS.download.advanced}} override
#' any arguments supplied in the \code{\link{CMEMS.config}} object, x.
#' @details If the \code{\link{CMEMS.config}} object, x, is missing either the username or the
#' password, both are dropped from the call to motuclient - in this case, the client will
#' use the local configuration file. See the README.md file supplied with motuclient for how
#' to set this up.
#' @return If debug is TRUE, returns the full command to the motu client, ready to be run (via \code{system()}) or checked manually. If
#' debug is FALSE (the default), runs the command and returns the error code associated with the motuclient.
#' @examples
#' \dontrun{
#' #Setup a configuration object, using OSTIA as an example
#' cfg <- CMEMS.config(motu="http://my.cmems-du.eu/motu-web/Motu",
#' service.id = "SST_GLO_SST_L4_REP_OBSERVATIONS_010_011-TDS",
#' product.id = "METOFFICE-GLO-SST-L4-RAN-OBS-SST",
#' variable = c("analysed_sst","analysis_error"))
#' CMEMS.download(cfg,
#' ROI = c(8,13,55,59),
#' date.range = c(ISOdate(2001,08,01),ISOdate(2001,08,10)),
#' out.path="test.nc",
#' debug=FALSE)
#'}
CMEMS.download <- function(x,
ROI="missing",
date.range="missing",
out.path="missing",
depth.range="missing",
...) {
#Build spatial ROI arguments
if(missing("ROI")) {
ROI.args <- sapply(c("longitude.min","longitude.max","latitude.min","latitude.max"),
slot,object=x,simplify=FALSE)
} else { #Take it from the function argument
ROI.args <- as.list(structure(ROI[1:4],
names=c("longitude.min","longitude.max","latitude.min","latitude.max")))
}
#Build date variables
if(missing(date.range)){
date.arg <- list([email protected],[email protected])
#Ensure that we have the quote marks right here by first stripping, then reapplying
date.arg <- lapply(date.arg,function(s) sprintf('"%s"',gsub('"',"",s)))
} else {
date.arg <- as.list(structure(format(range(date.range),'"%Y-%m-%d %H:%M:%S"'),
names=c("date.min","date.max")))
}
#Build depth variables, if supplied
if(missing(depth.range)) {
depth.args <- list([email protected],[email protected])
} else if(!is.numeric(depth.range)) {
stop("'depth.range' requires a numeric argument. To use the 'Surface' argument, please use the depth.min and
depth.max arguments of CMEMS.advanced.")
} else {
depth.args <- list(depth.min=min(depth.range),depth.max=max(depth.range))
}
#Split out.path into separate file names and directories
if(missing(out.path)) {
path.arg <- list([email protected],[email protected])
} else {
path.arg <- list(out.dir=dirname(out.path),out.name=basename(out.path))
}
#Combine all argument and use the ... to allow additional argument or overwriting
arg.l <- c(list(x=x),ROI.args,date.arg,depth.args,path.arg)
dot.l <- list(...)
arg.l[names(dot.l)] <- dot.l
#Do the call
rtn <- do.call(CMEMS.download.advanced,arg.l)
return(rtn)
}
#' @export
#' @rdname CMEMS.download
CMEMS.download.advanced <- function(x,
out.dir=NULL,
out.name=NULL,
date.min=NULL,
date.max=NULL,
latitude.min=NULL,
latitude.max=NULL,
longitude.min=NULL,
longitude.max=NULL,
depth.min=NULL,
depth.max=NULL,
quiet=FALSE,
debug=FALSE) {
#Extract the rest of the options from the CMEMS.config object to be build into a command
slts.rest <- slotNames(x)[-which(slotNames(x) %in% c("python","script","variable"))]
cfg.l <- lapply(slts.rest,function(n) slot(x,n))
names(cfg.l) <- slts.rest
#Check whether both the username and the password have been supplied.
#If not, drop these from the arguments to the command - this should
#encourage the client to pick up the names from the local configuration file
if(length(x@user)==0 | length(x@pwd)==0 ) {
if(!quiet) {
message("RCMEMS: Username or password is missing - using local configuration file instead.")
}
cfg.l[c("user","pwd")] <- NULL
}
#Now take the rest of the configurations from this call and
#populate it from the local enviroment
this.arg.names <- formalArgs(CMEMS.download.advanced)
this.arg.names <- subset(this.arg.names,!(this.arg.names %in% c("x","debug","quiet")))
this.argl <- lapply(this.arg.names,function(n) get0(n))
names(this.argl) <- this.arg.names
#The dates need special handling to ensure that we get the quotes right
if(!is.null(date.min)) {
this.argl[["date.min"]] <- sprintf('"%s"',date.min)
}
if(!is.null(date.max)) {
this.argl[["date.max"]] <- sprintf('"%s"',date.max)
}
#Compile into a single list (allowing for overwriting from the command line)
all.motu.args.l <- cfg.l
all.motu.args.l[names(this.argl)] <- this.argl
#Drop the nulls, then build a list of arguments
motu.args.l <- all.motu.args.l[sapply(all.motu.args.l,length)!=0]
names(motu.args.l) <- gsub("\\.","-",names(motu.args.l))
args.fmt <- sprintf("--%s=%s",names(motu.args.l),motu.args.l)
#Handle variables specifically - problematic when dealing with multiple variables
args.fmt <- c(args.fmt,paste("--variable",x@variable))
#Add quietly, if necessary
if(quiet) {args.fmt <- c(args.fmt,"--quiet")}
#Decide how to run it
if(is.na(x@script) ) {
client.cmd <- "-m motuclient"
} else {
client.cmd <- x@script
}
#Run it
if(!debug) {
err.code <- system2(command=x@python,
args=c(client.cmd,args.fmt))
if(err.code!=0) stop("Error in running CMEMS download command.")
return(err.code)
} else{
return(list(command=x@python, args=c(client.cmd,args.fmt)))
}
}
#' Get product details
#'
#' Requests details from the CMEMS servers about a specific product, including temporal coverage
#' and depth layers
#'
#' @export
#'
#' @param x An object of class \code{\link{CMEMS.config}} containing the configuration
#' parameters. At a minimum, the \code{python}, \code{script}, \code{motu}, \code{service.id}
#' and \code{product.id} slots need to be populated.
#' @param variable Specifies the object to return. Valid values are "times" and
#' "depths". All other values, including omission, return an xml object describing the product.
#' @param quiet Logical, indicating whether to supress output
#' @details This function returns the description that is associated with the particular product
#' id. The depths result is self explanatory, but the times result can be hard to work with, as it
#' is encoded in ISO 8061 format. See \url{https://github.com/clstoulouse/motu#describe-product}
#' for details.
#' @examples
#' \dontrun{
#' #Setup a configuration object, using OSTIA as an example
#' cfg <- CMEMS.config(motu="http://my.cmems-du.eu/motu-web/Motu",
#' service.id = "SST_GLO_SST_L4_REP_OBSERVATIONS_010_011-TDS",
#' product.id = "METOFFICE-GLO-SST-L4-RAN-OBS-SST",
#' variable = c("analysed_sst","analysis_error"))
#' #Time description is in ISO 8061 format
#' product.description(cfg,"times")
#' }
product.description <- function(x,variable="missing",quiet=TRUE) {
#Check the configuration is sane
check.motuclient(x)
#Extract the rest of the options from the CMEMS.config object to be build into a command
extract.these <- c("motu","service.id","product.id")
extract.l <- lapply(extract.these,function(n) slot(x,n))
names(extract.l) <- gsub("\\.","-",extract.these)
#Build a list of arguments
xml.fname <- tempfile(fileext=".xml")
args.fmt <- c(sprintf("--%s=%s",names(extract.l),extract.l),
sprintf("--out-dir=%s",dirname(xml.fname)),
sprintf("--out-name=%s",basename(xml.fname)))
if(length(x@user)!=0 & length(x@pwd)!=0) {
args.fmt <- c(args.fmt,
sprintf("--user=%s",x@user),
sprintf("--pwd=%s",x@pwd))
}
#Add quietly, if necessary
if(quiet) {args.fmt <- c(args.fmt,"--quiet")}
#Decide how to run it
if(is.na(x@script)) {
client.cmd <- "-m motuclient"
} else {
client.cmd <- x@script
}
#Retrieve description
err.code <- system2(x@python,args=c(client.cmd,args.fmt,"--describe-product"))
if(err.code!=0) stop("Error in retrieving CMEMS product description")
#Now import the xml (if available)
if(requireNamespace("xml2",quietly=TRUE)) {
xml.obj <- xml2::read_xml(xml.fname)
} else {
warning("This function requires the xml2 package to be installed. Returning a copy of the xml file.")
return(readLines(xml.fname))
}
#Return
valid.vars <- c("times"="availableTimes",
"depths"="availableDepths")
if(variable %in% names(valid.vars)) { #Return the xml
txt <- xml2::xml_text(xml2::xml_child(xml.obj,valid.vars[variable]))
rtn <- scan(text=txt,sep=";",what=character(),quiet = TRUE)
} else { #Return xml
rtn <- xml.obj
}
return(rtn)
}
#' Motuclient version numbers
#'
#' Checks that the installed motuclient is working and checks its version number.
#'
#' @name motuclient
#' @param x An object of class \code{\link{CMEMS.config}} containing the configuration
#' parameters. At a minimum, the \code{python} and \code{script} slots need to be populated.
#' If missing, the default \code{\link{CMEMS.config}} object is used.
#'
#' @return \code{get.motuclient.version()} returns the raw version string from the motuclient
#' @export
#'
#' @examples
#' \dontrun{
#' get.motuclient.version()
#' }
get.motuclient.version <- function(x="missing") {
if(missing(x)) {
x <- CMEMS.config()
}
#Decide how to run it
if(is.na(x@script)) {
client.cmd <- "-m motuclient"
} else {
client.cmd <- x@script
}
rtn <- system2(command=x@python, args=c(client.cmd,"--version"),
stdout=TRUE,
stderr=TRUE)
return(rtn)
}
#' @export
#' @rdname motuclient
#' @details \code{check.motuclient()}) first checks that the motuclient can be accessed and run.
#' It then checks the version number of this installed version against the minimum requirement
#' for this package (currently motuclient 1.8.4) and throws an error accordingly.
check.motuclient <- function(x) {
#Set default
if(missing(x)) {
x <- CMEMS.config()
}
#Set minimum version. Ideally we should also have a maximum version, e.g. if there is a new release
min.version="1.8.4"
#Check that system is alive
ver.str.full <- try(get.motuclient.version(x))
if(is(ver.str.full,"try-error")) {
stop("Cannot contact motuclient. Check configuration and try again.")
}
#Get version number
ver.str <- gsub("^.*?([0-9]*\\.*[0-9]*\\.*[0-9]*)$","\\1",ver.str.full)
valid.version <- compareVersion(ver.str,min.version) >= 0
if(!valid.version) {
stop(sprintf("RCMEMS does not support this version of motuclient. Installed version is %s. Min requirement for RCMEMS %s.",
ver.str,
min.version))
}
}