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.

105 lines
4.0KB

  1. import Foundation
  2. // https://archive.epa.gov/ttn/ozone/web/pdf/rg701.pdf
  3. // https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
  4. class AQIUtils {
  5. private static let index_low : [Double] = [ 0, 51, 101, 151, 201, 301, 401 ]
  6. private static let index_high : [Double] = [ 50, 100, 150, 200, 300, 400, 500 ]
  7. public enum Pollutant : String {
  8. public typealias RawValue = String
  9. case unknown = "Unknown"
  10. case pm2p5 = "PM2.5"
  11. case pm10 = "PM10"
  12. case co = "Carbon Monoxide"
  13. case so2 = "Sulfur Dioxide"
  14. }
  15. public enum AQICategory : String {
  16. public typealias RawValue = String
  17. case invalid = "Invalid"
  18. case good = "Good"
  19. case moderate = "Moderate"
  20. case unhealthySensitive = "Unhealthy for Sensitive Groups"
  21. case unhealthy = "Unhealthy"
  22. case veryUnhealthy = "Very Unhealthy"
  23. case hazardous = "Hazardous"
  24. }
  25. public struct AQIResult : CustomDebugStringConvertible {
  26. public var debugDescription: String {
  27. return "AQIResult(aqi: \(aqi), category: \(category.rawValue), pollutant: \(pollutant.rawValue))"
  28. }
  29. var aqi : Int
  30. var category : AQICategory
  31. var pollutant : Pollutant
  32. }
  33. private static func c2aqi(pollutant : Pollutant, concentration : Double, breakpoints_low : [Double], breakpoints_high : [Double]) -> AQIResult {
  34. if (concentration > breakpoints_high.last!) { return(AQIResult(aqi: -1, category: .invalid, pollutant: pollutant)) }
  35. let rank = breakpoints_high.map({ concentration <= $0 }).firstIndex(where: { $0 == true })!
  36. let result = ceil(
  37. (index_high[rank] - index_low[rank]) /
  38. (breakpoints_high[rank] - breakpoints_low[rank]) * (concentration - breakpoints_low[rank]) + index_low[rank]
  39. )
  40. if (result <= 50) {
  41. return(AQIResult(aqi: Int(result), category: .good, pollutant: pollutant))
  42. } else if (result <= 100) {
  43. return(AQIResult(aqi: Int(result), category: .moderate, pollutant: pollutant))
  44. } else if (result <= 150) {
  45. return(AQIResult(aqi: Int(result), category: .unhealthySensitive, pollutant: pollutant))
  46. } else if (result <= 200) {
  47. return(AQIResult(aqi: Int(result), category: .unhealthy, pollutant: pollutant))
  48. } else if (result <= 300) {
  49. return(AQIResult(aqi: Int(result), category: .veryUnhealthy, pollutant: pollutant))
  50. } else if (result <= 400) {
  51. return(AQIResult(aqi: Int(result), category: .hazardous, pollutant: pollutant))
  52. } else if (result <= 500) {
  53. return(AQIResult(aqi: Int(result), category: .hazardous, pollutant: pollutant))
  54. } else {
  55. return(AQIResult(aqi: Int(result), category: .invalid, pollutant: pollutant))
  56. }
  57. }
  58. public static func pm2p5_aqi(concentration: Double) -> AQIResult {
  59. let pm2p5_bpl : [Double] = [ 0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5 ]
  60. let pm2p5_bph : [Double] = [ 12.0, 35.4, 55.4, 150.4, 250.4, 350.4, 500.4 ]
  61. return(c2aqi(pollutant: .pm2p5, concentration: concentration, breakpoints_low: pm2p5_bpl, breakpoints_high: pm2p5_bph))
  62. }
  63. public static func pm10_aqi(concentration: Double) -> AQIResult {
  64. let pm10_bpl : [Double] = [ 0, 55, 155, 255, 355, 425, 505 ]
  65. let pm10_bph : [Double] = [ 54, 154, 254, 354, 424, 504, 604 ]
  66. return(c2aqi(pollutant: .pm10, concentration: concentration, breakpoints_low: pm10_bpl, breakpoints_high: pm10_bph))
  67. }
  68. public static func co_aqi(concentration: Double) -> AQIResult {
  69. let co_bpl : [Double] = [ 0, 4.5, 9.5, 12.5, 15.5, 30.5, 40.5 ]
  70. let cp_bph : [Double] = [ 4.4, 9.4, 12.4, 15.4, 30.4, 40.4, 50.4 ]
  71. return(c2aqi(pollutant: .co, concentration: concentration, breakpoints_low: co_bpl, breakpoints_high: cp_bph))
  72. }
  73. public static func so2_aqi(concentration: Double) -> AQIResult {
  74. let so2_bpl : [Double] = [ 0, 36, 76, 186, 305, 605, 805 ]
  75. let so2_bph : [Double] = [ 35, 75, 185, 304, 604, 804, 1004 ]
  76. return(c2aqi(pollutant: .so2, concentration: concentration*1000, breakpoints_low: so2_bpl, breakpoints_high: so2_bph))
  77. }
  78. }