fix incons
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import * as m from '$lib/paraglide/messages';
|
||||
|
||||
const weatherCodes: Record<number, string> = {
|
||||
0: 'clear',
|
||||
1: 'clear',
|
||||
2: 'cloudy',
|
||||
3: 'cloudy',
|
||||
3: 'overcast',
|
||||
4: 'fog',
|
||||
5: 'fog',
|
||||
10: 'fog',
|
||||
@@ -30,26 +32,26 @@ const weatherCodes: Record<number, string> = {
|
||||
42: 'rain',
|
||||
43: 'sprinkle',
|
||||
44: 'rain',
|
||||
45: 'hail',
|
||||
45: 'fog',
|
||||
46: 'hail',
|
||||
47: 'snow',
|
||||
48: 'snow',
|
||||
48: 'fog',
|
||||
50: 'sprinkle',
|
||||
51: 'sprinkle',
|
||||
52: 'rain',
|
||||
53: 'rain',
|
||||
53: 'sprinkle',
|
||||
54: 'sprinkle',
|
||||
55: 'rain',
|
||||
56: 'rain-mix',
|
||||
57: 'sprinkle',
|
||||
57: 'rain-mix',
|
||||
58: 'rain',
|
||||
60: 'sprinkle',
|
||||
61: 'sprinkle',
|
||||
62: 'rain',
|
||||
63: 'rain',
|
||||
64: 'hail',
|
||||
65: 'hail',
|
||||
66: 'hail',
|
||||
65: 'rain',
|
||||
66: 'rain-mix',
|
||||
67: 'rain-mix',
|
||||
68: 'rain-mix',
|
||||
70: 'snow',
|
||||
@@ -61,13 +63,13 @@ const weatherCodes: Record<number, string> = {
|
||||
76: 'snow',
|
||||
77: 'snow',
|
||||
78: 'snow',
|
||||
80: 'rain',
|
||||
81: 'sprinkle',
|
||||
80: 'showers',
|
||||
81: 'showers',
|
||||
82: 'rain',
|
||||
83: 'rain',
|
||||
84: 'storm-showers',
|
||||
85: 'rain-mix',
|
||||
86: 'rain-mix',
|
||||
85: 'snow',
|
||||
86: 'snow',
|
||||
87: 'rain-mix',
|
||||
89: 'hail',
|
||||
90: 'lightning',
|
||||
@@ -77,18 +79,68 @@ const weatherCodes: Record<number, string> = {
|
||||
94: 'lightning',
|
||||
95: 'thunderstorm',
|
||||
96: 'thunderstorm',
|
||||
99: 'tornado'
|
||||
99: 'storm-showers'
|
||||
};
|
||||
|
||||
// These conditions ship only as a single neutral glyph (no day/night variant).
|
||||
const NEUTRAL_ICONS = new Set(['snowflake-cold', 'strong-wind', 'dust', 'tornado']);
|
||||
// Conditions that ship as a single neutral glyph (no day/night variant). The
|
||||
// file is not always wi-<name>: 'overcast' uses the flat cloud, which keeps it
|
||||
// distinct from 'cloudy' (code 2), whose glyph carries a sun or moon.
|
||||
const NEUTRAL_ICONS: Record<string, string> = {
|
||||
'snowflake-cold': 'wi-snowflake-cold',
|
||||
'strong-wind': 'wi-strong-wind',
|
||||
dust: 'wi-dust',
|
||||
tornado: 'wi-tornado',
|
||||
overcast: 'wi-cloudy'
|
||||
};
|
||||
|
||||
export function getWeatherIconName(code: number, daytime: boolean): string {
|
||||
const name = weatherCodes[code as keyof typeof weatherCodes] ?? 'clear';
|
||||
if (NEUTRAL_ICONS.has(name)) return `wi-${name}`;
|
||||
const neutral = NEUTRAL_ICONS[name];
|
||||
if (neutral) return neutral;
|
||||
return `wi-${daytime ? 'day' : 'night'}-${name}`;
|
||||
}
|
||||
|
||||
// Plain-language name for each code open-meteo actually emits (WMO 4677 subset),
|
||||
// used as the hover title on the pictograms. A glyph alone is ambiguous - the
|
||||
// hail-thunderstorm swirl in particular reads as something far more dramatic
|
||||
// than "thunderstorm with heavy hail".
|
||||
const WMO_DESCRIPTIONS: Record<number, () => string> = {
|
||||
0: m.wmo_0,
|
||||
1: m.wmo_1,
|
||||
2: m.wmo_2,
|
||||
3: m.wmo_3,
|
||||
45: m.wmo_45,
|
||||
48: m.wmo_48,
|
||||
51: m.wmo_51,
|
||||
53: m.wmo_53,
|
||||
55: m.wmo_55,
|
||||
56: m.wmo_56,
|
||||
57: m.wmo_57,
|
||||
61: m.wmo_61,
|
||||
63: m.wmo_63,
|
||||
65: m.wmo_65,
|
||||
66: m.wmo_66,
|
||||
67: m.wmo_67,
|
||||
71: m.wmo_71,
|
||||
73: m.wmo_73,
|
||||
75: m.wmo_75,
|
||||
77: m.wmo_77,
|
||||
80: m.wmo_80,
|
||||
81: m.wmo_81,
|
||||
82: m.wmo_82,
|
||||
85: m.wmo_85,
|
||||
86: m.wmo_86,
|
||||
95: m.wmo_95,
|
||||
96: m.wmo_96,
|
||||
99: m.wmo_99
|
||||
};
|
||||
|
||||
/** Localized condition text for a weather code; '' for codes we have no name for. */
|
||||
export function getWeatherDescription(code: number | null | undefined): string {
|
||||
if (code == null || !Number.isFinite(code)) return '';
|
||||
return WMO_DESCRIPTIONS[code]?.() ?? '';
|
||||
}
|
||||
|
||||
// ─── Local day/night weather codes ──────────────────────────────────────────
|
||||
// Open-meteo's daily weather_code is a plain numeric max over all 24 hourly
|
||||
// codes (VariableDaily.swift: `.max(.weathercode)`) — there is no day/night
|
||||
@@ -113,8 +165,17 @@ const FOG = new Set([45, 48]);
|
||||
// counterparts when breaking frequency ties (fixes 80 "outranking" 65).
|
||||
const INTENSITY_EQUIV: Record<number, number> = { 80: 61, 81: 63, 82: 65, 77: 71, 85: 71, 86: 75 };
|
||||
|
||||
/** Most frequent code; ties go to the more intense (then higher) code. */
|
||||
function modeWithHighTiebreak(codes: number[]): number {
|
||||
/**
|
||||
* Most frequent code; ties go to the more intense (then higher) code, or with
|
||||
* `preferLower` to the least intense one.
|
||||
*
|
||||
* Thunder is the one group that ties downwards. Within it the code only
|
||||
* describes how much hail comes with the storm, and letting the worst of them
|
||||
* win a coin-flip tie is how a single hour of 99 used to brand a whole day as
|
||||
* the most extreme thing on the scale. Elsewhere the heavier code winning a tie
|
||||
* is the point (heavy rain over slight showers).
|
||||
*/
|
||||
function modeWithHighTiebreak(codes: number[], preferLower = false): number {
|
||||
const counts = new Map<number, number>();
|
||||
for (const c of codes) counts.set(c, (counts.get(c) ?? 0) + 1);
|
||||
let best = codes[0];
|
||||
@@ -122,11 +183,10 @@ function modeWithHighTiebreak(codes: number[]): number {
|
||||
for (const [code, count] of counts) {
|
||||
const intensity = INTENSITY_EQUIV[code] ?? code;
|
||||
const bestIntensity = INTENSITY_EQUIV[best] ?? best;
|
||||
if (
|
||||
count > bestCount ||
|
||||
(count === bestCount &&
|
||||
(intensity > bestIntensity || (intensity === bestIntensity && code > best)))
|
||||
) {
|
||||
const winsTie = preferLower
|
||||
? intensity < bestIntensity || (intensity === bestIntensity && code < best)
|
||||
: intensity > bestIntensity || (intensity === bestIntensity && code > best);
|
||||
if (count > bestCount || (count === bestCount && winsTie)) {
|
||||
best = code;
|
||||
bestCount = count;
|
||||
}
|
||||
@@ -144,7 +204,7 @@ function daypartCode(codes: number[]): number | null {
|
||||
// (open-meteo's "don't hide hazards" philosophy, kept per-group).
|
||||
for (const group of [THUNDER, FREEZING, SNOW, RAIN, DRIZZLE]) {
|
||||
const hits = hours.filter((c) => group.has(c));
|
||||
if (hits.length > 0) return modeWithHighTiebreak(hits);
|
||||
if (hits.length > 0) return modeWithHighTiebreak(hits, group === THUNDER);
|
||||
}
|
||||
|
||||
// Fog needs persistence (≥2h and ≥¼ of the daypart) so one misty hour at
|
||||
|
||||
Reference in New Issue
Block a user