import Foundation import NIOConcurrencyHelpers import OmFileFormat /** ICON Domains including ensemble */ enum IconDomains: String, CaseIterable, GenericDomain { /// hourly data until forecast hour 78, then 3 h until 180 case icon case iconEu = "icon-eu" case iconD2 = "icon-d2" case iconD2_15min = "icon-d2-15min" case iconEps = "icon-eps" case iconEuEps = "icon-eu-eps" case iconD2Eps = "icon-d2-eps" var dtSeconds: Int { if self == .iconD2_15min { return 3600/4 } return 3600 } var domainRegistry: DomainRegistry { switch self { case .icon: return .dwd_icon case .iconEu: return .dwd_icon_eu case .iconD2: return .dwd_icon_d2 case .iconD2_15min: return .dwd_icon_d2_15min case .iconEps: return .dwd_icon_eps case .iconEuEps: return .dwd_icon_eu_eps case .iconD2Eps: return .dwd_icon_d2_eps } } var domainRegistryStatic: DomainRegistry? { switch self { case .iconD2_15min: return .dwd_icon_d2 default: return domainRegistry } } var hasYearlyFiles: Bool { return false } var masterTimeRange: Range? { return nil } /// How many hourly timesteps to keep in each compressed chunk var omFileLength: Int { switch self { case .icon, .iconEps: return 180+1 + 3*24 case .iconEu, .iconEuEps: return 120+1 + 3*24 case .iconD2, .iconD2Eps: return 48+1 + 3*24 case .iconD2_15min: return 48*4 + 3*24 } } /// All available pressure levels for the current domain var levels: [Int] { switch self { case .icon: return [30, 50, 70, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 850, 900, 925, 950, 1000] case .iconEu: return [ 50, 70, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 850, 900, 925, 950, 1000] // disabled: 775, 825, 875 case .iconD2: return [ 200, 250, 300, 400, 500, 600, 700, 850, 950, 975, 1000] case .iconD2_15min: return [] case .iconEps: return [] case .iconEuEps: return [] // 300, 500, 850 only temperature and wind case .iconD2Eps: return [] // 500, 700, 850, 950, 975, 1000 } } var updateIntervalSeconds: Int { switch self { case .icon: return 6*3600 case .iconEu, .iconD2, .iconD2_15min: return 3*3600 case .iconEps: return 12*3600 case .iconEuEps: return 6*3600 case .iconD2Eps: return 3*3600 } } /// Number of available forecast steps differs from run /// E.g. icon global 0z has 180 as a last value, but 6z only 120 func getDownloadForecastSteps(run: Int) -> [Int] { switch self { case .iconEps: // Note ICON-EPS has only 6 hourly data for 6/18z runs, not used here // Hourly data until 48h, 3 hourly until 72, 6 hourly until 120h (same as ICON-EU-EPS) and 12 hourly until 180h return Array(0...48) + Array(stride(from: 51, through: 72, by: 3)) + Array(stride(from: 78, through: 120, by: 6)) + Array(stride(from: 132, through: 180, by: 12)) case .icon: if run == 6 || run == 18 { // only up to 120 return Array(0...78) + Array(stride(from: 81, through: 120, by: 3)) } else { // full 180 return Array(0...78) + Array(stride(from: 81, through: 180, by: 3)) } case .iconEuEps: // Hourly data until 48h, 3 hourly until 72, then 6 hourly until 120h (same as ICON-EPS) // no side runs return Array(0...48) + Array(stride(from: 51, through: 72, by: 3)) + Array(stride(from: 78, through: 120, by: 6)) case .iconEu: if run % 6 == 0 { return Array(0...78) + Array(stride(from: 81, through: 120, by: 3)) } // side runs return Array(0...30) case .iconD2_15min: return Array(0...48*4-1) case .iconD2Eps: fallthrough case .iconD2: return Array(0...48) } } var grid: Gridable { switch self { case .icon: return RegularGrid(nx: 2879, ny: 1441, latMin: -90, lonMin: -180, dx: 0.125, dy: 0.125) case .iconEu: return RegularGrid(nx: 1377, ny: 657, latMin: 29.5, lonMin: -23.5, dx: 0.0625, dy: 0.0625) case .iconD2_15min: fallthrough case .iconD2: return RegularGrid(nx: 1215, ny: 746, latMin: 43.18, lonMin: -3.94, dx: 0.02, dy: 0.02) case .iconEps: // R03B06 avg 26.5 km return RegularGrid(nx: 1439, ny: 721, latMin: -90, lonMin: -180, dx: 0.25, dy: 0.25) case .iconEuEps: // R03B07 avg 13.2 km return RegularGrid(nx: 689, ny: 329, latMin: 29.5, lonMin: -23.5, dx: 0.125, dy: 0.125) case .iconD2Eps: // R19B07 avg 2 km // Note: 1px difference to use the same weights as official return RegularGrid(nx: 1214, ny: 745, latMin: 43.18, lonMin: -3.94, dx: 0.02, dy: 0.02) } } /// name in the filenames var region: String { switch self { case .iconEps: fallthrough case .icon: return "global" case .iconEuEps: fallthrough case .iconEu: return "europe" case .iconD2Eps: fallthrough case .iconD2_15min: fallthrough case .iconD2: return "germany" } } /// model level standard heights, full levels /// icon wind level 1-90 88=98m, 87-174m /// icon-eu 1-60 58,57 /// icon-d2 1-65.... 63=78m, 62=126m var numberOfModelFullLevels: Int { switch self { case .iconEps: fallthrough case .icon: return 120 // was 90 case .iconEuEps: fallthrough case .iconEu: return 74 // was 60 case .iconD2Eps: fallthrough case .iconD2_15min: fallthrough case .iconD2: return 65 } } /// ICON uses 1.5°C melting point temperature: https://gitlab.dkrz.de/icon/icon-model/-/blob/release-2024.01-public/src/atm_phy_nwp/mo_nh_interface_nwp.f90?ref_type=heads#L2232 static let tMelt = Float(1.5) }