Swift class to convert pollutant concentration values to AQI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
4.0 KiB

import Foundation
// https://archive.epa.gov/ttn/ozone/web/pdf/rg701.pdf
// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
class AQIUtils {
private static let index_low : [Double] = [ 0, 51, 101, 151, 201, 301, 401 ]
private static let index_high : [Double] = [ 50, 100, 150, 200, 300, 400, 500 ]
public enum Pollutant : String {
public typealias RawValue = String
case unknown = "Unknown"
case pm2p5 = "PM2.5"
case pm10 = "PM10"
case co = "Carbon Monoxide"
case so2 = "Sulfur Dioxide"
}
public enum AQICategory : String {
public typealias RawValue = String
case invalid = "Invalid"
case good = "Good"
case moderate = "Moderate"
case unhealthySensitive = "Unhealthy for Sensitive Groups"
case unhealthy = "Unhealthy"
case veryUnhealthy = "Very Unhealthy"
case hazardous = "Hazardous"
}
public struct AQIResult : CustomDebugStringConvertible {
public var debugDescription: String {
return "AQIResult(aqi: \(aqi), category: \(category.rawValue), pollutant: \(pollutant.rawValue))"
}
var aqi : Int
var category : AQICategory
var pollutant : Pollutant
}
private static func c2aqi(pollutant : Pollutant, concentration : Double, breakpoints_low : [Double], breakpoints_high : [Double]) -> AQIResult {
if (concentration > breakpoints_high.last!) { return(AQIResult(aqi: -1, category: .invalid, pollutant: pollutant)) }
let rank = breakpoints_high.map({ concentration <= $0 }).firstIndex(where: { $0 == true })!
let result = ceil(
(index_high[rank] - index_low[rank]) /
(breakpoints_high[rank] - breakpoints_low[rank]) * (concentration - breakpoints_low[rank]) + index_low[rank]
)
if (result <= 50) {
return(AQIResult(aqi: Int(result), category: .good, pollutant: pollutant))
} else if (result <= 100) {
return(AQIResult(aqi: Int(result), category: .moderate, pollutant: pollutant))
} else if (result <= 150) {
return(AQIResult(aqi: Int(result), category: .unhealthySensitive, pollutant: pollutant))
} else if (result <= 200) {
return(AQIResult(aqi: Int(result), category: .unhealthy, pollutant: pollutant))
} else if (result <= 300) {
return(AQIResult(aqi: Int(result), category: .veryUnhealthy, pollutant: pollutant))
} else if (result <= 400) {
return(AQIResult(aqi: Int(result), category: .hazardous, pollutant: pollutant))
} else if (result <= 500) {
return(AQIResult(aqi: Int(result), category: .hazardous, pollutant: pollutant))
} else {
return(AQIResult(aqi: Int(result), category: .invalid, pollutant: pollutant))
}
}
public static func pm2p5_aqi(concentration: Double) -> AQIResult {
let pm2p5_bpl : [Double] = [ 0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5 ]
let pm2p5_bph : [Double] = [ 12.0, 35.4, 55.4, 150.4, 250.4, 350.4, 500.4 ]
return(c2aqi(pollutant: .pm2p5, concentration: concentration, breakpoints_low: pm2p5_bpl, breakpoints_high: pm2p5_bph))
}
public static func pm10_aqi(concentration: Double) -> AQIResult {
let pm10_bpl : [Double] = [ 0, 55, 155, 255, 355, 425, 505 ]
let pm10_bph : [Double] = [ 54, 154, 254, 354, 424, 504, 604 ]
return(c2aqi(pollutant: .pm10, concentration: concentration, breakpoints_low: pm10_bpl, breakpoints_high: pm10_bph))
}
public static func co_aqi(concentration: Double) -> AQIResult {
let co_bpl : [Double] = [ 0, 4.5, 9.5, 12.5, 15.5, 30.5, 40.5 ]
let cp_bph : [Double] = [ 4.4, 9.4, 12.4, 15.4, 30.4, 40.4, 50.4 ]
return(c2aqi(pollutant: .co, concentration: concentration, breakpoints_low: co_bpl, breakpoints_high: cp_bph))
}
public static func so2_aqi(concentration: Double) -> AQIResult {
let so2_bpl : [Double] = [ 0, 36, 76, 186, 305, 605, 805 ]
let so2_bph : [Double] = [ 35, 75, 185, 304, 604, 804, 1004 ]
return(c2aqi(pollutant: .so2, concentration: concentration*1000, breakpoints_low: so2_bpl, breakpoints_high: so2_bph))
}
}