const nwsEndpoint = 'https://api.weather.gov/gridpoints/MPX/105,74/forecast/hourly?units=us';
const nwsApiNewsLink = 'https://github.com/weather-gov/api/discussions/688';
function setCSSVars(degF) {
// The temperature → hue formula
var tempHue = 210 - (degF * 1.5);
if(tempHue < 0) tempHue = 360 + tempHue;
var accent = tempHue - 26;
if(accent < 0) accent = accent + 360;
document.documentElement.style.setProperty('--base-temp', tempHue);
document.documentElement.style.setProperty('--accent', accent);
}
async function updateWeatherDiv() {
const weatherDiv = document.getElementById('weather');
const slider = document.getElementById("myRange");
weatherDiv.innerHTML = 'Getting weather…';
try {
const weather = await loadWeather();
setCSSVars(weather.temp);
if(weather.status == "guess") {
weatherDiv.innerHTML = `Robbinsdale, MN: <abbr title="National Weather Service API is on break right now">${weather.conditions}</abbr> <a href="/mnwx.html">📸</a> `;
} else {
weatherDiv.innerHTML = `Robbinsdale, MN: ${properTemp(weather.temp)}, ${weather.conditions} <a href="/mnwx.html">📸</a>`;
}
slider.value = weather.temp;
} catch (e) {
weatherDiv.innerHTML = e.message;
}
}
const updateButton = document.getElementById("reset-weather");
updateButton.onclick = updateWeatherDiv;
async function loadWeather() {
let w = JSON.parse(localStorage.getItem('weather'));
if(w === null || Date.now() - w.timestamp > 3600000) {
w = await fetchWeather();
localStorage.setItem('weather', JSON.stringify(w));
}
return w;
}
const delay = (retryCount) => new Promise((res) => setTimeout(res, 10 ** retryCount));
async function fetchWeather(retryCount = 0) {
console.log('Updating weather data cache…');
try {
const response = await fetch('https://api.weather.gov/gridpoints/MPX/105,74/forecast/hourly?units=us');
if(!response.ok) {
const e = new Error(response.statusText);
e.code = response.status;
throw e;
}
const weatherJson = await response.json();
const weather = weatherJson.properties.periods[0];
return {
"status": "success",
"timestamp" : Date.now(),
"temp" : weather.temperature,
"conditions" : weather.shortForecast
};
} catch (e) {
console.log(`Attempt ${retryCount} failed: ${e.message} / ${e.code}`);
if (e.code >= 500 || e.message.match(/(?:Load failed|NetworkError)/gi) || retryCount > 3) {
return guessConditions();
} else {
await delay(retryCount + 1);
return fetchWeather(retryCount + 1);
}
}
}
updateWeatherDiv();
document.getElementById("myRange").oninput = function() {
const weatherDiv = document.getElementById('weather');
setCSSVars(this.value);
weatherDiv.innerHTML = "Let’s pretend it’s " + properTemp(this.value);
}
function properTemp(degF) {
const temp = probablyInAmerica() ? degF + "°F" :
Math.round((degF - 32) * 5 / 9) + "°C";
return temp.replaceAll("-", "−"); // proper minus sign
}
function guessConditions() {
// https://www.weather.gov/wrh/Climate?wfo=mpx
const avgTemps = [16,20,33,47,59,69,74,71,63,49,34,15];
const temperatureWords = [
["January", "frosty", "frigid", "icy", "freezing"],
["February", "chill", "frostbitten", "snowy", "arctic"],
["March", "cold", "nippy", "sleety", "blustery"],
["April", "cool", "crisp", "rainy", "muddy"],
["May", "mild", "temperate", "gentle", "refreshing"],
["June", "warm", "balmy", "sunny", "humid"],
["July", "hot", "sultry", "sweltering", "muggy"],
["August", "scorching", "boiling", "roasting", "stifling"],
["September", "warm", "pleasant", "temperate", "golden"],
["October", "crisp", "cool", "refreshing", "autumnal"],
["November", "chilly", "frosty", "wintry", "dreary"],
["December", "frigid", "snowy", "icy", "frosty"]
];
const d = new Date();
console.log(avgTemps[d.getMonth()]);
let tempWord = temperatureWords[d.getMonth()][Math.floor(Math.random() * 4) + 1];
return {
"status": "guess",
"temp": avgTemps[d.getMonth()],
"timestamp" : Date.now() - 3300000,
"conditions": `Probably ${tempWord}<a href="${nwsApiNewsLink}"><sup>‡</sup></a>`
};
}
const USTimeZoneCities = [
"Wake", /* US minor outlying islands */
"New_York", "Detroit", "Louisville", "Monticello", "Indianapolis", "Vincennes",
"Winamac", "Marengo", "Petersburg", "Vevay", "Chicago", "Tell_City", "Knox",
"Menominee", "Center", "New_Salem", "Beulah", "Denver", "Boise", "Phoenix",
"Los_Angeles", "Anchorage", "Juneau", "Sitka", "Metlakatla", "Yakutat", "Nome",
"Adak", "Honolulu"
];
function probablyInAmerica() {
if (Intl) {
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const tzArr = userTimeZone.split("/");
userCity = tzArr[tzArr.length - 1];
return USTimeZoneCities.includes(userCity);
} else {
return false;
}
}