-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaqi.go
153 lines (141 loc) · 4.03 KB
/
aqi.go
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
143
144
145
146
147
148
149
150
151
152
153
// Copyright 2021 Inca Roads LLC. All rights reserved.
// Use of this source code is governed by licenses granted by the
// copyright holder including that found in the LICENSE file.
package main
import (
"math"
ttdata "github.com/Safecast/safecast-go"
)
// Calculate AQI
func aqiCalculate(sd *ttdata.SafecastData) {
var aqi uint32
var aqiPm float64
var aqiNotes, aqiLevel string
// Perform calculations based on sensor type
if sd.Opc != nil && sd.Opc.Pm02_5 != nil {
aqiPm, aqiNotes = adjustForHumidity(sd, *sd.Opc.Pm02_5, ttdata.AqiCFEN481)
aqi, aqiLevel = pmToAqi(aqiPm)
sd.Opc.AqiLevel = &aqiLevel
sd.Opc.AqiNotes = &aqiNotes
sd.Opc.AqiPm = &aqiPm
sd.Opc.Aqi = &aqi
}
if sd.Pms2 != nil && sd.Pms2.Pm02_5 != nil {
if sd.Pms2.Pm02_5cf1 != nil {
aqiPm, aqiNotes = adjustForHumidity(sd, *sd.Pms2.Pm02_5cf1, ttdata.AqiCF1)
} else {
aqiPm, aqiNotes = adjustForHumidity(sd, *sd.Pms2.Pm02_5, ttdata.AqiCFATM)
}
aqi, aqiLevel = pmToAqi(aqiPm)
sd.Pms2.AqiNotes = &aqiNotes
sd.Pms2.AqiLevel = &aqiLevel
sd.Pms2.AqiPm = &aqiPm
sd.Pms2.Aqi = &aqi
}
if sd.Pms != nil && sd.Pms.Pm02_5 != nil {
if sd.Pms.Pm02_5cf1 != nil {
aqiPm, aqiNotes = adjustForHumidity(sd, *sd.Pms.Pm02_5cf1, ttdata.AqiCF1)
} else {
aqiPm, aqiNotes = adjustForHumidity(sd, *sd.Pms.Pm02_5, ttdata.AqiCFATM)
}
aqi, aqiLevel = pmToAqi(aqiPm)
sd.Pms.AqiNotes = &aqiNotes
sd.Pms.AqiLevel = &aqiLevel
sd.Pms.AqiPm = &aqiPm
sd.Pms.Aqi = &aqi
}
}
// Adjust humidity according to US EPA PM2.5 adjustment factor
// https://amt.copernicus.org/preprints/amt-2020-413/amt-2020-413.pdf
// https://cfpub.epa.gov/si/si_public_file_download.cfm?p_download_id=540979&Lab=CEMM
func adjustForHumidity(sd *ttdata.SafecastData, pmIn float64, notesIn string) (pmOut float64, notesOut string) {
if sd.Env != nil && sd.Env.Humid != nil {
pmOut = (0.524 * pmIn) - (0.0852 * *sd.Env.Humid) + 5.72
notesOut = notesIn + "," + ttdata.AqiUSEPAHumidity
} else {
pmOut = pmIn
notesOut = notesIn
}
// PM goes negative at low PMs and high humidity
if pmOut < 0 {
pmOut = 0
}
return
}
// Calculate AQI for PM2.5
// https://forum.airnowtech.org/t/the-aqi-equation/169
func pmToAqi(concIn float64) (aqi uint32, aqiLevel string) {
var concLo, concHi, aqiLo, aqiHi float64
// For all AQI calculations, the calculated average concentrations are truncated to 0.1 μg/m3 for PM2.5.
// This truncated concentration is then used as the input (ConcIn) in the AQI equation. The resulting AQI
// is rounded to the nearest whole number.
itemp := uint32(concIn * 10)
concIn = float64(itemp) / 10
// Calculate level
if aqiLevel == "" {
concLo = 0.0
concHi = 12.0
if concIn >= concLo && concIn <= concHi {
aqiLo = 0
aqiHi = 50
aqiLevel = ttdata.AqiLevelGood
}
}
if aqiLevel == "" {
concLo = concHi
concHi = 35.4
if concIn >= concLo && concIn <= concHi {
aqiLo = 51
aqiHi = 100
aqiLevel = ttdata.AqiLevelModerate
}
}
if aqiLevel == "" {
concLo = concHi
concHi = 55.4
if concIn >= concLo && concIn <= concHi {
aqiLo = 101
aqiHi = 150
aqiLevel = ttdata.AqiLevelUnhealthyIfSensitive
}
}
if aqiLevel == "" {
concLo = concHi
concHi = 150.4
if concIn >= concLo && concIn <= concHi {
aqiLo = 151
aqiHi = 200
aqiLevel = ttdata.AqiLevelUnhealthy
}
}
if aqiLevel == "" {
concLo = concHi
concHi = 250.4
if concIn >= concLo && concIn <= concHi {
aqiLo = 201
aqiHi = 300
aqiLevel = ttdata.AqiLevelVeryUnhealthy
}
}
if aqiLevel == "" {
concLo = concHi
concHi = 500.4
if concIn >= concLo && concIn <= concHi {
aqiLo = 301
aqiHi = 500
aqiLevel = ttdata.AqiLevelHazardous
}
}
if aqiLevel == "" {
// Level is higher than the top of the table. Luckily,
// the AQI stops at the same level as the concentration,
// so we can return a number that looks meaningful.
aqiLevel = ttdata.AqiLevelVeryHazardous
aqi = uint32(math.Round(concIn))
} else {
// Compute the AQI according to the equation
aqi = uint32(math.Round((((aqiHi - aqiLo) / (concHi - concLo)) * (concIn - concLo)) + aqiLo))
}
// Done
return
}