Files
WeatherStar4000/server/scripts/modules/icons/icons-parse.mjs
Eddy G 804d9e9e33 Centralize icon URL parsing; improve icon error handling
- Move common icon parsing logic into module
- Return a "real" icon value during error handling to avoid downstream
  consumers from trying to use an icon named "false"
- Use named regex to parse icon URLs based on API specification
2025-07-07 12:43:32 -04:00

94 lines
3.5 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Parses weather.gov icon URLs and extracts weather condition information
* Handles both single and dual condition formats according to the weather.gov API spec
*
* NOTE: The 'icon' properties are marked as deprecated in the API documentation. This
* is because it will eventually be replaced with a more generic value that is not a URL.
*/
import { debugFlag } from '../utils/debug.mjs';
/**
* Parses a weather.gov icon URL and extracts condition and timing information
* @param {string} iconUrl - Icon URL from weather.gov API (e.g., "/icons/land/day/skc?size=medium")
* @param {boolean} _isNightTime - Optional override for night time determination
* @returns {Object} Parsed icon data with conditionIcon, probability, and isNightTime
*/
const parseIconUrl = (iconUrl, _isNightTime) => {
if (!iconUrl) {
throw new Error('No icon URL provided');
}
// Parse icon URL according to API spec: /icons/{set}/{timeOfDay}/{condition}?{params}
// where {condition} might be single (skc) or dual (tsra_hi,20/rain,50)
// Each period will have an icon, or two if there is changing weather during that period
// see https://github.com/weather-gov/api/discussions/557#discussioncomment-9949521
// (On the weather.gov site, changing conditions results in a "dualImage" forecast icon)
const iconUrlPattern = /\/icons\/(?<set>\w+)\/(?<timeOfDay>day|night)\/(?<condition>[^?]+)(?:\?(?<params>.*))?$/i;
const match = iconUrl.match(iconUrlPattern);
if (!match?.groups) {
throw new Error(`Unable to parse icon URL format: ${iconUrl}`);
}
const { timeOfDay, condition } = match.groups;
// Determine if it's night time with preference strategy:
// 1. Primary: use _isNightTime parameter if provided (such as from API's isDaytime property)
// 2. Secondary: use timeOfDay parsed from URL
let isNightTime;
if (_isNightTime !== undefined) {
isNightTime = _isNightTime;
} else if (timeOfDay === 'day') {
isNightTime = false;
} else if (timeOfDay === 'night') {
isNightTime = true;
} else {
console.warn(`parseIconUrl: unexpected timeOfDay value: ${timeOfDay}`);
isNightTime = false;
}
// Dual conditions can have a probability
// Examples: "tsra_hi,30/sct", "rain_showers,30/tsra_hi,50", "hot/tsra_hi,70"
let conditionIcon;
let probability;
if (condition.includes('/')) { // Two conditions
const conditions = condition.split('/');
const firstCondition = conditions[0] || '';
const secondCondition = conditions[1] || '';
const [firstIcon, firstProb] = firstCondition.split(',');
const [secondIcon, secondProb] = secondCondition.split(',');
// Default to 100% probability if not specified (high confidence)
const firstProbability = parseInt(firstProb, 10) || 100;
const secondProbability = parseInt(secondProb, 10) || 100;
if (secondIcon !== firstIcon) {
// When there's more than one condition, use the second condition
// QUESTION: should the condition with the higher probability determine which one to use?
// if (firstProbability >= secondProbability) { ... }
conditionIcon = secondIcon;
probability = secondProbability;
if (debugFlag('icons')) {
console.debug(`2⃣ Using second condition: '${secondCondition}' instead of first '${firstCondition}'`);
}
} else {
conditionIcon = firstIcon;
probability = firstProbability;
}
} else { // Single condition
const [name, prob] = condition.split(',');
conditionIcon = name;
probability = parseInt(prob, 10) || 100;
}
return {
conditionIcon,
probability,
isNightTime,
};
};
export default parseIconUrl;