1.6.0
Fix and enhance the concrete calculation Minify the about.css file
This commit is contained in:
+181
-16
@@ -6,7 +6,9 @@
|
||||
//
|
||||
// Exports (in order of appearance):
|
||||
// SIGMA, EPSILON_P, A_K, ALBEDO_GRASS, ALBEDO_CONCRETE (radiation constants)
|
||||
// calcConcreteTemp(Ta, globalRad, windSpeed) urban surface temp
|
||||
// LAG_HOURS_CONCRETE slab thermal time constant
|
||||
// calcConcreteTemp(Ta, globalRad, windSpeed, ...) urban surface temp (instantaneous target)
|
||||
// calcConcreteTempPass(arrays...) thermal-lag pass over full hourly arrays
|
||||
// calcIndoorTempPass(TaArr, globArr, elevArr, buildingType) passive indoor temp
|
||||
// calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType) managed indoor temp
|
||||
// calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType, ventilated)
|
||||
@@ -42,26 +44,189 @@ export const ALBEDO_CONCRETE = 0.30;
|
||||
// CONCRETE SURFACE TEMPERATURE (Urban profile)
|
||||
// -------------------------------------------------------------------
|
||||
// Estimates the surface temperature of exposed concrete using a
|
||||
// simplified energy-balance approach:
|
||||
// - Absorbed solar = globalRad - (1 - albedo)
|
||||
// - No latent heat (no evaporation - concrete is dry)
|
||||
// - Convective loss to air proportional to wind speed
|
||||
// - Result is clamped to a physically plausible range
|
||||
// multi-factor energy-balance approach. Six physical effects are
|
||||
// modelled beyond the basic solar-gain / convective-loss pair:
|
||||
//
|
||||
// This is what matters for contact heat stress in cities - the UTCI
|
||||
// standard uses grass, which runs ~5-15 -C cooler than urban concrete
|
||||
// on a sunny day because grass sweats (transpires).
|
||||
// 1. SUN ANGLE CORRECTION
|
||||
// At low solar elevation the sun hits the surface obliquely,
|
||||
// spreading energy over a larger area. A sin(elev) factor
|
||||
// reduces absorbed solar proportionally. Clamped at a 5-deg
|
||||
// minimum so the result stays finite near the horizon.
|
||||
//
|
||||
// 2. CLOUD TYPE TRANSMITTANCE
|
||||
// Cloud cover category drives a transmittance multiplier.
|
||||
// Low cloud (stratus) is far more opaque than high cirrus:
|
||||
// clear 1.00 - full beam reaches the surface
|
||||
// wispy 0.92 - cirrus barely attenuates
|
||||
// scattered 0.72 - broken cumulus, significant blocking
|
||||
// overcast 0.28 - thick stratus, mostly diffuse remains
|
||||
// The raw effectiveRad already dims with cloud cover from the
|
||||
// API, but this adds the qualitative distinction between cloud
|
||||
// types that the single radiation number does not capture.
|
||||
//
|
||||
// 3. UV CLARITY FACTOR
|
||||
// UV index is a proxy for atmospheric clarity beyond cloud cover -
|
||||
// aerosols, haze, and humidity all reduce it. A UV of 8+ indicates
|
||||
// a very clean, dry atmosphere with maximum direct-beam intensity.
|
||||
// Normalised to a 0.85-1.00 range so it modulates rather than
|
||||
// dominates. No UV data defaults to neutral (1.0).
|
||||
//
|
||||
// 4. EVAPORATIVE COOLING FROM SOIL MOISTURE
|
||||
// Wet concrete loses heat via evaporation. Soil moisture at 0-1cm
|
||||
// is used as a proxy for surface wetness (0 = bone dry, 1 = fully
|
||||
// saturated). A saturated surface loses up to ~8-C relative to
|
||||
// the dry case - consistent with published wet-pavement studies.
|
||||
//
|
||||
// 5. RAIN-WET SURFACE
|
||||
// Active precipitation forces surface wetness regardless of soil
|
||||
// moisture data. Above 0.5 mm/h the surface is considered fully
|
||||
// wet and the maximum evaporative penalty applies.
|
||||
//
|
||||
// 6. SNOW COVER
|
||||
// Snow on concrete insulates the slab from solar gain AND strongly
|
||||
// reflects incoming radiation (albedo ~0.80 for fresh snow vs 0.30
|
||||
// for bare concrete). When snowfall is active or lying snow is
|
||||
// implied (snow > 0), absorbed radiation is cut by 85% and a small
|
||||
// insulating offset is applied instead.
|
||||
//
|
||||
// Colour thresholds (same as before - surface contact risk):
|
||||
// < Ta - should not occur (clamped)
|
||||
// Ta - 45 -C - warm but bearable contact
|
||||
// 45 - 60 -C - pain threshold for bare skin contact
|
||||
// > 60 -C - burns on contact (relevant for paws / bare feet)
|
||||
// -------------------------------------------------------------------
|
||||
export function calcConcreteTemp(Ta, globalRad, windSpeed) {
|
||||
export function calcConcreteTemp(Ta, globalRad, windSpeed, solElev, uv, cloudCat, soilM, precip, snow) {
|
||||
if (globalRad == null || Ta == null) return null;
|
||||
const absorbed = globalRad * (1 - ALBEDO_CONCRETE); // W/m-
|
||||
// Convective heat transfer coefficient: ~10 W/m-K still air, rises with wind
|
||||
// (10 reflects realistic natural convection; 5 was too low and ran too hot)
|
||||
|
||||
// -- 6. Snow short-circuit --------------------------------------------
|
||||
// Snow-covered concrete behaves like a white reflective insulator.
|
||||
// Absorbed solar collapses; slab temp stays close to air temp.
|
||||
if (snow != null && snow > 0) {
|
||||
const snowAbsorbed = globalRad * (1 - 0.80); // fresh snow albedo ~0.80
|
||||
const hcSnow = 10 + 4.5 * Math.sqrt(Math.max(windSpeed || 0, 0));
|
||||
const Ts = Ta + snowAbsorbed / hcSnow;
|
||||
return Math.max(Ta - 1, Math.min(Ts, 40)); // snow-covered slab rarely exceeds 40-C
|
||||
}
|
||||
|
||||
// -- 1. Sun angle correction ------------------------------------------
|
||||
// Low-angle sun spreads energy across a larger surface area.
|
||||
// sin(elev) = 1.0 at 90-deg (overhead), ~0.17 at 10-deg (grazing).
|
||||
// Default to sin(45-deg) ~0.71 when elevation is unknown.
|
||||
const elevDeg = (solElev != null) ? Math.max(5, solElev) : 45;
|
||||
const angleCorrection = Math.sin(elevDeg * Math.PI / 180);
|
||||
|
||||
// -- 2. Cloud type transmittance --------------------------------------
|
||||
// Modulates beam quality beyond what raw radiation already captures.
|
||||
const cloudTransmit = cloudCat === 'clear' ? 1.00
|
||||
: cloudCat === 'wispy' ? 0.92
|
||||
: cloudCat === 'scattered' ? 0.72
|
||||
: /* overcast */ 0.28;
|
||||
|
||||
// -- 3. UV clarity factor --------------------------------------------
|
||||
// UV index as atmospheric-clarity proxy. Scaled to 0.85-1.00 range.
|
||||
// uv=0 (night or heavy cloud) - neutral 1.0 (no adjustment needed,
|
||||
// radiation already near-zero). uv=8+ - max clarity bonus of 1.0.
|
||||
const uvClarity = (uv && uv > 0)
|
||||
? 0.85 + 0.15 * Math.min(1, uv / 8)
|
||||
: 1.0;
|
||||
|
||||
// -- Absorbed solar with all modifiers --------------------------------
|
||||
const absorbed = globalRad * (1 - ALBEDO_CONCRETE)
|
||||
* angleCorrection
|
||||
* cloudTransmit
|
||||
* uvClarity;
|
||||
|
||||
// -- Convective loss --------------------------------------------------
|
||||
const hc = 10 + 4.5 * Math.sqrt(Math.max(windSpeed || 0, 0));
|
||||
// Surface temp: Ta + solar gain / convective loss
|
||||
|
||||
// -- Dry surface temperature ------------------------------------------
|
||||
const Ts = Ta + absorbed / hc;
|
||||
// Clamp: can't be cooler than air, cap at 85 -C (melting asphalt territory)
|
||||
return Math.max(Ta, Math.min(Ts, 85));
|
||||
|
||||
// -- 4 + 5. Evaporative cooling ---------------------------------------
|
||||
// Rain-wet surface overrides soil moisture - surface is fully saturated.
|
||||
const surfaceWet = (precip != null && precip >= 0.5)
|
||||
? 1.0
|
||||
: Math.max(0, Math.min(1, soilM ?? 0));
|
||||
// Max evaporative delta ~8-C at full saturation (published wet-pavement data).
|
||||
const evapCooling = surfaceWet * 8;
|
||||
|
||||
const TsCooled = Ts - evapCooling;
|
||||
|
||||
// -- Final clamp: no cooler than air, no hotter than 85-C -------------
|
||||
return Math.max(Ta, Math.min(TsCooled, 85));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CONCRETE THERMAL LAG TIME CONSTANT
|
||||
// -------------------------------------------------------------------
|
||||
// A standard urban pavement slab (~100 mm thick, exposed top surface,
|
||||
// air below via sub-base) has moderate thermal mass. Real-world
|
||||
// measurement studies put the e-folding time constant at 1.5-2 h for
|
||||
// this geometry - we use 1.5 h as representative of the thin end of
|
||||
// typical footway construction (block paving, tarmac-over-hardcore).
|
||||
//
|
||||
// If this ever needs to vary by surface type, pull it into a
|
||||
// SURFACE_TYPES preset object mirroring BUILDING_TYPES in utils.js.
|
||||
// For now a single named constant keeps the intent obvious.
|
||||
// -------------------------------------------------------------------
|
||||
export const LAG_HOURS_CONCRETE = 1.5;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CONCRETE SURFACE TEMPERATURE - THERMAL LAG PASS
|
||||
// -------------------------------------------------------------------
|
||||
// Wraps calcConcreteTemp in the same exponential-blending pattern used
|
||||
// by calcIndoorTempPass. Instead of snapping to the instantaneous
|
||||
// target each hour, the slab temperature blends toward it at a rate
|
||||
// controlled by LAG_HOURS_CONCRETE.
|
||||
//
|
||||
// Effect in practice:
|
||||
// - A slab baking at 55-C when cloud rolls in will still read ~48-C
|
||||
// an hour later and ~44-C two hours later - not instantly 22-C.
|
||||
// - A cold slab at dawn takes 2-3 hours of strong sun to fully heat.
|
||||
// - Post-rain cool-down persists into the next hour even if it stops.
|
||||
//
|
||||
// Call AFTER building hourlyRows (same pattern as calcIndoorTempPass).
|
||||
// Returns a Float64-like plain Array of concrete temps, one per hour.
|
||||
//
|
||||
// Arguments are parallel arrays (one value per forecast hour):
|
||||
// TaArr - air temperature (-C)
|
||||
// radArr - effectiveRad (dir + dif*0.2) (W/m-)
|
||||
// vaArr - wind speed (m/s)
|
||||
// elevArr - solar elevation (degrees)
|
||||
// uvArr - UV index
|
||||
// cloudCatArr - cloud category string
|
||||
// soilMArr - soil moisture 0-1cm (0-1 fraction)
|
||||
// precipArr - precipitation (mm/h)
|
||||
// snowArr - snowfall (cm/h)
|
||||
// -------------------------------------------------------------------
|
||||
export function calcConcreteTempPass(TaArr, radArr, vaArr, elevArr, uvArr, cloudCatArr, soilMArr, precipArr, snowArr) {
|
||||
const n = TaArr.length;
|
||||
const result = new Array(n);
|
||||
const alpha = 1 - Math.exp(-1 / LAG_HOURS_CONCRETE);
|
||||
|
||||
// Seed from the first hours instantaneous value so we start somewhere
|
||||
// physically reasonable rather than zero.
|
||||
let Tc = calcConcreteTemp(
|
||||
TaArr[0], radArr[0], vaArr[0],
|
||||
elevArr[0], uvArr[0], cloudCatArr[0],
|
||||
soilMArr[0], precipArr[0], snowArr[0]
|
||||
) ?? TaArr[0];
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const target = calcConcreteTemp(
|
||||
TaArr[i], radArr[i], vaArr[i],
|
||||
elevArr[i], uvArr[i], cloudCatArr[i],
|
||||
soilMArr[i], precipArr[i], snowArr[i]
|
||||
) ?? TaArr[i];
|
||||
|
||||
// Blend slab temp toward this hours target.
|
||||
Tc = Tc + alpha * (target - Tc);
|
||||
|
||||
// Slab cannot be cooler than air (no active cooling mechanism).
|
||||
result[i] = Math.max(TaArr[i], Tc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user