1.5.7
Profile memory Comment cleanup
This commit is contained in:
+121
-121
@@ -1,5 +1,5 @@
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// physics.js — Physical constants and meteorological calculations.
|
||||
// ------------------------------------------------------------------------
|
||||
// physics.js - Physical constants and meteorological calculations.
|
||||
//
|
||||
// All numbers are peer-reviewed constants or coefficients. Nothing in
|
||||
// here should need editing unless the underlying science changes.
|
||||
@@ -10,63 +10,63 @@
|
||||
// calcIndoorTempPass(TaArr, globArr, elevArr, buildingType) passive indoor temp
|
||||
// calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType) managed indoor temp
|
||||
// calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType, ventilated)
|
||||
// vaporPressureHpa(Ta, RH) Magnus formula → hPa
|
||||
// solarElevationDeg(lat, lon, dateUTC) NOAA simplified solar position (°)
|
||||
// vaporPressureHpa(Ta, RH) Magnus formula - hPa
|
||||
// solarElevationDeg(lat, lon, dateUTC) NOAA simplified solar position (-)
|
||||
// calcTmrt(Ta, dirRad, diffRad, globalRad, solElev) Mean radiant temperature
|
||||
// utciApprox(Ta, Tmrt, va10, ehPa) Bröde et al. 2012 UTCI polynomial
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// utciApprox(Ta, Tmrt, va10, ehPa) Br-de et al. 2012 UTCI polynomial
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
import { VEHICLE_TYPES, BUILDING_TYPES } from './utils.js';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
// PHYSICAL CONSTANTS
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// These are real-world physics values — don't change them unless you
|
||||
// -------------------------------------------------------------------
|
||||
// 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%)
|
||||
// ALBEDO_CONCRETE — how much sun concrete reflects back (30%)
|
||||
// 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 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
|
||||
// - 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
|
||||
// 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: ~10 W/m²K still air, rises with wind
|
||||
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)
|
||||
const hc = 10 + 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)
|
||||
// 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.
|
||||
//
|
||||
@@ -74,37 +74,37 @@ export function calcConcreteTemp(Ta, globalRad, windSpeed) {
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// Brick and concrete have high thermal mass - the house responds
|
||||
// slowly to outdoor temperature swings. Each hour builds a realistic
|
||||
// target temperature from outdoor air, window solar gain, retained
|
||||
// warmth, and internal gains, then the room temperature lags toward
|
||||
// that target. This avoids runaway accumulation while still giving
|
||||
// the characteristic late-day indoor peak.
|
||||
// Call calcIndoorTempPass() on the full hourly arrays after
|
||||
// building rows — it returns a per-hour indoor temp array.
|
||||
// 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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// < 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
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Two-pass function: call with the full arrays of hourly Ta and globalRad.
|
||||
// Returns an array of indoor temperatures, one per hour.
|
||||
@@ -124,7 +124,7 @@ export function calcIndoorTempPass(TaArr, globArr, elevArr, buildingType = 'bric
|
||||
const Ta = TaArr[i] ?? Ti;
|
||||
const glob = globArr[i] ?? 0;
|
||||
|
||||
// Solar gain through windows (W/m² effective)
|
||||
// Solar gain through windows (W/m- effective)
|
||||
const solarGain = glob * glazingRatio * gValue * orientFactor;
|
||||
|
||||
const retainedWarmth = Math.max(0, (baseTemp ?? 16) - Ta) * (retainedScale ?? 0.35);
|
||||
@@ -141,9 +141,9 @@ export function calcIndoorTempPass(TaArr, globArr, elevArr, buildingType = 'bric
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// UK HOUSE INDOOR TEMPERATURE — MANAGED (curtains closed, windows open)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// UK HOUSE INDOOR TEMPERATURE - MANAGED (curtains closed, windows open)
|
||||
// -------------------------------------------------------------------
|
||||
// Models the same UK brick house as calcIndoorTempPass but with two
|
||||
// behavioural interventions that reflect standard heatwave advice:
|
||||
//
|
||||
@@ -156,11 +156,11 @@ export function calcIndoorTempPass(TaArr, globArr, elevArr, buildingType = 'bric
|
||||
// When outdoor air is cooler than the indoor air, windows are open
|
||||
// and a ventilation heat exchange pulls the indoor temp toward
|
||||
// outdoor. When outdoor is hotter than indoor, windows are kept
|
||||
// shut — so this strategy never makes things worse, only better.
|
||||
// shut - so this strategy never makes things worse, only better.
|
||||
// Ventilation rate ~2 air changes/hour for a well-opened house.
|
||||
//
|
||||
// Same thermal lag model as calcIndoorTempPass (4 h brick time constant).
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
// buildingType must be a key of BUILDING_TYPES; defaults to 'brick'.
|
||||
export function calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType = 'brick') {
|
||||
const preset = BUILDING_TYPES[buildingType] || BUILDING_TYPES.brick;
|
||||
@@ -176,7 +176,7 @@ export function calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType
|
||||
const Ta = TaArr[i] ?? Ti;
|
||||
const glob = globArr[i] ?? 0;
|
||||
|
||||
// Solar gain — curtains block curtainBlock fraction
|
||||
// Solar gain - curtains block curtainBlock fraction
|
||||
const solarGain = glob * glazingRatio * gValue * orientFactor * (1 - curtainBlock);
|
||||
|
||||
const retainedWarmth = Math.max(0, (baseTemp ?? 16) - Ta) * (retainedScale ?? 0.35);
|
||||
@@ -196,9 +196,9 @@ export function calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType
|
||||
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:
|
||||
@@ -207,72 +207,72 @@ export function calcManagedIndoorTempPass(TaArr, globArr, elevArr, buildingType
|
||||
// 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.
|
||||
// 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
|
||||
// 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 —
|
||||
// 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.
|
||||
// Above 60- the sun mostly hits the roof; below 10- it reflects.
|
||||
//
|
||||
// Wind is ignored unless ventilation is enabled. No evaporative cooling.
|
||||
// Cars warm quickly; motorhomes and caravans are treated as insulated living
|
||||
// spaces with 25–35 mm sandwich panels, so panel heat gain is much smaller
|
||||
// spaces with 25-35 mm sandwich panels, so panel heat gain is much smaller
|
||||
// and the interior response is slower than a car cabin. Because they are
|
||||
// occupied living spaces, they also retain warmth from previous hours, people,
|
||||
// appliances, and background heating; without that, cool sunny days are
|
||||
// under-estimated badly.
|
||||
//
|
||||
// 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
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// < 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, vehicleType = 'car', ventilated = false) {
|
||||
if (globalRad == null || Ta == null) return null;
|
||||
|
||||
// Look up vehicle preset; fall back to a standard car if key unknown.
|
||||
const preset = VEHICLE_TYPES[vehicleType] || VEHICLE_TYPES.car;
|
||||
|
||||
// ── 1. Panel conduction ──────────────────────────────────────────
|
||||
const panelAbsorbed = globalRad * (1 - preset.albedo); // W/m² absorbed by bodywork
|
||||
// -- 1. Panel conduction ------------------------------------------
|
||||
const panelAbsorbed = globalRad * (1 - preset.albedo); // 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)
|
||||
// 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. Cars are thin metal + trim; motorhomes and
|
||||
// caravans use insulated sandwich panels, so their bodyU is much lower.
|
||||
const hCabin = preset.bodyU ?? 4;
|
||||
const conductionGain = hCabin * (panelSurfaceTemp - Ta); // W/m²
|
||||
const conductionGain = hCabin * (panelSurfaceTemp - Ta); // W/m-
|
||||
|
||||
// ── 2. Side-window glazing gain (angle-dependent) ─────────────────
|
||||
// -- 2. Side-window glazing gain (angle-dependent) -----------------
|
||||
// Glazing transmission for auto glass ~0.70; scaled by vehicle glazing area.
|
||||
const tau = 0.70 * preset.glazingArea;
|
||||
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°.
|
||||
// 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
|
||||
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
|
||||
// -- Combine into cabin air temperature ---------------------------
|
||||
// Total heat input per m- of cabin surface
|
||||
const totalGain = conductionGain + glazingGain;
|
||||
// Cabin heat rejection: an effective blend of leakage, internal air volume,
|
||||
// and surfaces exchanging heat with the outside. With windows open it is
|
||||
// roughly 5× higher — air moves freely
|
||||
// roughly 5- higher - air moves freely
|
||||
// through the cabin, flushing heat out and capping interior temperature much
|
||||
// closer to ambient. Cabin temp still rises a little due to panel/roof solar gain.
|
||||
const effectiveHLoss = ventilated ? preset.hCabinLoss * 5 : preset.hCabinLoss;
|
||||
@@ -289,16 +289,16 @@ export function calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType = 'c
|
||||
|
||||
const Ti = Ta + solarRise + retainedWarmth + internalGain;
|
||||
|
||||
// Clamp: can't be cooler than outside air; physical cap at 90 °C
|
||||
// Clamp: can't be cooler than outside air; physical cap at 90 -C
|
||||
return Math.max(Ta, Math.min(Ti, 90));
|
||||
}
|
||||
|
||||
// ── FUTURE FEATURE v2: Pets Profile — Fur Temperature & Heat Stress ──────────
|
||||
// -- FUTURE FEATURE v2: Pets Profile - Fur Temperature & Heat Stress ----------
|
||||
// Dogs and cats experience heat very differently from humans:
|
||||
//
|
||||
// FUR SURFACE TEMPERATURE
|
||||
// Dark/thick fur absorbs solar radiation and can run significantly hotter
|
||||
// than air temperature — similar in principle to calcConcreteTemp() but
|
||||
// than air temperature - similar in principle to calcConcreteTemp() but
|
||||
// with fur-specific albedo values:
|
||||
// Black fur: albedo ~0.05 (almost all radiation absorbed)
|
||||
// Brown fur: albedo ~0.15
|
||||
@@ -306,44 +306,44 @@ export function calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType = 'c
|
||||
// White fur: albedo ~0.40
|
||||
// Add a calcFurSurfaceTemp(Ta, globalRad, windSpeed, furAlbedo) function
|
||||
// mirroring calcConcreteTemp but with appropriate convective coefficients
|
||||
// for fur (lower hc than concrete — fur insulates).
|
||||
// for fur (lower hc than concrete - fur insulates).
|
||||
//
|
||||
// PAW BURN RISK
|
||||
// Paw pads are highly sensitive to surface temperature. Use calcConcreteTemp()
|
||||
// output directly — the existing concrete surface temp is already the right
|
||||
// output directly - the existing concrete surface temp is already the right
|
||||
// number. Threshold guidance:
|
||||
// < 40 °C — safe
|
||||
// 40–52 °C — discomfort / possible burn (hold-your-hand-for-7-seconds test)
|
||||
// > 52 °C — burns within 60 seconds
|
||||
// < 40 -C - safe
|
||||
// 40-52 -C - discomfort / possible burn (hold-your-hand-for-7-seconds test)
|
||||
// > 52 -C - burns within 60 seconds
|
||||
// Display as a simple traffic-light column in the Pets profile.
|
||||
//
|
||||
// BREED-SPECIFIC HEAT STRESS
|
||||
// Brachycephalic breeds (Bulldogs, Pugs, French Bulldogs, Persians) have
|
||||
// severely impaired thermoregulation — their safe UTCI ceiling is much lower.
|
||||
// severely impaired thermoregulation - their safe UTCI ceiling is much lower.
|
||||
// A breed selector (Normal / Brachycephalic / Senior) applies a risk multiplier
|
||||
// to the UTCI thresholds, similar to the planned activity modifier.
|
||||
//
|
||||
// SUGGESTED COLUMNS FOR PETS PROFILE
|
||||
// Hour | Air Temp | UTCI+P | Fur Surface Temp | Paw Burn Risk | Shade Advised
|
||||
//
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// ── FUTURE FEATURE: Vehicle-at-speed thermal model ────────────────────────────
|
||||
// -- FUTURE FEATURE: Vehicle-at-speed thermal model ----------------------------
|
||||
// Idea: extend calcVehicleInteriorTemp (or add a companion function) to model
|
||||
// cabin temperature for a vehicle travelling at speed, not just parked.
|
||||
//
|
||||
// Key physics differences from the static model:
|
||||
// • Forced convection over the shell scales with vehicle speed (v²), so
|
||||
// hOut rises significantly — shell cools much faster than when parked.
|
||||
// • Above ~30 mph the vehicle's own forward motion dominates airflow, so
|
||||
// - Forced convection over the shell scales with vehicle speed (v-), so
|
||||
// hOut rises significantly - shell cools much faster than when parked.
|
||||
// - Above ~30 mph the vehicle's own forward motion dominates airflow, so
|
||||
// ambient wind direction becomes largely irrelevant (simplifies the model).
|
||||
// • Speed classes to model: urban (~20 mph), dual carriageway (~50 mph),
|
||||
// motorway (~70 mph) — each with a derived hOut multiplier.
|
||||
// • Windows-open behaviour changes completely at speed: at 70 mph open
|
||||
// - Speed classes to model: urban (~20 mph), dual carriageway (~50 mph),
|
||||
// motorway (~70 mph) - each with a derived hOut multiplier.
|
||||
// - Windows-open behaviour changes completely at speed: at 70 mph open
|
||||
// windows create high-velocity through-flow, dramatically cutting cabin
|
||||
// temp vs. the sealed-car case (but much less pleasant than AC!).
|
||||
// • Roof and bonnet solar gain stays the same; side-glass gain is unchanged.
|
||||
// • AC-off vs AC-on would be the primary user toggle alongside speed class.
|
||||
// - Roof and bonnet solar gain stays the same; side-glass gain is unchanged.
|
||||
// - AC-off vs AC-on would be the primary user toggle alongside speed class.
|
||||
//
|
||||
// Suggested signature:
|
||||
// calcVehicleInteriorTempAtSpeed(Ta, globalRad, solElev, vehicleType, speedMph, windowsOpen)
|
||||
@@ -351,42 +351,42 @@ export function calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType = 'c
|
||||
// This would be a useful companion output column (e.g. "Vehicle (moving)")
|
||||
// for road-trip planning, dog-in-car safety at a rest stop vs. motorway, etc.
|
||||
//
|
||||
// ── CYCLIST AT SPEED (companion to the above) ────────────────────────────────
|
||||
// -- CYCLIST AT SPEED (companion to the above) --------------------------------
|
||||
// A cyclist generates their own headwind, so the felt temperature (UTCI) is
|
||||
// very different from a stationary person — even without ambient wind.
|
||||
// very different from a stationary person - even without ambient wind.
|
||||
// Could share the same speed-class approach as the vehicle model:
|
||||
// Slow (10 mph) / Moderate (15 mph) / Fast (25 mph)
|
||||
//
|
||||
// Key differences from the vehicle model:
|
||||
// • The cyclist IS the exposed person — use the UTCI polynomial directly
|
||||
// - The cyclist IS the exposed person - use the UTCI polynomial directly
|
||||
// with va = max(ambientWind, cyclingSpeed * conversionFactor)
|
||||
// • No cabin heating effect — the cyclist gets wind chill, not solar entrapment
|
||||
// • High metabolic heat generation raises core temp (links to the activity
|
||||
// modifier planned in compute.js — cycling at speed is a combined effect)
|
||||
// • UVA/UVB exposure is unchanged — still fully exposed to the sun
|
||||
// - No cabin heating effect - the cyclist gets wind chill, not solar entrapment
|
||||
// - High metabolic heat generation raises core temp (links to the activity
|
||||
// modifier planned in compute.js - cycling at speed is a combined effect)
|
||||
// - UVA/UVB exposure is unchanged - still fully exposed to the sun
|
||||
//
|
||||
// Could be a sub-option within the existing Cycling activity variant rather
|
||||
// than a separate column — e.g. a speed picker in the variant controls.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// than a separate column - e.g. a speed picker in the variant controls.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// VAPOUR PRESSURE — Magnus formula.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Converts air temperature (°C) and relative humidity (%) to vapour
|
||||
// -------------------------------------------------------------------
|
||||
// VAPOUR PRESSURE - Magnus formula.
|
||||
// -------------------------------------------------------------------
|
||||
// Converts air temperature (-C) and relative humidity (%) to vapour
|
||||
// pressure in hPa. Used as the humidity input to utciApprox().
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
export function vaporPressureHpa(Ta, RH) {
|
||||
const es = 6.105 * Math.exp((17.27 * Ta) / (237.7 + Ta));
|
||||
return es * (RH / 100);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SOLAR ELEVATION — NOAA simplified algorithm (degrees above horizon).
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// Accurate to within ~0.01° for most practical purposes. Returns a
|
||||
// -------------------------------------------------------------------
|
||||
// SOLAR ELEVATION - NOAA simplified algorithm (degrees above horizon).
|
||||
// -------------------------------------------------------------------
|
||||
// Accurate to within ~0.01- for most practical purposes. Returns a
|
||||
// negative value when the sun is below the horizon (civil twilight
|
||||
// starts at −6°, nautical at −12°, astronomical at −18°).
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// starts at -6-, nautical at -12-, astronomical at -18-).
|
||||
// -------------------------------------------------------------------
|
||||
export function solarElevationDeg(lat, lon, dateUTC) {
|
||||
const start = Date.UTC(dateUTC.getUTCFullYear(), 0, 0);
|
||||
const diff = dateUTC - start;
|
||||
@@ -422,21 +422,21 @@ export function solarElevationDeg(lat, lon, dateUTC) {
|
||||
return (Math.PI / 2 - zenith) * (180 / Math.PI);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
// MEAN RADIANT TEMPERATURE (Tmrt)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// Tmrt is the uniform temperature of an imaginary enclosure that would
|
||||
// cause the same net radiation exchange as the actual environment.
|
||||
// It accounts for:
|
||||
// • Direct solar beam (DNI), scaled by the projected-area factor fp
|
||||
// • Diffuse sky radiation (scattered and cloud-reflected)
|
||||
// • Ground-reflected shortwave (albedo × global radiation)
|
||||
// • Longwave thermal emission from surrounding surfaces (≈ blackbody at Ta)
|
||||
// - Direct solar beam (DNI), scaled by the projected-area factor fp
|
||||
// - Diffuse sky radiation (scattered and cloud-reflected)
|
||||
// - Ground-reflected shortwave (albedo - global radiation)
|
||||
// - Longwave thermal emission from surrounding surfaces (- blackbody at Ta)
|
||||
//
|
||||
// The fabric index 0.308 (fp formula from ISO 7933) projects the sun
|
||||
// onto a standing person's silhouette as a function of solar elevation.
|
||||
// Output feeds directly into utciApprox() as the Tmrt argument.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
export function calcTmrt(Ta, dirRad, diffRad, globalRad, solElev) {
|
||||
const TaK = Ta + 273.15;
|
||||
let fp = 0;
|
||||
@@ -455,14 +455,14 @@ export function calcTmrt(Ta, dirRad, diffRad, globalRad, solElev) {
|
||||
return TmrtK - 273.15;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// UTCI POLYNOMIAL — DO NOT EDIT (or be VERY careful if you do)
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// This is the official 210-term Bröde et al. (2012) approximation.
|
||||
// -------------------------------------------------------------------
|
||||
// UTCI POLYNOMIAL - DO NOT EDIT (or be VERY careful if you do)
|
||||
// -------------------------------------------------------------------
|
||||
// This is the official 210-term Br-de et al. (2012) approximation.
|
||||
// It takes air temp, mean radiant temp, wind, and humidity and returns
|
||||
// the "felt" temperature. Every number is a peer-reviewed coefficient.
|
||||
// Scroll past it — there's nothing here you'll want to change.
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Scroll past it - there's nothing here you'll want to change.
|
||||
// -------------------------------------------------------------------
|
||||
export function utciApprox(Ta, Tmrt, va10, ehPa) {
|
||||
const va = Math.max(0.5, Math.min(17, va10));
|
||||
const D_Tmrt = Tmrt - Ta;
|
||||
|
||||
Reference in New Issue
Block a user