open-wether / Sources /App /Helper /Reader /GenericDomain.swift
soiz1's picture
Migrated from GitHub
6ee917b verified
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
}
}
}