File size: 5,842 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
import Foundation

extension Zensun {
    /// Calculate DNI based on zenith angle
    public static func calculateInstantDNI(directRadiation: [Float], latitude: Float, longitude: Float, timerange: TimerangeDt) -> [Float] {
        var out = [Float]()
        out.reserveCapacity(directRadiation.count)
        
        for (dhi, timestamp) in zip(directRadiation, timerange) {
            // direct horizontal irradiation
            if dhi.isNaN {
                out.append(.nan)
                continue
            }
            if dhi <= 0 {
                out.append(0)
                continue
            }

            let decang = timestamp.getSunDeclination()
            let eqtime = timestamp.getSunEquationOfTime()
            
            let latsun=decang
            /// universal time
            let ut = timestamp.hourWithFraction
            let t1 = (90-latsun).degreesToRadians
            
            let lonsun = -15.0*(ut-12.0+eqtime)
            
            /// longitude of sun
            let p1 = lonsun.degreesToRadians
            let t0=(90-latitude).degreesToRadians

            /// longitude of point
            let p0 = longitude.degreesToRadians
            let zz = cos(t0)*cos(t1)+sin(t0)*sin(t1)*cos(p1-p0)
            if zz <= 0 {
                out.append(0)
                continue
            }
            let b = max(zz, cos(Float(85).degreesToRadians))
            let dni = dhi / b
            out.append(dni)
        }
        return out
    }
    
    /// Calculate DNI using super sampling
    public static func calculateBackwardsDNISupersampled(directRadiation: [Float], latitude: Float, longitude: Float, timerange: TimerangeDt, samples: Int = 60) -> [Float] {
        // Shift timerange by dt and increase time resolution
        let dtNew = timerange.dtSeconds / samples
        let timeSuperSampled = timerange.range.add(-timerange.dtSeconds + dtNew).range(dtSeconds: dtNew)
        let dhiBackwardsSuperSamled = directRadiation.interpolateSolarBackwards(timeOld: timerange, timeNew: timeSuperSampled, latitude: latitude, longitude: longitude, scalefactor: 1000)
        
        let averagedToInstant = backwardsAveragedToInstantFactor(time: timeSuperSampled, latitude: latitude, longitude: longitude)
        let dhiSuperSamled = zip(dhiBackwardsSuperSamled, averagedToInstant).map(*)
        
        let dniSuperSampled = calculateInstantDNI(directRadiation: dhiSuperSamled, latitude: latitude, longitude: longitude, timerange: timeSuperSampled)
        
        /// return instant values
        //return (0..<timerange.count).map { dhiBackwardsSuperSamled[Swift.min($0 * samples + samples, dhiBackwardsSuperSamled.count-1)] }
        
        let dni = dniSuperSampled.mean(by: samples)
        
        return dni
    }
    
    /// Calculate DNI based on zenith angle
    public static func calculateBackwardsDNI(directRadiation: [Float], latitude: Float, longitude: Float, timerange: TimerangeDt, convertToInstant: Bool = false) -> [Float] {
        //return calculateBackwardsDNISupersampled(directRadiation: directRadiation, latitude: latitude, longitude: longitude, timerange: timerange)
        
        return zip(directRadiation, timerange).map { (dhi, timestamp) in
            if dhi.isNaN {
                return .nan
            }
            if dhi <= 0 {
                return 0
            }
            
            /// DNI is typically limted to 85° zenith. We apply 5° to the parallax in addition to atmospheric refraction
            /// The parallax is then use to limit integral coefficients to sun rise/set
            let alpha = Float(0.83333 - 5).degreesToRadians

            let decang = timestamp.getSunDeclination()
            let eqtime = timestamp.getSunEquationOfTime()
            
            let latsun=decang
            /// universal time
            let ut = timestamp.hourWithFraction
            let t1 = (90-latsun).degreesToRadians
            
            let lonsun = -15.0*(ut-12.0+eqtime)
            
            /// longitude of sun
            let p1 = lonsun.degreesToRadians
            
            
            let ut0 = ut - (Float(timerange.dtSeconds)/3600)
            let lonsun0 = -15.0*(ut0-12.0+eqtime)
            
            let p10 = lonsun0.degreesToRadians
            
            let t0=(90-latitude).degreesToRadians

            /// longitude of point
            var p0 = longitude.degreesToRadians
            if p0 < p1 - .pi {
                p0 += 2 * .pi
            }
            if p0 > p1 + .pi {
                p0 -= 2 * .pi
            }

            // limit p1 and p10 to sunrise/set
            let arg = -(sin(alpha)+cos(t0)*cos(t1))/(sin(t0)*sin(t1))
            let carg = arg > 1 || arg < -1 ? .pi : acos(arg)
            let sunrise = p0 + carg
            let sunset = p0 - carg
            let p1_l = min(sunrise, p10)
            let p10_l = max(sunset, p1)
            
            // solve integral to get sun elevation dt
            // integral(cos(t0) cos(t1) + sin(t0) sin(t1) cos(p - p0)) dp = sin(t0) sin(t1) sin(p - p0) + p cos(t0) cos(t1) + constant
            let left = sin(t0) * sin(t1) * sin(p1_l - p0) + p1_l * cos(t0) * cos(t1)
            let right = sin(t0) * sin(t1) * sin(p10_l - p0) + p10_l * cos(t0) * cos(t1)
            let zzBackwards = (left-right) / (p1_l - p10_l)
            let dni = dhi / zzBackwards
            
            // Prevent possible division by zero
            // See https://github.com/open-meteo/open-meteo/discussions/395
            if zzBackwards <= 0.0001 {
                return dhi
            }
            
            /// Instant sun elevation
            let zzInstant = cos(t0)*cos(t1)+sin(t0)*sin(t1)*cos(p1-p0)
            return convertToInstant ? dni * max(zzInstant, 0) / zzBackwards : dni
        }
    }
}