3.8.5
Correct 'Shade' temps for night
This commit is contained in:
+7
-4
@@ -379,9 +379,9 @@ export function UTCIForecast() {
|
||||
// Columns that mark the start of a logical group - used to draw a faint
|
||||
// vertical border separating groups in the forecast table.
|
||||
const GROUP_ORDER = {
|
||||
felt: ['utciP', 'vehicleT', 'indoorT', 'managedT', 'petHomeT', 'burn', 'utci', 'delta', 'tmrt'],
|
||||
felt: ['utciP', 'shadeT', 'vehicleT', 'indoorT', 'managedT', 'petHomeT', 'burn', 'utci', 'delta', 'tmrt'],
|
||||
surface: ['concreteT', 'soilT', 'soilT6', 'soilM', 'furSurfaceT', 'pawT'],
|
||||
ambient: ['shadeT', 'petShadeT', 'air', 'rh', 'dew'],
|
||||
ambient: ['petShadeT', 'air', 'rh', 'dew'],
|
||||
precip: ['precip', 'precipProb', 'lightning'],
|
||||
sky: ['cloud', 'vis'],
|
||||
wind: ['wind', 'dir'],
|
||||
@@ -1351,6 +1351,7 @@ export function UTCIForecast() {
|
||||
</span>
|
||||
</th>
|
||||
${visibleCols.utciP && html`<th class=${`col-info-th ${groupStart('utciP')} ${groupColor('utciP')}`} scope="col" onClick=${(e) => handleThClick('utciP', e)} onMouseEnter=${(e) => handleThEnter('utciP', e)} onMouseLeave=${handleThLeave}>SunSoak <span class="col-unit">°C felt</span>${UTCI_ENVIRONMENTS[utciEnv]?.shortLabel ? html`<span class="col-env-badge">${UTCI_ENVIRONMENTS[utciEnv].shortLabel}</span>` : null}</th>`}
|
||||
${visibleCols.shadeT && html`<th class=${`utci-tight-head col-info-th ${groupStart('shadeT')} ${groupColor('shadeT')}`} scope="col" onClick=${(e) => handleThClick('shadeT', e)} onMouseEnter=${(e) => handleThEnter('shadeT', e)} onMouseLeave=${handleThLeave}>Shade <span class="col-unit">°C felt</span></th>`}
|
||||
${visibleCols.vehicleT && html`<th class=${`col-info-th ${groupStart('vehicleT')} ${groupColor('vehicleT')}`} scope="col" onClick=${(e) => handleThClick('vehicleT', e)} onMouseEnter=${(e) => handleThEnter('vehicleT', e)} onMouseLeave=${handleThLeave}>Vehicle <span class="col-unit">°C peak</span></th>`}
|
||||
${indoorMode === 'on' && !indoorManaged && html`<th class=${`col-info-th ${groupStart('indoorT')} ${groupColor('indoorT')}`} scope="col" onClick=${(e) => handleThClick('indoorT', e)} onMouseEnter=${(e) => handleThEnter('indoorT', e)} onMouseLeave=${handleThLeave}>Indoors <span class="col-unit">°C est.</span></th>`}
|
||||
${indoorMode === 'on' && indoorManaged && html`<th class=${`col-info-th ${groupStart('managedT')} ${groupColor('managedT')}`} scope="col" onClick=${(e) => handleThClick('managedT', e)} onMouseEnter=${(e) => handleThEnter('managedT', e)} onMouseLeave=${handleThLeave}>Managed <span class="col-unit">°C est.</span></th>`}
|
||||
@@ -1365,7 +1366,6 @@ export function UTCIForecast() {
|
||||
${visibleCols.soilT && html`<th class=${`col-info-th ${groupStart('soilT')} ${groupColor('soilT')}`} scope="col" onClick=${(e) => handleThClick('soilT', e)} onMouseEnter=${(e) => handleThEnter('soilT', e)} onMouseLeave=${handleThLeave}>Soil °C <span class="col-unit">surface</span></th>`}
|
||||
${visibleCols.soilT6 && html`<th class=${`col-info-th ${groupStart('soilT6')} ${groupColor('soilT6')}`} scope="col" onClick=${(e) => handleThClick('soilT6', e)} onMouseEnter=${(e) => handleThEnter('soilT6', e)} onMouseLeave=${handleThLeave}>Soil 6cm <span class="col-unit">°C root</span></th>`}
|
||||
${visibleCols.soilM && html`<th class=${`col-info-th ${groupStart('soilM')} ${groupColor('soilM')}`} scope="col" onClick=${(e) => handleThClick('soilM', e)} onMouseEnter=${(e) => handleThEnter('soilM', e)} onMouseLeave=${handleThLeave}>Soil moist <span class="col-unit">%</span></th>`}
|
||||
${visibleCols.shadeT && html`<th class=${`utci-tight-head col-info-th ${groupStart('shadeT')} ${groupColor('shadeT')}`} scope="col" onClick=${(e) => handleThClick('shadeT', e)} onMouseEnter=${(e) => handleThEnter('shadeT', e)} onMouseLeave=${handleThLeave}>Shade <span class="col-unit">°C</span></th>`}
|
||||
${visibleCols.petShadeT && html`<th class=${`utci-tight-head col-info-th ${groupStart('petShadeT')} ${groupColor('petShadeT')}`} scope="col" onClick=${(e) => handleThClick('petShadeT', e)} onMouseEnter=${(e) => handleThEnter('petShadeT', e)} onMouseLeave=${handleThLeave}>Pet Shade <span class="col-unit">°C</span></th>`}
|
||||
${visibleCols.air && html`<th class=${`utci-tight-head col-info-th ${groupStart('air')} ${groupColor('air')}`} scope="col" onClick=${(e) => handleThClick('air', e)} onMouseEnter=${(e) => handleThEnter('air', e)} onMouseLeave=${handleThLeave}>Air <span class="col-unit">°C</span></th>`}
|
||||
${visibleCols.rh && html`<th class=${`col-info-th ${groupStart('rh')} ${groupColor('rh')}`} scope="col" onClick=${(e) => handleThClick('rh', e)} onMouseEnter=${(e) => handleThEnter('rh', e)} onMouseLeave=${handleThLeave}>RH <span class="col-unit">%</span></th>`}
|
||||
@@ -1521,6 +1521,10 @@ export function UTCIForecast() {
|
||||
<td class=${groupStart('utciP')} style=${{ background: airTempBg(r.utciAdj, rPrev?.utciAdj, rNext?.utciAdj), color: airTempFontColor(), fontSize: '13px' }}>
|
||||
${fmt(r.utciAdj)}${u('°C')}
|
||||
</td>`}
|
||||
${visibleCols.shadeT && html`
|
||||
<td class=${groupStart('shadeT')} style=${{ color: airTempFontColor(), background: airTempBg(r.shadeT, rPrev?.shadeT, rNext?.shadeT) }}>
|
||||
${r.shadeT != null ? fmt(r.shadeT) + u('°C') : '—'}
|
||||
</td>`}
|
||||
${visibleCols.vehicleT && html`
|
||||
<td class=${groupStart('vehicleT')} style=${{ color: airTempFontColor(), background: airTempBg(r.vehicleT, rPrev?.vehicleT, rNext?.vehicleT) }}>
|
||||
${fmt(r.vehicleT)}${u('°C')}
|
||||
@@ -1571,7 +1575,6 @@ export function UTCIForecast() {
|
||||
<td class=${groupStart('soilT6')} style=${{ color: airTempFontColor(), background: airTempBg(r.soilT6, rPrev?.soilT6, rNext?.soilT6) }}>${r.soilT6 != null ? fmt(r.soilT6) + u('°C') : '—'}</td>`}
|
||||
${visibleCols.soilM && html`
|
||||
<td class=${groupStart('soilM')} style=${(() => { const sm = soilMoistureBg(r.soilM); return { color: sm.fg, background: sm.bg }; })()}>${r.soilM != null ? fmt(r.soilM * 100, 1) + u('%') : '—'}</td>`}
|
||||
${visibleCols.shadeT && html`<td class=${groupStart('shadeT')} style=${{ background: airTempBg(r.shadeT, rPrev?.shadeT, rNext?.shadeT), color: airTempFontColor() }}>${r.shadeT != null ? fmt(r.shadeT) + u('°C') : '—'}</td>`}
|
||||
${visibleCols.petShadeT && html`
|
||||
<td class=${groupStart('petShadeT')} title=${r.petShadeT != null ? (petCategory(r.petShadeT)?.label ?? '') : ''} style=${{ color: airTempFontColor(), background: petAirTempBg(r.petShadeT, rPrev?.petShadeT, rNext?.petShadeT) }}>
|
||||
${r.petShadeT != null ? fmt(r.petShadeT) + u('°C') : '—'}
|
||||
|
||||
+20
-12
@@ -14,7 +14,7 @@
|
||||
import {
|
||||
vaporPressureHpa, solarElevationDeg, calcTmrt, utciApprox,
|
||||
calcConcreteTempPass, calcVehicleInteriorTempPass,
|
||||
calcIndoorTempPass, calcManagedIndoorTempPass, calcShadeAirTemp,
|
||||
calcIndoorTempPass, calcManagedIndoorTempPass, calcShadeAirTemp, calcShadeFeltTemp,
|
||||
calcFurSurfaceTempPass,
|
||||
} from './physics.js';
|
||||
import { windCompass8, uvSplit, cloudCategory, precipPenalty, sunburnMinutes, burnLabel, FUR_COLORS } from './utils.js';
|
||||
@@ -84,15 +84,22 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
||||
const difEnv = dif * env.difFactor;
|
||||
const globEnv = glob * env.globFactor;
|
||||
// concreteT is now stamped in the two-pass section below (thermal lag).
|
||||
// Shade air temp: the air you'd feel in this Solar Model's typical shade
|
||||
// (building shadow, beach umbrella, canopy...). Shares SunSoak's TaEnv
|
||||
// baseline and env-reduced radiation, so the two columns stay consistent.
|
||||
const shadeT = calcShadeAirTemp(TaEnv, dirEnv + difEnv * 0.2, va, elev, env.shade);
|
||||
// vehicleT is stamped in the two-pass section below (thermal lag).
|
||||
const eh = vaporPressureHpa(Ta, RH);
|
||||
const Tmrt = calcTmrt(TaEnv, dirEnv, difEnv, globEnv, elev);
|
||||
const utci = utciApprox(TaEnv, Tmrt, va, eh);
|
||||
const utciAdj = utci + precipPenalty(precip, snow, va);
|
||||
// Shade air temp: what a thermometer in this Solar Model's typical shade
|
||||
// (building shadow, beach umbrella, canopy...) would read. Shares
|
||||
// SunSoak's TaEnv baseline and env-reduced radiation, so the two stay
|
||||
// consistent. Pet Shade reads this directly; the human Shade column
|
||||
// below turns it into a felt temperature first.
|
||||
const shadeAirT = calcShadeAirTemp(TaEnv, dirEnv + difEnv * 0.2, va, elev, env.shade);
|
||||
// Shade (felt): SunSoak with the direct beam taken away — still outdoors,
|
||||
// so the same wind, humidity and rain penalty apply. See
|
||||
// calcShadeFeltTemp() for why this is a felt number and not the air temp.
|
||||
const shadeT = calcShadeFeltTemp(shadeAirT, difEnv, va, eh, elev, env.shade)
|
||||
+ precipPenalty(precip, snow, va);
|
||||
// Derived
|
||||
const compass = windCompass8(wd);
|
||||
const { uvA, uvB } = uvSplit(uv, elev);
|
||||
@@ -154,10 +161,11 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
||||
uv, uvA, uvB,
|
||||
precip, precipProb, lightning, cape, snow,
|
||||
soilT0, soilT6, soilM, shadeT,
|
||||
// Pet Shade reads the same hourly value as Shade but aggregates as a
|
||||
// MEAN rather than a worst case, so it needs its own field to diverge
|
||||
// from shadeT at 2h/3h/4h. Same for pawT vs concreteT below.
|
||||
petShadeT: shadeT,
|
||||
// Pet Shade stays on the shade AIR temperature: the human Shade column
|
||||
// is a UTCI felt number on a human comfort scale, which says nothing
|
||||
// useful about a cat. It also aggregates as a MEAN rather than a worst
|
||||
// case, so it needs its own field either way (same for pawT/concreteT).
|
||||
petShadeT: shadeAirT,
|
||||
effectiveRad,
|
||||
elev, Tmrt, utci, utciAdj, eh, compass,
|
||||
visKm, aqi,
|
||||
@@ -723,7 +731,7 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
||||
// ── Pets (cats / small dogs) ────────────────────────────────────────────
|
||||
if (profile === 'pets') {
|
||||
const peakFur = peakRow('furSurfaceT');
|
||||
const peakShade = peakRow('shadeT');
|
||||
const peakShade = peakRow('petShadeT');
|
||||
const peakIndoor = peakRow('indoorT');
|
||||
const maxPollen = Math.max(0, ...todayRows.map(r => r.grassPollen ?? 0));
|
||||
const peakAqi = Math.max(0, ...todayRows.map(r => r.aqi ?? 0));
|
||||
@@ -740,8 +748,8 @@ export function computeGlanceSummary(todayRows, profile, variant, skinType, cols
|
||||
...(show('petShadeT') ? [{
|
||||
icon: '🌳',
|
||||
label: 'Shade for pet',
|
||||
value: peakShade ? `${Math.round(peakShade.shadeT)}° at ${hhmm(peakShade.iso)}` : '-',
|
||||
alert: !!(peakShade && peakShade.shadeT >= 26),
|
||||
value: peakShade ? `${Math.round(peakShade.petShadeT)}° at ${hhmm(peakShade.iso)}` : '-',
|
||||
alert: !!(peakShade && peakShade.petShadeT >= 26),
|
||||
grp: 'ambient',
|
||||
}] : []),
|
||||
...(show('petHomeT') ? [{
|
||||
|
||||
+8
-5
@@ -143,8 +143,9 @@ export const POLLEN_TYPES = {
|
||||
// taOffset - offset applied to air temperature in degrees C (evapotranspiration etc.)
|
||||
// label - short display name
|
||||
// note - shown in column header tooltip to flag modified calculation
|
||||
// shade - per-environment microclimate inputs for the Shade air-temp
|
||||
// column (see calcShadeAirTemp in physics.js). The env air-temp
|
||||
// shade - per-environment microclimate inputs for the Shade column
|
||||
// (calcShadeAirTemp + calcShadeFeltTemp in physics.js, where
|
||||
// shelter doubles as the sky-view proxy). The env air-temp
|
||||
// shift is NOT here - it comes from taOffset via the shared
|
||||
// TaEnv baseline. This block only carries local sun-trap effects:
|
||||
// shelter 0-1 wind shelter (high = enclosed, calm pocket)
|
||||
@@ -195,7 +196,9 @@ export function deriveProfileMain(activeProfile, outdoorsVariant) {
|
||||
const isPetsProfile = activeProfile === 'pets';
|
||||
const mainConfigKey = isHomeOrOffice ? 'indoor' : isVehicleProfile ? 'vehicle' : isPetsProfile ? 'fur' : 'solar';
|
||||
const mainField = isHomeOrOffice ? 'indoorT' : isVehicleProfile ? 'vehicleT' : isPetsProfile ? 'furSurfaceT' : 'utciAdj';
|
||||
const secondaryField = isHomeOrOffice ? 'managedT' : isPetsProfile ? 'shadeT' : isVehicleProfile ? null : 'shadeT';
|
||||
// Pets pairs Fur Temp with PET Shade: the human Shade column is now a UTCI
|
||||
// felt number on a human comfort scale, which says nothing about a cat.
|
||||
const secondaryField = isHomeOrOffice ? 'managedT' : isPetsProfile ? 'petShadeT' : isVehicleProfile ? null : 'shadeT';
|
||||
const mainLabel = activeProfile === 'outdoors'
|
||||
? (OUTDOORS_VARIANTS[outdoorsVariant]?.name || FILTER_PROFILES.outdoors.label)
|
||||
: (FILTER_PROFILES[activeProfile]?.label || 'SunSoak');
|
||||
@@ -260,7 +263,7 @@ export const workVariantKeys = ['farming', 'construction', 'market', 'windowclea
|
||||
export const COL_DESCRIPTIONS = {
|
||||
hour: { title: 'Rows', desc: 'Local wall-clock time for this forecast row. Each row covers the interval set by the stepper in this header, clock-aligned from midnight — at 3h, a row labelled 09:00 spans 09:00–11:59. Totals like rainfall are summed across the block, risk figures show the block\'s peak, and smooth readings like air temperature are averaged.' },
|
||||
air: { title: 'Air Temperature', desc: 'Measured air temperature at 2 m above the ground. This is the standard thermometer reading — it does not account for sun, wind, or humidity.' },
|
||||
shadeT: { title: 'Shade', desc: 'Estimated air temperature sitting in the typical shade of the selected Solar Model — building shadow, beach umbrella, tree canopy, boat panels, and so on. Sheltered, sun-warmed surroundings push it above the official open-ground Air figure on calm sunny hours; wind and cloud pull it back toward it. This is an estimate, not a measurement, and differs from the standardised 2 m Air reading.' },
|
||||
shadeT: { title: 'Shade', desc: 'SunSoak with the sun taken away: the felt temperature standing in the typical shade of the selected Solar Model — building shadow, beach umbrella, tree canopy, boat panels, and so on. You are still outdoors, so wind, humidity and rain hit you exactly as they do in the open; only the direct beam and part of the sky are blocked. Expect several degrees below SunSoak in daytime sun, and near-identical readings after dark, when there is no sun to stand out of. An estimate of a typical shaded spot, not a measurement.' },
|
||||
rh: { title: 'Relative Humidity', desc: 'How much moisture the air holds relative to its maximum capacity at that temperature. High RH makes warm days feel stickier and cold days feel rawer.' },
|
||||
dew: { title: 'Dew Point', desc: 'The temperature at which air becomes saturated and moisture begins to condense. A useful measure of absolute humidity — above 16 °C it starts to feel muggy; above 21 °C it feels oppressive.' },
|
||||
wind: { title: 'Wind Speed', desc: 'Mean wind speed at 10 m in mph, with peak gust in brackets where significantly higher. Gust colour indicates Beaufort scale severity: amber = Near Gale (32+ mph), orange = Gale (39+ mph), red = Severe Gale or above (55+ mph). Banners above the table classify conditions from Strong Breeze through to Violent Storm using standard wind categories. Wind dramatically increases heat loss from exposed skin — the basis of wind chill.' },
|
||||
@@ -291,7 +294,7 @@ export const COL_DESCRIPTIONS = {
|
||||
pollen: { title: 'Pollen', desc: 'Pollen concentration in grains per cubic metre for the selected pollen type, sourced from the Copernicus CAMS pollen forecast. Low = <10; Moderate = 10–50; High = 50–200; Very High = 200+. Values vary by species and season.' },
|
||||
furSurfaceT: { title: 'Fur Temp', desc: 'Estimated fur surface temperature a cat or small dog experiences in direct sun, calibrated for a thin/short coat rather than a heavily-insulated breed. Runs hotter than air temperature on sunny, still days — set fur colour with the selector alongside this column.' },
|
||||
pawT: { title: 'Paw Temp', desc: 'Estimated ground/pavement surface temperature for paw contact, colour-coded by burn risk: green below 40 °C (safe), amber 40–52 °C (discomfort / possible burn on prolonged contact), red above 52 °C (can burn within 60 seconds). If you can\'t hold the back of your hand on the surface for 7 seconds, it\'s too hot to walk your pet.' },
|
||||
petShadeT: { title: 'Pet Shade', desc: 'Shaded air temperature for a resting pet, using the same shade estimate as the Shade column but with a lower, pet-specific alert threshold — small pets have far less capacity to dump heat than humans.' },
|
||||
petShadeT: { title: 'Pet Shade', desc: 'Shaded air temperature for a resting pet, using the same shade microclimate model as the Shade column but kept as an air temperature — a human felt-temperature index says little about an animal — and with a lower, pet-specific alert threshold — small pets have far less capacity to dump heat than humans.' },
|
||||
petHomeT: { title: 'Pet Home', desc: 'Indoor temperature estimate for a pet left home alone, using the same model as the Indoors column but with a lower, pet-specific alert threshold (26 °C) rather than the human heatwave guidance threshold.' },
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ const COLS = [
|
||||
['Tmrt', '°C', 'right', 7, '0.0'],
|
||||
// Air (4)
|
||||
['Air Temp', '°C', 'right', 8, '0.0'],
|
||||
['Shade Air', '°C', 'right', 9, '0.0'],
|
||||
['Shade Felt', '°C', 'right', 9, '0.0'],
|
||||
['Dew Point', '°C', 'right', 9, '0.0'],
|
||||
['Humidity', '%', 'right', 8, '0' ],
|
||||
// UV & Solar (7)
|
||||
|
||||
+49
-2
@@ -14,6 +14,7 @@
|
||||
// calcVehicleInteriorTemp(Ta, globalRad, solElev, vehicleType, ventilated, speedMph, windMps) equilibrium cabin temp
|
||||
// calcVehicleInteriorTempPass(TaArr, globArr, elevArr, vaArr, vehicleType, ventilated, speedMph) lagged cabin temp
|
||||
// calcShadeAirTemp(TaEnv, effRadEnv, va, elev, shade) per-env shade air temp
|
||||
// calcShadeFeltTemp(TaShade, difEnv, va, eh, elev, shade) felt temp in that shade
|
||||
// vaporPressureHpa(Ta, RH) Magnus formula - hPa
|
||||
// solarElevationDeg(lat, lon, dateUTC) NOAA simplified solar position (-)
|
||||
// calcTmrt(Ta, dirRad, diffRad, globalRad, solElev) Mean radiant temperature
|
||||
@@ -601,9 +602,55 @@ export function calcShadeAirTemp(Ta, effectiveRad, va, elev, shade) {
|
||||
const daytime = (elev != null && elev > 0) ? 1 : 0;
|
||||
const sunTerm = SHADE_K_SUN * (shade.sun ?? 0) * rad * daytime;
|
||||
const windTerm = SHADE_K_WIND * (1 - (shade.shelter ?? 0)) * Math.max(0, va || 0);
|
||||
const adj = sunTerm - windTerm;
|
||||
// Wind can only cancel the sun-trap warming, never push the pocket BELOW
|
||||
// ambient: mixing drags trapped air back toward the open-air reading, it
|
||||
// does not manufacture cold. Without the floor a breezy night read a
|
||||
// degree under Air for no physical reason (and Pet Shade with it).
|
||||
// Genuinely cool environments still sit below Air via env.taOffset.
|
||||
const adj = Math.max(0, sunTerm - windTerm);
|
||||
// Local sun-trap nudge only - the env air-temp shift is already in Ta.
|
||||
return Ta + Math.max(-4, Math.min(5, adj));
|
||||
return Ta + Math.min(5, adj);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// SHADE FELT TEMPERATURE - "SunSoak, but out of the sun"
|
||||
// -------------------------------------------------------------------
|
||||
// calcShadeAirTemp() above answers "what would a thermometer in the shade
|
||||
// read". That is NOT what someone standing in the shade feels: they are
|
||||
// still outdoors, so wind still strips heat off them and humidity still
|
||||
// blunts their sweating, exactly as in the open. Reading a shade AIR temp
|
||||
// beside SunSoak's FELT temp made Shade look hotter than full sun on a
|
||||
// breezy night - both numbers were right, but they were different
|
||||
// quantities sitting in adjacent columns.
|
||||
//
|
||||
// So Shade runs the same UTCI machinery as SunSoak, changing one input:
|
||||
// the radiant load. Out of the beam a person sees no direct sun, only the
|
||||
// part of the sky their shade does not block, so:
|
||||
//
|
||||
// direct -> 0 (that is what shade IS)
|
||||
// diffuse -> difEnv * skyView
|
||||
// global -> the same diffuse (no beam reaching the ground to reflect)
|
||||
//
|
||||
// skyView derives from shade.shelter, reusing it as an enclosure proxy: a
|
||||
// forest canopy or building canyon that stops the wind also hides most of
|
||||
// the sky dome, while an umbrella on open sand blocks little besides the
|
||||
// sun itself. It is a proxy, not a measurement - like the rest of the
|
||||
// shade block, this is an estimate of a typical spot in that environment.
|
||||
//
|
||||
// Consequences worth knowing: at night there is no beam to remove, so
|
||||
// Shade converges on SunSoak (they differ only by the sun-trap nudge in
|
||||
// the air temp). In daytime sun Shade sits several degrees under SunSoak,
|
||||
// which is the whole point of standing in it. On a windy hour BOTH fall
|
||||
// together, because both now carry the same wind term.
|
||||
// -------------------------------------------------------------------
|
||||
export const SHADE_SKY_BLOCK = 0.6; // fraction of the sky an enclosed shade hides, per unit shelter
|
||||
|
||||
export function calcShadeFeltTemp(TaShade, difRad, va, eh, elev, shade) {
|
||||
if (TaShade == null) return null;
|
||||
const skyView = 1 - SHADE_SKY_BLOCK * (shade?.shelter ?? 0);
|
||||
const difShade = Math.max(0, (difRad || 0) * skyView);
|
||||
const Tmrt = calcTmrt(TaShade, 0, difShade, difShade, elev);
|
||||
return utciApprox(TaShade, Tmrt, va, eh);
|
||||
}
|
||||
|
||||
// -- Pets Profile - Fur Surface Temperature -----------------------------------
|
||||
|
||||
Reference in New Issue
Block a user