File size: 4,763 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
import Foundation
import OmFileFormat

/**
 Generic domain that is required for the reader
 */
protocol GenericDomain {
    /// The grid definition. Could later be replaced with a more generic implementation
    var grid: Gridable { get }
    
    /// Domain name used as data directory
    var domainRegistry: DomainRegistry { get }
    
    /// Domain which is used for static files. E.g. 15minutes domains refer to the 1-hourly domain
    var domainRegistryStatic: DomainRegistry? { get }
    
    /// Time resoltuion of the deomain. 3600 for hourly, 10800 for 3-hourly
    var dtSeconds: Int { get }
    
    /// How often a domain is updated in seconds. `3600` for updates every hour
    var updateIntervalSeconds: Int { get }
    
    /// If true, domain has yearly files
    var hasYearlyFiles: Bool { get }
    
    /// If present, the timerange that is available in a master file
    var masterTimeRange: Range<Timestamp>? { get }
    
    /// The time length of each compressed time series file
    var omFileLength: Int { get }
}

extension GenericDomain {
    var dtHours: Int { dtSeconds / 3600 }
    
    /// Temporary directory to download data
    var downloadDirectory: String {
        return "\(OpenMeteo.tempDirectory)download-\(domainRegistry.rawValue)/"
    }
    
    /// The the file containing static information for elevation of soil types
    func getStaticFile(type: ReaderStaticVariable) -> OmFileReaderArray<MmapFile, Float>? {
        guard let domainRegistryStatic else {
            return nil
        }
        switch type {
        case .soilType:
            return try? OmFileManager.get(
                .staticFile(domain: domainRegistryStatic, variable: "soil_type", chunk: nil)
            )
        case .elevation:
            return try? OmFileManager.get(
                .staticFile(domain: domainRegistryStatic, variable: "HSURF", chunk: nil)
            )
        }
    }
    
    func getMetaJson() throws -> ModelUpdateMetaJson? {
        return try MetaFileManager.get(OmFileManagerReadable.meta(domain: domainRegistry))
    }
    
    /// Filename of the surface elevation file
    var surfaceElevationFileOm: OmFileManagerReadable {
        .staticFile(domain: domainRegistry, variable: "HSURF", chunk: nil)
    }
    
    var soilTypeFileOm: OmFileManagerReadable {
        .staticFile(domain: domainRegistry, variable: "soil_type", chunk: nil)
    }
}

/**
 Generic variable for the reader implementation
 */
protocol GenericVariable: GenericVariableMixable {
    /// The filename of the variable. Typically just `temperature_2m`. Level is used to store mutliple levels or ensemble members in one file
    /// NOTE: `level` has been replaced with `ensembleMemberLevel` in settings
    var omFileName: (file: String, level: Int) { get }
    
    /// The scalefactor to compress data
    var scalefactor: Float { get }
    
    /// Kind of interpolation for this variable. Used to interpolate from 1 to 3 hours
    var interpolation: ReaderInterpolation { get }
    
    /// SI unit of this variable
    var unit: SiUnit { get }
    
    /// If true, temperature will be corrected by 0.65°K per 100 m
    var isElevationCorrectable: Bool { get }
    
    /// If true, forecasts from the previous model runs will be preserved
    var storePreviousForecast: Bool { get }
}

enum ReaderInterpolation {
    /// Simple linear interpolation
    case linear
    
    /// Interpolate 0-360° values
    case linearDegrees
    
    /// Hermite interpolation for more smooth interpolation for temperature
    case hermite(bounds: ClosedRange<Float>?)
    
    /// Solar fluxes are properly backwards averaged during model run time. Always be the case with weather models
    case solar_backwards_averaged
    
    /// Solar flux is backwards averaged, but values after missing values are not averaged correctly.
    /// This happens with satellite data and missing time steps
    case solar_backwards_missing_not_averaged
    
    /// Take the next hour, and devide by `dt` to preserve sums like precipitation
    case backwards_sum
    
    /// Replicate value backwards. E.g. min/max of previous hours
    case backwards
    
    /// How many timesteps on the left and right side are used for interpolation
    var padding: Int {
        switch self {
        case .linear, .linearDegrees:
            return 1
        case .hermite:
            return 2
        case .solar_backwards_averaged, .solar_backwards_missing_not_averaged:
            return 2
        case .backwards_sum, .backwards:
            return 1
        }
    }
    
    var bounds: ClosedRange<Float>? {
        switch self {
        case .hermite(let bounds):
            return bounds
        default:
            return nil
        }
    }
}