From 87a0db03884b376adb00961873dca54ea446d6c8 Mon Sep 17 00:00:00 2001 From: hrbrmstr Date: Sun, 18 Oct 2020 11:31:06 -0400 Subject: [PATCH] initial commit --- AQIUtils.swift | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 24 ++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 AQIUtils.swift diff --git a/AQIUtils.swift b/AQIUtils.swift new file mode 100644 index 0000000..eaf96a9 --- /dev/null +++ b/AQIUtils.swift @@ -0,0 +1,104 @@ +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 = "PM 10" + 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)) + } + +} + diff --git a/README.md b/README.md index ee5b830..0813a84 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # AQIUtils -Swift class to convert pollutant concentration values to AQI \ No newline at end of file +Swift class to convert pollutant concentration values to AQI + +## Examples + +```swift +debugPrint("concentration: 15.70 => \(AQIUtils.pm10_aqi(concentration: 15.70))") +// "concentration: 15.70 => AQIResult(aqi: 15, category: Good, pollutant: PM 10)" +``` + +```swift +debugPrint("concentration: 13.36 => \(AQIUtils.pm2p5_aqi(concentration: 13.36))") +// "concentration: 13.36 => AQIResult(aqi: 54, category: Moderate, pollutant: PM2.5)" +``` + +```swift +debugPrint("concentration: 31.20 => \(AQIUtils.co_aqi(concentration: 31.2))") +// "concentration: 31.20 => AQIResult(aqi: 308, category: Hazardous, pollutant: Carbon Monoxide)" +``` + +```swift +debugPrint("concentration: 0.15 => \(AQIUtils.so2_aqi(concentration: 0.15))") +// "concentration: 0.15 => AQIResult(aqi: 135, category: Unhealthy for Sensitive Groups, pollutant: Sulfur Dioxide)" +```