mirror of
https://github.com/acaloiaro/roam-location
synced 2026-07-21 10:12:21 +00:00
fix(weather): apply Beer-Lambert extinction to clear-sky model
The simple atmosphericCeiling*sin(elevation) model overestimates clear-sky radiation at low sun angles because it ignores the longer atmospheric path light travels near the horizon. At 5° elevation the path is ~11x vertical, making the real clear-sky ceiling ~26 W/m² rather than the 83 W/m² the naive model predicts — causing sunny low-angle readings to be misclassified as mostly cloudy. Replace with the Beer-Lambert correction: expectedClearSky = 950 * exp(-opticalDepth / sin(elevation)) * sin(elevation) opticalDepth=0.1 (clear atmosphere) produces accurate values across all angles: ~26 W/m² at 5°, ~93 W/m² at 10°, ~715 W/m² at solar noon.
This commit is contained in:
parent
367ab3ec98
commit
567845f88e
1 changed files with 14 additions and 5 deletions
|
|
@ -11,11 +11,18 @@ type Condition struct {
|
||||||
Emoji string `json:"emoji"`
|
Emoji string `json:"emoji"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// atmosphericCeiling is the clear-sky solar radiation (W/m²) with the sun
|
// atmosphericCeiling is the extraterrestrial solar irradiance at sea level (W/m²)
|
||||||
// directly overhead at sea level. Actual clear-sky radiation at any other
|
// with the sun directly overhead.
|
||||||
// elevation angle scales by sin(elevation).
|
|
||||||
const atmosphericCeiling = 950.0
|
const atmosphericCeiling = 950.0
|
||||||
|
|
||||||
|
// opticalDepth is the vertical atmospheric optical depth used in the Beer-Lambert
|
||||||
|
// extinction model. At low sun angles the light path through the atmosphere is
|
||||||
|
// 1/sin(elevation) times longer than vertical, so the effective transmittance
|
||||||
|
// drops sharply. A value of 0.1 represents a clear sky and produces realistic
|
||||||
|
// clear-sky readings across all elevation angles (e.g. ~26 W/m² at 5°, ~715 W/m²
|
||||||
|
// at solar noon for mid-latitudes in spring).
|
||||||
|
const opticalDepth = 0.1
|
||||||
|
|
||||||
// solarPosition returns the solar elevation angle (radians) and hour angle
|
// solarPosition returns the solar elevation angle (radians) and hour angle
|
||||||
// (degrees) for the given latitude, longitude, and UTC time.
|
// (degrees) for the given latitude, longitude, and UTC time.
|
||||||
//
|
//
|
||||||
|
|
@ -80,8 +87,10 @@ func Infer(d Data, lat, lon float64) *Condition {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sun is above the horizon. Normalise against the clear-sky radiation
|
// Sun is above the horizon. Normalise against the clear-sky radiation
|
||||||
// expected at this elevation angle.
|
// expected at this elevation angle, corrected for atmospheric extinction
|
||||||
expectedClearSky := atmosphericCeiling * math.Sin(elevationRad)
|
// (Beer-Lambert): at low angles the light path is ~1/sin(elev) times
|
||||||
|
// longer than vertical, so the clear-sky ceiling drops sharply.
|
||||||
|
expectedClearSky := atmosphericCeiling * math.Exp(-opticalDepth/math.Sin(elevationRad)) * math.Sin(elevationRad)
|
||||||
if solar < 10 {
|
if solar < 10 {
|
||||||
return &Condition{"Overcast", "☁️"}
|
return &Condition{"Overcast", "☁️"}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue