import Foundation enum MfWaveVariable: String, CaseIterable, GenericVariable, GenericVariableMixable { case wave_height case wave_period case wave_direction case wind_wave_height case wind_wave_period case wind_wave_direction case swell_wave_height case swell_wave_period case swell_wave_direction var storePreviousForecast: Bool { return false } var isElevationCorrectable: Bool { return false } var requiresOffsetCorrectionForMixing: Bool { return false } var omFileName: (file: String, level: Int) { return (rawValue, 0) } /// Si unit var unit: SiUnit { switch self { case .wave_height: return .metre case .wave_period: return .seconds case .wave_direction: return .degreeDirection case .wind_wave_height: return .metre case .wind_wave_period: return .seconds case .wind_wave_direction: return .degreeDirection case .swell_wave_height: return .metre case .swell_wave_period: return .seconds case .swell_wave_direction: return .degreeDirection } } var scalefactor: Float { let period: Float = 20 // 0.05s resolution let height: Float = 50 // 0.02m resolution let direction: Float = 1 switch self { case .wave_height: return height case .wave_period: return period case .wave_direction: return direction case .wind_wave_height: return height case .wind_wave_period: return period case .wind_wave_direction: return direction case .swell_wave_height: return height case .swell_wave_period: return period case .swell_wave_direction: return direction } } var interpolation: ReaderInterpolation { switch self { case .wave_height: return .linear case .wave_period: return .hermite(bounds: 0...Float.infinity) case .wave_direction: return .linearDegrees case .wind_wave_height: return .linear case .wind_wave_period: return .hermite(bounds: 0...Float.infinity) case .wind_wave_direction: return .linearDegrees case .swell_wave_height: return .linear case .swell_wave_period: return .hermite(bounds: 0...Float.infinity) case .swell_wave_direction: return .linearDegrees } } } enum MfCurrentVariable: String, CaseIterable, GenericVariable, GenericVariableMixable { case ocean_u_current case ocean_v_current case sea_level_height_msl case invert_barometer_height var storePreviousForecast: Bool { return false } var isElevationCorrectable: Bool { return false } var requiresOffsetCorrectionForMixing: Bool { return false } var omFileName: (file: String, level: Int) { return (rawValue, 0) } /// Si unit var unit: SiUnit { switch self { case .ocean_u_current, .ocean_v_current: return .metrePerSecond case .sea_level_height_msl, .invert_barometer_height: return .metre } } var scalefactor: Float { switch self { case .ocean_u_current, .ocean_v_current: return 20 // 0.05 ms (~0.1 knots) case .sea_level_height_msl, .invert_barometer_height: return 100 // 1cm res } } var interpolation: ReaderInterpolation { switch self { case .ocean_u_current, .ocean_v_current: return .hermite(bounds: nil) case .sea_level_height_msl, .invert_barometer_height: return .hermite(bounds: nil) } } } enum MfCurrentVariableDerived: String, CaseIterable, GenericVariableMixable { case ocean_current_velocity case ocean_current_direction var requiresOffsetCorrectionForMixing: Bool { return false } } struct MfCurrentReader: GenericReaderDerived, GenericReaderProtocol { typealias Domain = MfWaveDomain typealias Variable = MfCurrentVariable typealias Derived = MfCurrentVariableDerived typealias MixingVar = VariableOrDerived let reader: GenericReaderCached func get(raw: MfCurrentVariable, time: TimerangeDtAndSettings) throws -> DataAndUnit { return try reader.get(variable: raw, time: time) } func prefetchData(raw: MfCurrentVariable, time: TimerangeDtAndSettings) throws { try reader.prefetchData(variable: raw, time: time) } func get(derived: MfCurrentVariableDerived, time: TimerangeDtAndSettings) throws -> DataAndUnit { switch derived { case .ocean_current_velocity: let u = try get(raw: .ocean_u_current, time: time).data let v = try get(raw: .ocean_v_current, time: time).data let speed = zip(u,v).map(Meteorology.windspeed) return DataAndUnit(speed, .metrePerSecond) case .ocean_current_direction: let u = try get(raw: .ocean_u_current, time: time).data let v = try get(raw: .ocean_v_current, time: time).data let direction = Meteorology.windirectionFast(u: u, v: v).map { ($0+180).truncatingRemainder(dividingBy: 360) } return DataAndUnit(direction, .degreeDirection) } } func prefetchData(derived: MfCurrentVariableDerived, time: TimerangeDtAndSettings) throws { switch derived { case .ocean_current_direction, .ocean_current_velocity: try prefetchData(raw: .ocean_u_current, time: time) try prefetchData(raw: .ocean_v_current, time: time) } } } /// Converts negative wave direction to positive struct MfWaveReader: GenericReaderProtocol { typealias Domain = MfWaveDomain typealias MixingVar = MfWaveVariable let reader: GenericReader var modelLat: Float { return reader.modelLat } var modelLon: Float { return reader.modelLon } var modelElevation: ElevationOrSea { return reader.modelElevation } var targetElevation: Float { return reader.targetElevation } var modelDtSeconds: Int { return reader.modelDtSeconds } func getStatic(type: ReaderStaticVariable) throws -> Float? { return try reader.getStatic(type: type) } func get(variable: MfWaveVariable, time: TimerangeDtAndSettings) throws -> DataAndUnit { let data = try reader.get(variable: variable, time: time) switch variable { case .wave_direction, .wind_wave_direction, .swell_wave_direction: let direction = data.data.map { ($0+180).truncatingRemainder(dividingBy: 360) } return DataAndUnit(direction, .degreeDirection) default: return data } } func prefetchData(variable: MfWaveVariable, time: TimerangeDtAndSettings) throws { try reader.prefetchData(variable: variable, time: time) } } enum MfSSTVariable: String, CaseIterable, GenericVariable, GenericVariableMixable { case sea_surface_temperature var storePreviousForecast: Bool { return false } var isElevationCorrectable: Bool { return false } var requiresOffsetCorrectionForMixing: Bool { return false } var omFileName: (file: String, level: Int) { return (rawValue, 0) } /// Si unit var unit: SiUnit { switch self { case .sea_surface_temperature: return .celsius } } var scalefactor: Float { switch self { case .sea_surface_temperature: return 20 } } var interpolation: ReaderInterpolation { switch self { case .sea_surface_temperature: return .hermite(bounds: nil) } } }