Skip to content

Commit

Permalink
add heat index & warning level calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
cdzombak committed Jul 17, 2024
1 parent bdf198a commit 97c58a8
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 1 deletion.
108 changes: 108 additions & 0 deletions libwx.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,111 @@ func WetBulbC(temp TempC, rh RelHumidity) (TempC, error) {
),
nil
}

func heatIndexConstantsF() [9]float64 {
// from https://en.wikipedia.org/wiki/Heat_index#Formula
// captured on 2024-07-17
return [9]float64{
-42.379,
2.04901523,
10.14333127,
-0.22475541,
-6.83783e-3,
-5.481717e-2,
1.22874e-3,
8.5282e-4,
-1.99e-6,
}
}

func heatIndexConstantsC() [9]float64 {
// from https://en.wikipedia.org/wiki/Heat_index#Formula
// captured on 2024-07-17
return [9]float64{
-8.78469475556,
1.61139411,
2.33854883889,
-0.14611605,
-0.012308094,
-0.0164248277778,
2.211732e-3,
7.2546e-4,
-3.582e-6,
}
}

func rawHeatIndex(c [9]float64, rawTemp, rawRelH float64) float64 {
// from https://en.wikipedia.org/wiki/Heat_index#Formula
// captured on 2024-07-17
// note that constants on that page are 1-indexed, while the constants
// array here is 0-indexed
return c[0] +
c[1]*rawTemp +
c[2]*rawRelH +
c[3]*rawTemp*rawRelH +
c[4]*math.Pow(rawTemp, 2) +
c[5]*math.Pow(rawRelH, 2) +
c[6]*math.Pow(rawTemp, 2)*rawRelH +
c[7]*rawTemp*math.Pow(rawRelH, 2) +
c[8]*math.Pow(rawTemp, 2)*math.Pow(rawRelH, 2)
}

// HeatIndexF calculates the heat index for the given temperature (in Fahrenheit)
// and relative humidity percentage.
func HeatIndexF(temp TempF, rh RelHumidity) TempF {
return TempF(rawHeatIndex(
heatIndexConstantsF(),
temp.Unwrap(),
rh.Clamped().UnwrapFloat64(),
))
}

// HeatIndexC calculates the heat index for the given temperature (in Celsius)
// and relative humidity percentage.
func HeatIndexC(temp TempC, rh RelHumidity) TempC {
return TempC(rawHeatIndex(
heatIndexConstantsC(),
temp.Unwrap(),
rh.Clamped().UnwrapFloat64(),
))
}

// HeatIndexWarningF returns a heat index warning level for the
// given heat index temperature (in Fahrenheit) per
// https://en.wikipedia.org/wiki/Heat_index#Table_of_values
// captured on 2024-07-17.
func HeatIndexWarningF(heatIndex TempF) HeatIndexWarning {
if heatIndex.Unwrap() < 80 {
return HeatIndexWarningNone
}
if heatIndex.Unwrap() < 91 {
return HeatIndexWarningCaution
}
if heatIndex.Unwrap() < 104 {
return HeatIndexWarningExtremeCaution
}
if heatIndex.Unwrap() < 125 {
return HeatIndexWarningDanger
}
return HeatIndexWarningExtremeDanger
}

// HeatIndexWarningC returns a heat index warning level for the
// given heat index temperature (in Celsius) per
// https://en.wikipedia.org/wiki/Heat_index#Table_of_values
// captured on 2024-07-17.
func HeatIndexWarningC(heatIndex TempC) HeatIndexWarning {
if heatIndex.Unwrap() < 27 {
return HeatIndexWarningNone
}
if heatIndex.Unwrap() < 33 {
return HeatIndexWarningCaution
}
if heatIndex.Unwrap() < 40 {
return HeatIndexWarningExtremeCaution
}
if heatIndex.Unwrap() < 52 {
return HeatIndexWarningDanger
}
return HeatIndexWarningExtremeDanger
}
84 changes: 83 additions & 1 deletion libwx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// TODO(cdzombak): testing for calculations in libwx.go

