File size: 12,588 Bytes
6ee917b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

/// Define functions to download surface and pressure level variables for ICON
protocol IconVariableDownloadable: GenericVariable {
    func skipHour(hour: Int, domain: IconDomains, forDownload: Bool, run: Timestamp) -> Bool
    var multiplyAdd: (multiply: Float, add: Float)? { get }
    func getVarAndLevel(domain: IconDomains) -> (variable: String, cat: String, level: Int?)?
}

extension IconSurfaceVariable: IconVariableDownloadable {
    /// Vmax and precip always are empty in the first hour. Weather codes differ a lot in hour 0.
    func skipHour(hour: Int, domain: IconDomains, forDownload: Bool, run: Timestamp) -> Bool {
        if self == .direct_radiation && domain == .iconEps && hour % 3 != 0 {
            // ICON-EPS only has 3-hourly data for direct radiation
            return true
        }
        if domain == .iconEuEps &&
            [.wind_u_component_80m, .wind_v_component_80m, .temperature_80m].contains(self) &&
            [57, 63, 69].contains(hour) {
            // Upper levels have fewer timestamps
            return true
        }
        if domain == .iconEuEps &&
            [Self.snowfall_convective_water_equivalent, .snowfall_water_equivalent].contains(self) &&
            [6,18].contains(run.hour) &&
            hour % 6 != 0 {
            return true
        }
        // only 6h pressure in icon
        if domain == .iconEps && self == .pressure_msl && hour % 6 != 0 {
            return true
        }
        // only 6h cape in icon-eu for 6 and 18z run
        if domain == .iconEuEps && self == .cape && [6,18].contains(run.hour) && (hour % 6 != 0 || hour == 0) {
            return true
        }
        if hour != 0 {
            return false
        }
        // download hour0 from ICON-D2, because it still contains 15 min data
        if forDownload && domain == .iconD2 && self != .weather_code {
            return false
        }
        
        switch self {
        case .wind_gusts_10m: return true
        case .sensible_heat_flux: return true
        case .latent_heat_flux: return true
        case .direct_radiation: return true
        case .diffuse_radiation: return true
        case .weather_code: return true
        case .snowfall_water_equivalent: fallthrough
        case .snowfall_convective_water_equivalent: fallthrough
        case .precipitation: fallthrough
        case .showers: fallthrough
        case .rain: return true
        case .updraft: return true
        default: return false
        }
    }
    
