open-wether / Sources /App /Helper /WeatherCode.swift
soiz1's picture
Migrated from GitHub
6ee917b verified
import Foundation
enum WeatherCode: Int {
case clearSky = 0
case mainlyClear = 1
case partlyCloudy = 2
case overcast = 3
case fog = 45
case depositingRimeFog = 48
case lightDrizzle = 51
case moderateDrizzle = 53
case denseDrizzle = 55
case lightFreezingDrizzle = 56
case moderateOrDenseFreezingDrizzle = 57
case lightRain = 61
case moderateRain = 63
case heavyRain = 65
case lightFreezingRain = 66
case moderateOrHeavyFreezingRain = 67
case slightSnowfall = 71
case moderateSnowfall = 73
case heavySnowfall = 75
case snowGrains = 77
case slightRainShowers = 80
case moderateRainShowers = 81
case heavyRainShowers = 82
case slightSnowShowers = 85
case heavySnowShowers = 86
case thunderstormSlightOrModerate = 95
case thunderstormStrong = 96
case thunderstormHeavy = 99
/// Calculate weather interpretation code
/// http://www.cosmo-model.org/content/model/documentation/newsLetters/newsLetter06/cnl6_hoffmann.pdf
/// https://www.dwd.de/DE/leistungen/pbfb_verlag_promet/pdf_promethefte/28_1_2_pdf.pdf?__blob=publicationFile&v=8
public static func calculate(cloudcover: Float, precipitation: Float, convectivePrecipitation: Float?, snowfallCentimeters: Float, gusts: Float?, cape: Float?, liftedIndex: Float?, visibilityMeters: Float?, categoricalFreezingRain: Float?, modelDtSeconds: Int) -> WeatherCode? {
guard cloudcover.isFinite, precipitation.isFinite, snowfallCentimeters.isFinite else {
return nil
}
let modelDtHours = Float(modelDtSeconds) / 3600
//let thunderstromStrength: WeatherCode = ((gusts ?? 0) >= 18/3.6 || (precipitation / modelDtHours) >= 10) ? .thunderstormStrong : ((gusts ?? 0 >= 29/3.6) || (precipitation / modelDtHours) >= 25) ? .thunderstormStrong : .thunderstormSlightOrModerate
if let cape, cape >= 3000 {
if let liftedIndex {
if liftedIndex <= -5 {
return .thunderstormSlightOrModerate
}
} else {
return .thunderstormSlightOrModerate
}
}
if let categoricalFreezingRain, categoricalFreezingRain >= 1 {
switch precipitation / modelDtHours {
case 0.01..<0.5: return .lightFreezingDrizzle
case 0.5..<1.0: return .moderateOrDenseFreezingDrizzle
case 1.0..<1.3: return .moderateOrDenseFreezingDrizzle
case 1.3..<2.5: return .lightFreezingRain
case 2.5..<7.6: return .moderateOrHeavyFreezingRain
case 7.6...: return .moderateOrHeavyFreezingRain
default: break
}
}
if (convectivePrecipitation ?? 0) > 0 || (cape ?? 0) >= 800 {
switch snowfallCentimeters / modelDtHours {
case 0.01..<0.2: return .slightSnowShowers
case 0.2..<0.8: return .slightSnowShowers
case 0.8...: return .heavySnowShowers
default: break
}
switch precipitation / modelDtHours {
case 1.3..<2.5: return .slightRainShowers
case 2.5..<7.6: return .moderateRainShowers
case 7.6...: return .moderateRainShowers
default: break
}
}
switch snowfallCentimeters / modelDtHours {
case 0.01..<0.2: return .slightSnowfall
case 0.2..<0.8: return .moderateSnowfall
case 0.8...: return .heavySnowfall
default: break
}
switch precipitation / modelDtHours {
case 0.01..<0.5: return .lightDrizzle
case 0.5..<1.0: return .moderateDrizzle
case 1.0..<1.3: return .denseDrizzle
case 1.3..<2.5: return .lightRain
case 2.5..<7.6: return .moderateRain
case 7.6...: return .heavyRain
default: break
}
if let visibilityMeters, visibilityMeters <= 1000 {
return .fog
}
switch cloudcover {
case 0..<20: return .clearSky
case 20..<50: return .mainlyClear
case 50..<80: return .partlyCloudy
case 80...: return .overcast
default: break
}
return nil
}
public static func calculate(cloudcover: [Float], precipitation: [Float], convectivePrecipitation: [Float]?, snowfallCentimeters: [Float], gusts: [Float]?, cape: [Float]?, liftedIndex: [Float]?, visibilityMeters: [Float]?, categoricalFreezingRain: [Float]?, modelDtSeconds: Int) -> [Float] {
return cloudcover.indices.map { i in
return calculate(
cloudcover: cloudcover[i],
precipitation: precipitation[i],
convectivePrecipitation: convectivePrecipitation?[i],
snowfallCentimeters: snowfallCentimeters[i],
gusts: gusts?[i],
cape: cape?[i],
liftedIndex: liftedIndex?[i],
visibilityMeters: visibilityMeters?[i],
categoricalFreezingRain: categoricalFreezingRain?[i],
modelDtSeconds: modelDtSeconds
).map({Float($0.rawValue)}) ?? .nan
}
}
/// True if weather code is an precipitation event. Thunderstorm, return false as they may only indicate potential
var isPrecipitationEvent: Bool {
switch self {
case .lightDrizzle, .moderateDrizzle, .denseDrizzle:
fallthrough
case .lightFreezingDrizzle, .moderateOrDenseFreezingDrizzle:
fallthrough
case .lightRain, .moderateRain, .heavyRain:
fallthrough
case .lightFreezingRain, .moderateOrHeavyFreezingRain:
fallthrough
case .slightSnowfall, .moderateSnowfall, .heavySnowfall, .snowGrains:
fallthrough
case .slightRainShowers, .moderateRainShowers,.heavyRainShowers:
fallthrough
case .slightSnowShowers, .heavySnowShowers:
return true
default:
return false
}
}
/// DWD ICON weather codes show rain although precipitation is 0
/// Similar for snow at +2°C or more
func correctDwdIconWeatherCode(temperature_2m: Float, precipitation: Float, snowfallHeightAboveGrid: Bool) -> WeatherCode {
if precipitation <= 0 && self.isPrecipitationEvent {
// Weather code shows drizzle, but no precipitation, demote to overcast
return .overcast
}
if temperature_2m >= 2 || snowfallHeightAboveGrid {
// Weather code may show snow, although temperature is high
switch self {
case .slightSnowfall:
return .lightRain
case .moderateSnowfall:
return .moderateRain
case .heavySnowfall:
return .heavyRain
default:
break
}
}
if temperature_2m < -1 {
switch self {
case .lightRain:
return .slightSnowfall
case .moderateRain:
return .moderateSnowfall
case .heavyRain:
return .heavySnowfall
default:
break
}
}
return self
}
// If temperature smaller or greated 0°C, set or unset snow
func correctSnowRainHardCutOff(temperature_2m: Float) -> Self {
if temperature_2m > 0 {
// Weather code may show snow, although temperature is high
switch self {
case .slightSnowfall:
return .lightRain
case .moderateSnowfall:
return .moderateRain
case .heavySnowfall:
return .heavyRain
default:
break
}
}
if temperature_2m < 0 {
switch self {
case .lightRain:
return .slightSnowfall
case .moderateRain:
return .moderateSnowfall
case .heavyRain:
return .heavySnowfall
default:
break
}
}
return self
}
}