Skip to content

Commit f39411f

Browse files
authored
Fix some holes in era5 functions. (#1716)
* Add CDSAPI secret * Update era5 tests * Fix some holes in era5 functions. * Fixes
1 parent b5b7193 commit f39411f

6 files changed

Lines changed: 50 additions & 34 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ jobs:
106106
${{ runner.os }}-test-${{ env.cache-name }}-
107107
${{ runner.os }}-test-
108108
${{ runner.os }}-
109+
- name: Configure API Keys 🔒
110+
shell: bash
111+
run: |
112+
echo "url: https://cds.climate.copernicus.eu/api" > ~/.cdsapirc
113+
echo "key: ${{ secrets.CDSAPI_KEY }}" >> ~/.cdsapirc
109114
- uses: julia-actions/julia-buildpkg@v1
110115
- uses: julia-actions/julia-runtest@v1
111116
- uses: julia-actions/julia-processcoverage@v1

src/extras/weather.jl

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,19 @@ This function retrieves data from the Climate Data Store (CDS) (https://cds.clim
9292
- `params`: A JSON string containing the request parameters. This string should be in the format expected
9393
by the CDSAPI. When using input via this option the `dataset` option is mandatory.
9494
If you feel brave, you can create the request parametrs yourself and pass them as a two elements string
95-
vector with the output of the ``era5vars()`` and ``era5time()`` functions. In this case, a region selection,
96-
if desired, must be provided via the `region` option that has the same syntax in all other GMT.jl modules
97-
that use it, _e.g._ the ``coast`` function.
95+
vector with the output of the ``era5vars()`` and ``era5time()`` functions. In this case, a region selection
96+
and pressure levels, if desired, must be provided via the `region` and `pressure` options. The `region`
97+
option has the same syntax in all other GMT.jl modules that use it, _e.g._ the ``coast`` function.
9898
- `key`: The API key for the CDSAPI server. Default is the value in the ``.cdsapirc`` file in the home directory.
9999
but if that file does not exist, the user can provide the `key` and `url` as arguments. Instructions on how
100100
to create the ``.cdsapirc`` file for your user account can be found at https://cds.climate.copernicus.eu/how-to-api
101101
- `url`: The URL of the CDS API server. Default is https://cds.climate.copernicus.eu/api
102+
- `pressure`: List of pressure levels to retrieve. It can be a string to select a unique level, or a vector
103+
of strings or Ints to select multiple levels. But it can also be a range of levels, e.g. "1000:-100:500".
104+
This option is only used when the `params` argument is provided as a string vector.
102105
- `region`: Specify a region of a specific geographic area. It can be provided as a string with form "N/W/S/E"
103106
or a 4-element vector or tuple with numeric data. This option is only used when the `params` argument is
104-
provided as a two elements string vector.
107+
provided as a string vector.
105108
- `format`: The format of the data to download. Default is "netcdf". Other options is "grib".
106109
- `debug`: A boolean indicating whether to print the `params` from the outputs of the `era5vars()` and
107110
`era5time()` functions. I this case, we just print the `params` and return without trying to download any file.
@@ -139,7 +142,7 @@ datetime = era5time(hour=10:14);
139142
era5(dataset="reanalysis-era5-land", params=[var, datetime], region=(-10, 0, 30, 45))
140143
```
141144
"""
142-
function era5(reanalysis::Symbol=:reanalysis; filename="", cb::Bool=false, dataset="", params::Union{AbstractString, Vector{String}}="", key::String="", url::String="", wait=1.0, region="", format="netcdf", debug::Bool=false, verbose::Bool=true)
145+
function era5(reanalysis::Symbol=:reanalysis; filename="", cb::Bool=false, dataset="", params::Union{AbstractString, Vector{String}}="", key::String="", url::String="", wait=1.0, pressure="", region="", format="netcdf", debug::Bool=false, verbose::Bool=true)
143146

144147
function cdsapikey()::Tuple{String, String}
145148
# Get the API key and URL from the ~/.cdsapirc file
@@ -185,6 +188,7 @@ function era5(reanalysis::Symbol=:reanalysis; filename="", cb::Bool=false, datas
185188
split(readlines(io)[1], ',')
186189
end
187190
end
191+
# ======================== End of nested functions ========================
188192

189193
if (key == "")
190194
KEY, URL = cdsapikey()
@@ -201,8 +205,15 @@ function era5(reanalysis::Symbol=:reanalysis; filename="", cb::Bool=false, datas
201205
else
202206
if isa(params, Vector)
203207
params = join(params, '\n')
208+
if (pressure != "") # Pressure levels are provided
209+
pr = getdtp(pressure, "1000"); (pr == "e") && error("Unknown type for 'pressure'")
210+
sp = @sprintf("\"pressure_level\": [\"%s\"],\n", pr)
211+
sp = replace(sp, "[\"[" => "["); sp = replace(sp, "]\"]" => "]"); # Remove double [[ & ]]
212+
params *= sp
213+
end
204214
params *= (format == "netcdf") ? "\"data_format\": \"netcdf\",\n" : "\"data_format\": \"grib\",\n"
205-
params *= "\"download_format\": \"unarchived\",\n"
215+
last = (region == "") ? "\n" : ",\n" # Having an extra comma at the end of the line is a json syntax error
216+
params *= "\"download_format\": \"unarchived\"" * last
206217
if (region != "") # The region is provided by parse_R() as a string like " -R58/6/55/9" (N/W/S/E)
207218
optR = split(parse_R(Dict(:R => region), "")[1], '/')
208219
params *= "\"area\": [" * optR[1][4:end] * ", " * optR[4] * ", " * optR[2] * ", " * optR[3] * "]\n"
@@ -216,14 +227,15 @@ function era5(reanalysis::Symbol=:reanalysis; filename="", cb::Bool=false, datas
216227
(dataset == "" && _dataset != "") && (dataset = _dataset)
217228

218229
s = curl_post(URL * "/retrieve/v1/processes/$dataset/execute", body, KEY)
219-
st_line = findfirst(startswith.(s,"\"status"))
220-
status = s[st_line][11:end-1] # It has the form "{\"status\":\"accepted\""
221-
if (contains(s[st_line], ":4"))
230+
ind = findfirst(startswith.(s,"\"status"))
231+
(ind === nothing) && throw(ArgumentError("The request was not accepted, probably a malformed one. Check it the 'debug' option."))
232+
status = s[ind][11:end-1] # It has the form "{\"status\":\"accepted\""
233+
if (contains(s[ind], ":4"))
222234
ind = findfirst(startswith.(s,"\"title"))
223235
throw(ArgumentError(split(s[ind], ':')[2][2:end-1])) # It has the form "\"title\":\"Autentication failed\""
224236
end
225-
ep_line = findfirst(startswith.(s,"{\"href"))
226-
endpoint = s[ep_line][10:end-1] # It has the form "{\"href\":\"https://cds.climate...\""
237+
ind = findfirst(startswith.(s,"{\"href"))
238+
endpoint = s[ind][10:end-1] # It has the form "{\"href\":\"https://cds.climate...\""
227239
while (status != "successful")
228240
s = curl_get(endpoint, KEY)
229241
st_line = findfirst(startswith.(s,"\"status"))
@@ -347,7 +359,7 @@ This function returns a JSON formatted string that can be used as an input to th
347359
It can also be a range of hours, e.g. "01:10".
348360
349361
### Returns
350-
A string with the JSON formatted time.
362+
A string with the JSON formatted date-time.
351363
352364
### Example
353365
```julia
@@ -356,19 +368,19 @@ var = era5time(year="2023")
356368
```
357369
"""
358370
function era5time(; year="", month="", day="", hour="")
359-
function getdt(x, def)
360-
(x == "") ? def : (typeof(x) <: OrdinalRange) ? string.(collect(x)) : isa(x, Vector{Int}) ? string.(x) : isa(x, Vector{String}) ? x : "e"
361-
end
362-
363371
_y, _m, _d, _h = agora()
364-
yr = (year == "all") ? string(collect(2000:parse(Int, _y))) : getdt(year, _y); (yr == "e") && error("Unknown type for 'year'")
365-
mo = (month == "all") ? string(collect(1:12)) : getdt(month, _m); (mo == "e") && error("Unknown type for 'month'")
366-
dy = (day == "all") ? string(collect(1:30)) : getdt(day, _d); (dy == "e") && error("Unknown type for 'day'")
367-
hr = (day == "all") ? string(collect(0:23)) : getdt(hour, _h); (hr == "e") && error("Unknown type for 'hour'")
372+
yr = getdtp(year, _y); (yr == "e") && error("Unknown type for 'year'")
373+
mo = getdtp(month, _m); (mo == "e") && error("Unknown type for 'month'")
374+
dy = getdtp(day, _d); (dy == "e") && error("Unknown type for 'day'")
375+
hr = getdtp(hour, _h); (hr == "e") && error("Unknown type for 'hour'")
368376
s = @sprintf("\"year\": [\"%s\"],\n\"month\": [\"%s\"],\n\"day\": [\"%s\"],\n\"time\": [\"%s\"],\n", yr, mo, dy, hr)
369377
s = replace(s, "[\"[" => "["); s = replace(s, "]\"]" => "]"); # Remove double [[ & ]]
370378
return s
371379
end
380+
381+
function getdtp(x, def) # used also in era5() to get the pressure levels
382+
(x == "") ? def : (typeof(x) <: OrdinalRange) ? string.(collect(x)) : isa(x, Vector{Int}) ? string.(x) : isa(x, Vector{String}) ? x : "e"
383+
end
372384

373385
function agora() # Must put this in a separate function because I want to use the keywords year, month, etc
374386
t = now()

test/runtests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ using InteractiveUtils
1717
API = GMT.GMT_Create_Session("GMT", 2, GMT.GMT_SESSION_NOEXIT + GMT.GMT_SESSION_EXTERNAL);
1818
GMT.GMT_Get_Ctrl(API);
1919

20+
include("test_avatars.jl")
21+
include("test_misc.jl")
2022
println(" Entering: test_gd_ext.jl")
2123
include("test_gd_ext.jl")
2224
println(" Entering: test_gdal.jl")
@@ -51,8 +53,6 @@ using InteractiveUtils
5153
@warn("Failed the WMS test. Error was:\n $err")
5254
end
5355

54-
include("test_avatars.jl")
55-
include("test_misc.jl")
5656
include("test_isoutlier.jl")
5757
include("test_okadas.jl")
5858
include("test_findpeaks.jl")

test/test_PSs.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ t = ["\tIt was the best of times, it was the worst of times, it was the age of w
198198
"",
199199
"\tThere were a king with a large jaw and a queen with a plain face,"];
200200
T = text_record(t,"> 3 5 18p 5i j");
201-
@info "3..."
202201
pstext!(T, F="+f16p,Times-Roman,red+jTC", M=true)
203202
pstext!(T, font=(16,"Times-Roman",:red), justify=:TC, M=true)
204203
pstext!(["MERDA"], x=2.0, y=2.0, Vd=dbg2)

test/test_makecpts.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@
66
@test_throws ErrorException("E option requires that a data table is provided as well") makecpt(E="", C=:rainbow)
77
cpt = makecpt(range="-1/1/0.1");
88
cpt = makecpt(-1,1,0.1);
9-
println(" MAKECPT - 0")
109
#C = cpt4dcw("eu");
1110
C = cpt4dcw("PT,ES,FR", [3., 5, 8], range=[3,9,1]);
1211
C = cpt4dcw("PT,ES,FR", [.3, .5, .8], cmap=cpt);
13-
println(" MAKECPT - 1")
1412
@test_throws ErrorException("Unknown continent ue") cpt4dcw("ue")
1513
GMT.iso3to2_eu();
1614
GMT.iso3to2_af();
17-
println(" MAKECPT - 2")
1815
GMT.iso3to2_na();
1916
GMT.iso3to2_world();
2017
GMT.mk_codes_values(["PRT", "ESP", "FRA"], [1.0, 2, 3], region="eu");

test/test_misc.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,10 @@
123123
setindex!(I, [101 1],1:2)
124124
I .+ UInt8(0);
125125

126-
@info "linearfitxy"
127126
GMT.resetGMT()
128127
D = linearfitxy([0.0, 0.9, 1.8, 2.6, 3.3, 4.4, 5.2, 6.1, 6.5, 7.4], [5.9, 5.4, 4.4, 4.6, 3.5, 3.7, 2.8, 2.8, 2.4, 1.5], sx=1 ./ sqrt.([1000., 1000, 500, 800, 200, 80, 60, 20, 1.8, 1]), sy=1 ./ sqrt.([1., 1.8, 4, 8, 20, 20, 70, 70, 100, 500]));
129128
plot(D, linefit=true, band_ab=true, band_CI=true, ellipses=true, Vd=dbg2);
130129
plot!(D, linefit=true, Vd=dbg2)
131-
@info "ablines"
132130
ablines!(D, Vd=dbg2)
133131
ablines!(0,1, Vd=dbg2)
134132
ablines!([1, 2, 3], [1, 1.5, 2], linecolor=[:red, :orange, :pink], linestyle=:dash, linewidth=2, Vd=dbg2)
@@ -151,8 +149,9 @@
151149
D[:Time];
152150
D["Time", "b"];
153151
try
154-
display(D); # It seems the pretty tables solution has an Heisenbug.
155-
catch
152+
display(D); # It seems the pretty tables solution has an Heisenbug.
153+
catch e
154+
println(e)
156155
end
157156
plot(D, legend=true, Vd=dbg2);
158157
mat2ds(rand(5,4), x=:ny, color=:cycle, hdr=" -W1");
@@ -265,7 +264,7 @@
265264
GMT.zscale(0:9999)
266265

267266
# Orbits
268-
println(" Orbits")
267+
println(" ORBITS")
269268
@test_throws ErrorException("Only Orthographic projection is allowed.") orbits!();
270269
@test_throws ErrorException("Only Orthographic projection is allowed.") orbits!(mat2ds(rand(10,3)));
271270
orbits()
@@ -299,15 +298,19 @@
299298
"download_format": "unarchived",
300299
"area": [58, 6, 55, 9]
301300
}"""
302-
@test_throws ArgumentError era5(dataset=dataset, params=request, key="blabla");
303-
if !Sys.isunix() # The Linux CI fails saying they don't have clipboard installed
301+
#@test_throws ArgumentError era5(dataset=dataset, params=request, key="blabla");
302+
era5(dataset=dataset, params=request);
303+
#if !Sys.isunix() # The Linux CI fails saying they don't have clipboard installed
304+
try # Because the Linux CI fails saying they don't have clipboard installed
304305
clipboard(request)
305306
@test_throws ArgumentError era5(cb=true, dataset=dataset, key="blabla");
307+
catch e
308+
println(e)
306309
end
307310
listera5vars(contain="Temperature", test=true)
308311
var = era5vars(["t2m", "skt"]); # "t2m" is the 2m temperature and "skt" is the skin temperature
309312
dt = era5time(hour=10:14);
310-
@test_throws ArgumentError era5(dataset="reanalysis-era5-land", params=[var, dt], region=(-10, 0, 30, 45), key="blabla")
313+
@test_throws ArgumentError era5(dataset="reanalysis-era5-land", params=[var, dt], pressure=[1000, 900], region=(-10, 0, 30, 45), key="blabla")
311314

312315
# MB-System
313316
println(" MB-System")

0 commit comments

Comments
 (0)