    func getVarAndLevel(domain: IconDomains) -> (variable: String, cat: String, level: Int?)? {
        if domain == .iconEps || domain == .iconEuEps || domain == .iconD2Eps {
            switch self {
            case .diffuse_radiation:
                if domain == .iconEps {
                    // ICON-EPS does not have diffuse radiation
                    // Put regular shortwave radiation into this field
                    return ("asob_s", "single-level", nil)
                }
                break
            case .pressure_msl:
                if domain == .iconEps || domain == .iconEuEps {
                    // use surface pressure instead of sea level pressure
                    return ("ps", "single-level", nil)
                }
                break
            case .direct_radiation:
                break // ICON-EPS has only 3-hourly data
            case .cloud_cover:
                break
            case .temperature_2m:
                break
            case .relative_humidity_2m:
                if domain == .iconEps {
                    // use dewpoint, because relative humidity is only 6 hourly
                    return ("td_2m", "single-level", nil)
                }
                if domain == .iconEuEps {
                    // No dewpoint or relative humidity available in EU-EPS
                    return nil
                }
                break
            case .precipitation:
                break
            case .wind_u_component_10m:
                break
            case .wind_v_component_10m:
                break
                // all variables below are not in the global EPS model
            case .wind_u_component_80m:
                fallthrough
            case .wind_v_component_80m:
                fallthrough
            case .temperature_80m:
                fallthrough
            case .wind_gusts_10m:
                fallthrough
            case .snowfall_convective_water_equivalent:
                fallthrough
            case .snowfall_water_equivalent:
                fallthrough
            case .cape:
                if domain == .iconEps {
                    return nil // not in global
                }
                break
                
                // all variables below are only in the D2 EPS model
            case .wind_u_component_120m:
                fallthrough
            case .wind_v_component_120m:
                fallthrough
            case .temperature_120m:
                fallthrough
            case .wind_u_component_180m:
                fallthrough
            case .wind_v_component_180m:
                fallthrough
            case .rain:
                fallthrough
            case .showers:
                fallthrough
            case .snow_depth:
                fallthrough
            case .temperature_180m:
                if domain != .iconD2Eps {
                    return nil
                }
                break
            default:
                return nil
            }
        }
        
        if domain == .iconD2_15min {
            switch self {
            case .direct_radiation:
                break
            case .diffuse_radiation:
                break
            case .precipitation:
                break
            case .cape:
                break
            case .lightning_potential:
                break
            case .snowfall_height:
                break
            case .snowfall_water_equivalent:
                break
            case .freezing_level_height:
                break
            case .rain:
                break
            default:
                return nil
                // All other variables are not in ICON-D2 15 minutes
            }
        }
        
        switch self {
        case .soil_temperature_0cm: return ("t_so", "soil-level", 0)
        case .soil_temperature_6cm: return ("t_so", "soil-level", 6)
        case .soil_temperature_18cm: return ("t_so", "soil-level", 18)
        case .soil_temperature_54cm: return ("t_so", "soil-level", 54)
        case .soil_moisture_0_to_1cm: return ("w_so", "soil-level", 0)
        case .soil_moisture_1_to_3cm: return ("w_so", "soil-level", 1)
        case .soil_moisture_3_to_9cm: return ("w_so", "soil-level", 3)
        case .soil_moisture_9_to_27cm: return ("w_so", "soil-level", 9)
        case .soil_moisture_27_to_81cm: return ("w_so", "soil-level", 27)
        case .wind_u_component_80m: return ("u", "model-level", domain.numberOfModelFullLevels-2)
        case .wind_v_component_80m: return ("v", "model-level", domain.numberOfModelFullLevels-2)
        case .wind_u_component_120m: return ("u", "model-level", domain.numberOfModelFullLevels-3)
        case .wind_v_component_120m: return ("v", "model-level", domain.numberOfModelFullLevels-3)
        case .wind_u_component_180m: return ("u", "model-level", domain.numberOfModelFullLevels-4)
        case .wind_v_component_180m: return ("v", "model-level", domain.numberOfModelFullLevels-4)
        case .temperature_80m: return ("t", "model-level", domain.numberOfModelFullLevels-2)
        case .temperature_120m: return ("t", "model-level", domain.numberOfModelFullLevels-3)
        case .temperature_180m: return ("t", "model-level", domain.numberOfModelFullLevels-4)
        case .temperature_2m: return ("t_2m", "single-level", nil)
        case .cloud_cover: return ("clct", "single-level", nil)
        case .cloud_cover_low: return ("clcl", "single-level", nil)
        case .cloud_cover_mid: return ("clcm", "single-level", nil)
        case .cloud_cover_high: return ("clch", "single-level", nil)
        case .convective_cloud_top: 
            let shallowOrDeepConvectionTop = domain == .iconD2 ? "htop_sc" : "htop_con"
            return (shallowOrDeepConvectionTop, "single-level", nil)
        case .convective_cloud_base: 
            let shallowOrDeepConvectionBase = domain == .iconD2 ? "hbas_sc" : "hbas_con"
            return (shallowOrDeepConvectionBase, "single-level", nil)
        case .precipitation: return ("tot_prec", "single-level", nil)
        case .weather_code: return ("ww", "single-level", nil)
        case .wind_v_component_10m: return ("v_10m", "single-level", nil)
        case .wind_u_component_10m: return ("u_10m", "single-level", nil)
        case .snow_depth: return ("h_snow", "single-level", nil)
        case .sensible_heat_flux: return ("ashfl_s", "single-level", nil)
        case .latent_heat_flux: return ("alhfl_s", "single-level", nil)
        case .showers: return ("rain_con", "single-level", nil)
        case .rain: return ("rain_gsp", "single-level", nil)
        case .wind_gusts_10m: return ("vmax_10m", "single-level", nil)
        case .freezing_level_height: return ("hzerocl", "single-level", nil)
        case .relative_humidity_2m: return ("relhum_2m", "single-level", nil)
        case .pressure_msl: return ("pmsl", "single-level", nil)
        case .diffuse_radiation: return ("aswdifd_s", "single-level", nil)
        case .direct_radiation: return ("aswdir_s", "single-level", nil)
        case .snowfall_convective_water_equivalent: return ("snow_con", "single-level", nil)
        case .snowfall_water_equivalent: return ("snow_gsp", "single-level", nil)
        case .cape: return ("cape_ml", "single-level", nil)
        case .lightning_potential:
            return domain == .iconD2 ? ("lpi", "single-level", nil) : nil // only in icon d2
        case .snowfall_height:
            return domain == .icon ? nil : ("snowlmt", "single-level", nil) // not in icon global
        case .updraft:
            return domain == .iconD2 ? ("w_ctmax", "single-level", nil) : nil // only in icon d2
        case .visibility:
            return domain == .icon ? nil : ("vis", "single-level", nil) // not in icon global
        }
    }
    
    var multiplyAdd: (multiply: Float, add: Float)? {
        switch self {
        case .temperature_2m: fallthrough
        case .temperature_80m: fallthrough
        case .temperature_120m: fallthrough
        case .temperature_180m: fallthrough
        case .soil_temperature_0cm: fallthrough
        case .soil_temperature_6cm: fallthrough
        case .soil_temperature_18cm: fallthrough
        case .soil_temperature_54cm:
            return (1, -273.15) // Temperature is stored in kelvin. Convert to celsius
        case .pressure_msl:
            return (1/100, 0) // convert to hPa
        case .soil_moisture_0_to_1cm:
            return (0.001 / 0.01, 0) // 1cm depth
        case .soil_moisture_1_to_3cm:
            return (0.001 / 0.02, 0) // 2cm depth
        case .soil_moisture_3_to_9cm:
            return (0.001 / 0.06, 0) // 6cm depth
        case .soil_moisture_9_to_27cm:
            return (0.001 / 0.18, 0) // 18cm depth
        case .soil_moisture_27_to_81cm:
            return (0.001 / 0.54, 0) // 54cm depth
        default:
            return nil
        }
    }
}

extension IconPressureVariable: IconVariableDownloadable {
    func skipHour(hour: Int, domain: IconDomains, forDownload: Bool, run: Timestamp) -> Bool {
        return false
    }
    
    var multiplyAdd: (multiply: Float, add: Float)? {
        switch variable {
        case .temperature:
            return (1, -273.15)
        case.geopotential_height:
            // convert geopotential to height (WMO defined gravity constant)
            return (1/9.80665, 0)
        default:
            return nil
        }
    }
    
    func getVarAndLevel(domain: IconDomains) -> (variable: String, cat: String, level: Int?)? {
        if domain == .iconD2_15min {
            return nil
        }
        switch variable {
        case .temperature:
        return ("t", "pressure-level", level)
        case .wind_u_component:
            return ("u", "pressure-level", level)
        case .wind_v_component:
            return ("v", "pressure-level", level)
        case .geopotential_height:
            return ("fi", "pressure-level", level)
        case .relative_humidity:
            return ("relhum", "pressure-level", level)
        }
    }
}