func Test_WetBulbC(t *testing.T) {
func Test_WetBulb_C(t *testing.T) {
r := require.New(t)
eq := CurriedFloat64Equal(Tolerance0)

Expand Down Expand Up @@ -52,3 +52,85 @@ func Test_WetBulbC(t *testing.T) {
r.True(eq(result.Unwrap(), c.expected.Unwrap()), msgAndArgs...)
}
}

func Test_HeatIndex_All(t *testing.T) {
r := require.New(t)
eq := CurriedFloat64Equal(0.5)

cases := []struct {
t TempF
rh RelHumidity
expected TempF
}{
{TempF(80), RelHumidity(40), TempF(80)},
{TempF(80), RelHumidity(60), TempF(82)},
{TempF(80), RelHumidity(80), TempF(84)},
{TempF(80), RelHumidity(100), TempF(87)},
{TempF(86), RelHumidity(40), TempF(85)},
{TempF(86), RelHumidity(60), TempF(91)},
{TempF(86), RelHumidity(80), TempF(100)},
{TempF(86), RelHumidity(100), TempF(112)},
{TempF(90), RelHumidity(40), TempF(91)},
{TempF(90), RelHumidity(60), TempF(100)},
{TempF(90), RelHumidity(80), TempF(113)},
{TempF(90), RelHumidity(100), TempF(132)},
{TempF(104), RelHumidity(40), TempF(119)},
{TempF(104), RelHumidity(45), TempF(124)},
{TempF(104), RelHumidity(50), TempF(131)},
{TempF(104), RelHumidity(55), TempF(137)},
}

for _, c := range cases {
resultF := HeatIndexF(c.t, c.rh)
msgAndArgsF := []interface{}{
"given t %v degF + rh %v: expected %v, got %v",
c.t,
c.rh,
c.expected,
resultF,
}
r.True(eq(resultF.Unwrap(), c.expected.Unwrap()), msgAndArgsF...)

resultC := HeatIndexC(c.t.C(), c.rh)
msgAndArgsC := []interface{}{
"given t %v degC + rh %v: expected %v, got %v",
c.t.C(),
c.rh,
c.expected.C(),
resultC,
}
r.True(eq(resultC.Unwrap(), c.expected.C().Unwrap()), msgAndArgsC...)
}
}

func Test_HeatIndexWarning_F(t *testing.T) {
r := require.New(t)

cases := []struct {
t TempF
expected HeatIndexWarning
}{
{TempF(-19), HeatIndexWarningNone},
{TempF(79), HeatIndexWarningNone},
{TempF(80), HeatIndexWarningCaution},
{TempF(87), HeatIndexWarningCaution},
{TempF(90), HeatIndexWarningCaution},
{TempF(91), HeatIndexWarningExtremeCaution},
{TempF(103), HeatIndexWarningExtremeCaution},
{TempF(104), HeatIndexWarningDanger},
{TempF(124), HeatIndexWarningDanger},
{TempF(126), HeatIndexWarningExtremeDanger},
{TempF(135), HeatIndexWarningExtremeDanger},
}

for _, c := range cases {
result := HeatIndexWarningF(c.t)
msgAndArgs := []interface{}{
"given t %v degF: expected %v, got %v",
c.t,
c.expected,
result,
}
r.Equal(c.expected, result, msgAndArgs...)
}
}
10 changes: 10 additions & 0 deletions temperature_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ type TempC float64

func (t TempF) Unwrap() float64 { return float64(t) }
func (t TempC) Unwrap() float64 { return float64(t) }

type HeatIndexWarning int

const (
HeatIndexWarningNone = iota
HeatIndexWarningCaution
HeatIndexWarningExtremeCaution
HeatIndexWarningDanger
HeatIndexWarningExtremeDanger
)

0 comments on commit 97c58a8

Please sign in to comment.