File size: 8,276 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
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
    }
}