Spaces:
Sleeping
Sleeping
| /// 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) | |
| } | |
| } | |
| } | |