Beta 6
Now with concrete, vehicle and home temps and profiles
This commit is contained in:
+219
-8
@@ -18,15 +18,226 @@
|
||||
// These are real-world physics values — don't change them unless you
|
||||
// have a peer-reviewed reason. They're used by calcTmrt() below to
|
||||
// work out how much heat your skin actually absorbs from the sun.
|
||||
// SIGMA — Stefan–Boltzmann constant (radiates heat)
|
||||
// EPSILON_P — emissivity of human skin (~0.97)
|
||||
// A_K — short-wave absorption coefficient for clothing
|
||||
// ALBEDO_GRASS — how much sun grass reflects back at you (23%)
|
||||
// SIGMA — Stefan–Boltzmann constant (radiates heat)
|
||||
// EPSILON_P — emissivity of human skin (~0.97)
|
||||
// A_K — short-wave absorption coefficient for clothing
|
||||
// ALBEDO_GRASS — how much sun grass reflects back at you (23%)
|
||||
// ALBEDO_CONCRETE — how much sun concrete reflects back (30%)
|
||||
// Concrete absorbs more net solar than grass and has
|
||||
// no evaporative cooling, so its surface runs hot.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
export const SIGMA = 5.670374419e-8;
|
||||
export const EPSILON_P = 0.97;
|
||||
export const A_K = 0.7;
|
||||
export const ALBEDO_GRASS = 0.23;
|
||||
export const SIGMA = 5.670374419e-8;
|
||||
export const EPSILON_P = 0.97;
|
||||
export const A_K = 0.7;
|
||||
export const ALBEDO_GRASS = 0.23;
|
||||
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
|
||||
//
|
||||
// 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).
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
export function calcConcreteTemp(Ta, globalRad, windSpeed) {
|
||||
if (globalRad == null || Ta == null) return null;
|
||||
const absorbed = globalRad * (1 - ALBEDO_CONCRETE); // W/m²
|
||||
// Convective heat transfer coefficient: ~5 W/m²K still air, rises with wind
|
||||
const hc = 5 + 4.5 * Math.sqrt(Math.max(windSpeed || 0, 0));
|
||||
// Surface temp: Ta + solar gain / convective loss
|
||||
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));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// UK HOUSE INDOOR TEMPERATURE (windows closed, no active cooling)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Estimates the ambient indoor air temperature of a typical UK brick
|
||||
// house with windows closed and no air conditioning.
|
||||
//
|
||||
// Two heat pathways are modelled:
|
||||
//
|
||||
// 1. WALL CONDUCTION
|
||||
// Heat conducts through brick cavity walls and roof. UK Part L
|
||||
// compliant walls have a U-value around 0.28–0.45 W/m²K; older
|
||||
// solid-brick stock runs higher. A representative mid-stock value
|
||||
// is used. This drives a slow, steady heat transfer proportional
|
||||
// to the difference between outdoor and indoor air temperature.
|
||||
//
|
||||
// 2. WINDOW SOLAR GAIN
|
||||
// A typical UK semi has ~15–18% glazing ratio. Solar energy
|
||||
// transmits through glass, is absorbed by floors and furniture,
|
||||
// and heats the indoor air. Gain is averaged across orientations
|
||||
// (not all windows face south). Diffuse radiation contributes
|
||||
// regardless of sun angle.
|
||||
//
|
||||
// THERMAL LAG
|
||||
// Brick and concrete have high thermal mass — the house responds
|
||||
// slowly to outdoor temperature swings. This function takes a
|
||||
// weighted average of the current hour's heat load and the
|
||||
// previous few hours', giving the characteristic lag where indoor
|
||||
// temperature peaks 2–4 hours after the outdoor peak.
|
||||
// Call calcIndoorTempPass() on the full hourly arrays after
|
||||
// building rows — it returns a per-hour indoor temp array.
|
||||
//
|
||||
// No mechanical cooling. Minimal infiltration (windows closed).
|
||||
// Internal heat gains (people, appliances) are not modelled.
|
||||
//
|
||||
// Colour thresholds:
|
||||
// < 20 °C — cool, may need heating
|
||||
// 20–26 °C — comfortable
|
||||
// 26–32 °C — warm; WHO heatwave advisory threshold for sleeping
|
||||
// > 32 °C — hot; risk for elderly and vulnerable occupants
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
// Single-hour instantaneous heat load (W/m² effective, indoor side).
|
||||
// Used internally by calcIndoorTempPass — not exported.
|
||||
function _houseHeatLoad(Ta, globalRad, solElev) {
|
||||
// Wall + roof conduction: U-value ~0.35 W/m²K × effective envelope area ratio
|
||||
// We express as a gain-per-degree-delta — applied against Ti later in the pass.
|
||||
// (See calcIndoorTempPass for how this feeds the lag model.)
|
||||
|
||||
// Window solar gain: glazing ratio 0.16, g-value 0.63 (standard double glazing),
|
||||
// averaged across orientations (0.5 factor — not all windows face the sun).
|
||||
const glazingRatio = 0.16;
|
||||
const gValue = 0.63;
|
||||
const orientFactor = 0.50;
|
||||
const solarGain = globalRad * glazingRatio * gValue * orientFactor;
|
||||
|
||||
return { conductionDelta: Ta, solarGain };
|
||||
}
|
||||
|
||||
// Two-pass function: call with the full arrays of hourly Ta and globalRad.
|
||||
// Returns an array of indoor temperatures, one per hour.
|
||||
export function calcIndoorTempPass(TaArr, globArr, elevArr) {
|
||||
const n = TaArr.length;
|
||||
const result = new Array(n);
|
||||
|
||||
// Thermal resistance of the building envelope (°C per W/m² of heat load)
|
||||
// Lower = faster response to outdoor changes. UK brick mid-stock ~0.45.
|
||||
const uWall = 0.35; // W/m²K effective wall U-value
|
||||
// Thermal mass time constant: heavier = longer lag.
|
||||
// ~4 h lag for typical UK brick semi (expressed as exponential decay weight).
|
||||
const lagHours = 4;
|
||||
const alpha = 1 - Math.exp(-1 / lagHours); // per-hour blending weight
|
||||
|
||||
// Seed indoor temp to first outdoor temp
|
||||
let Ti = TaArr[0] ?? 15;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const Ta = TaArr[i] ?? Ti;
|
||||
const glob = globArr[i] ?? 0;
|
||||
const solElev = elevArr[i] ?? 0;
|
||||
|
||||
// Solar gain through windows (W/m² effective)
|
||||
const glazingRatio = 0.16;
|
||||
const gValue = 0.63;
|
||||
const orientFactor = 0.50;
|
||||
const solarGain = glob * glazingRatio * gValue * orientFactor;
|
||||
|
||||
// Conductive heat flow through walls: proportional to (Ta - Ti)
|
||||
// uWall drives how quickly the indoor temp chases outdoor temp.
|
||||
const conductionGain = uWall * (Ta - Ti);
|
||||
|
||||
// Target indoor temp this hour if there were no thermal mass:
|
||||
// Ti_instant = Ti + conduction + solar load / heat capacity proxy
|
||||
// heatCap proxy: how many degrees does 1 W/m² raise the indoor air?
|
||||
// For a typical 90 m² house, ~0.15 °C per W/m² effective.
|
||||
const heatCapProxy = 0.15;
|
||||
const Ti_instant = Ti + (conductionGain + solarGain) * heatCapProxy;
|
||||
|
||||
// Apply thermal lag: blend toward Ti_instant slowly
|
||||
Ti = Ti + alpha * (Ti_instant - Ti);
|
||||
|
||||
// Can't be colder than outdoor (house doesn't actively cool)
|
||||
// Cap at 55 °C (physically implausible above this for a house interior)
|
||||
result[i] = Math.max(Math.min(Ti, 55), Math.min(Ta, Ti));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// VEHICLE INTERIOR CABIN TEMPERATURE (seated occupant, not in sunbeam)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Models the ambient cabin air temperature experienced by an occupant
|
||||
// seated out of direct sunlight inside a sealed, parked vehicle.
|
||||
// Two heat sources are combined:
|
||||
//
|
||||
// 1. PANEL CONDUCTION
|
||||
// Aluminium body panels absorb solar radiation and conduct heat
|
||||
// into the cabin. Albedo ~0.25 (mid-point for typical mixed-colour
|
||||
// fleet; dark paint ~0.10, silver/white ~0.40).
|
||||
// Panel surface temp → conductive gain into cabin air.
|
||||
//
|
||||
// 2. SIDE-WINDOW GLAZING GAIN (sun-angle dependent)
|
||||
// When solar elevation is between ~10° and ~60°, the sun's rays
|
||||
// cut through the side glass at an angle that allows significant
|
||||
// transmission into the cabin (rather than hitting the roof or
|
||||
// reflecting off at a shallow angle). This warms the cabin air
|
||||
// but the occupant is modelled as NOT sitting in the beam —
|
||||
// so it adds to ambient cabin temp, not direct radiant load.
|
||||
// Above 60° the sun mostly hits the roof; below 10° it reflects.
|
||||
//
|
||||
// Wind is ignored (vehicle is sealed). No evaporative cooling.
|
||||
// Steady-state reached after ~45–60 min of parking.
|
||||
//
|
||||
// Colour thresholds in the table:
|
||||
// < 35 °C — warm but tolerable for short periods
|
||||
// 35–45 °C — dangerous for children/pets (hyperthermia risk)
|
||||
// > 45 °C — potentially fatal within minutes
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
export function calcVehicleInteriorTemp(Ta, globalRad, solElev) {
|
||||
if (globalRad == null || Ta == null) return null;
|
||||
|
||||
// ── 1. Panel conduction ──────────────────────────────────────────
|
||||
const albedoPanel = 0.25; // typical mixed fleet
|
||||
const panelAbsorbed = globalRad * (1 - albedoPanel); // W/m² absorbed by bodywork
|
||||
// Panel surface temp: absorbed solar / convective loss to outside air
|
||||
// hOut ~10 W/m²K (light breeze over panel surface even when parked)
|
||||
const hOut = 10;
|
||||
const panelSurfaceTemp = Ta + panelAbsorbed / hOut;
|
||||
// Conductive gain into cabin: panel-to-cabin air, ~4 W/m²K through metal + trim
|
||||
const hCabin = 4;
|
||||
const conductionGain = hCabin * (panelSurfaceTemp - Ta); // W/m²
|
||||
|
||||
// ── 2. Side-window glazing gain (angle-dependent) ─────────────────
|
||||
// Glazing transmission for auto glass ~0.70
|
||||
const tau = 0.70;
|
||||
let glazingGain = 0;
|
||||
if (solElev != null && solElev > 10 && solElev < 60) {
|
||||
// Scale factor: peaks around 30–40° elevation (sun cuts squarely
|
||||
// through side glass), tapers off toward 10° (shallow/reflected)
|
||||
// and 60° (sun increasingly hitting roof not side glass).
|
||||
// Use a simple tent function peaking at 35°.
|
||||
const peak = 35;
|
||||
const halfWidth = 25; // degrees either side
|
||||
const factor = Math.max(0, 1 - Math.abs(solElev - peak) / halfWidth);
|
||||
// Diffuse radiation also enters through glass regardless of angle
|
||||
glazingGain = tau * globalRad * factor * 0.5; // occupant not in beam → 50% ambient
|
||||
} else {
|
||||
// Outside the side-window zone: diffuse only (scattered sky light)
|
||||
glazingGain = tau * (globalRad * 0.15); // ~15% diffuse fraction
|
||||
}
|
||||
|
||||
// ── Combine into cabin air temperature ───────────────────────────
|
||||
// Total heat input per m² of cabin surface
|
||||
const totalGain = conductionGain + glazingGain;
|
||||
// Cabin heat loss: poor natural ventilation in sealed car ~2.0 W/m²K
|
||||
const hCabinLoss = 2.0;
|
||||
const Ti = Ta + totalGain / hCabinLoss;
|
||||
|
||||
// Clamp: can't be cooler than outside air; physical cap at 90 °C
|
||||
return Math.max(Ta, Math.min(Ti, 90));
|
||||
}
|
||||
|
||||
// Vapour pressure (Magnus → hPa)
|
||||
export function vaporPressureHpa(Ta, RH) {
|
||||
|
||||
Reference in New Issue
Block a user