@angelrove/forecast-utils 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1046 -0
- package/dist/types/OpenMeteo/conf.d.ts +13 -0
- package/dist/types/OpenMeteo/current/fetchParams.d.ts +1 -0
- package/dist/types/OpenMeteo/current/transformer.d.ts +13 -0
- package/dist/types/OpenMeteo/current/useForecastCurrent.d.ts +14 -0
- package/dist/types/OpenMeteo/daily/fetchParams.d.ts +1 -0
- package/dist/types/OpenMeteo/daily/transformer.d.ts +9 -0
- package/dist/types/OpenMeteo/daily/useForecastDaily.d.ts +14 -0
- package/dist/types/OpenMeteo/helpers.d.ts +10 -0
- package/dist/types/OpenMeteo/hourly/fetchParams.d.ts +1 -0
- package/dist/types/OpenMeteo/hourly/useForecastHourly.d.ts +15 -0
- package/dist/types/OpenMeteo/weatherSymbol/lib/WeatherCodesEn.d.ts +43 -0
- package/dist/types/OpenMeteo/weatherSymbol/lib/WeatherCodesEs.d.ts +43 -0
- package/dist/types/OpenMeteo/weatherSymbol/lib/getWeatherCodeEntry.d.ts +7 -0
- package/dist/types/OpenMeteo/weatherSymbol/weatherSymbol.d.ts +13 -0
- package/dist/types/astronomy/moon/MoonCalc.d.ts +63 -0
- package/dist/types/astronomy/moon/parseBasicData.d.ts +8 -0
- package/dist/types/astronomy/sun/SunCalc.d.ts +73 -0
- package/dist/types/astronomy/sun/helpers.d.ts +0 -0
- package/dist/types/astronomy/timeZoneInfo.d.ts +10 -0
- package/dist/types/astronomy/timehelpers.d.ts +27 -0
- package/dist/types/astronomy/types.d.ts +43 -0
- package/dist/types/geolocation/getGeolocation.d.ts +12 -0
- package/dist/types/geolocation/lib/geolocation.d.ts +10 -0
- package/dist/types/geolocation/lib/geolocationCapacitor.d.ts +10 -0
- package/dist/types/geolocation/lib/reversegeocoding.d.ts +19 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/utils/degreesToCompass.d.ts +11 -0
- package/dist/types/utils/warning.d.ts +46 -0
- package/dist/types/utils/wind/WindArrow.d.ts +32 -0
- package/dist/types/utils/wind/windArrowTx.d.ts +9 -0
- package/dist/types/utils/wind/windLevel.d.ts +29 -0
- package/package.json +40 -0
- package/src/OpenMeteo/conf.js +47 -0
- package/src/OpenMeteo/current/fetchParams.js +21 -0
- package/src/OpenMeteo/current/transformer.js +37 -0
- package/src/OpenMeteo/current/useForecastCurrent.js +34 -0
- package/src/OpenMeteo/daily/fetchParams.js +9 -0
- package/src/OpenMeteo/daily/transformer.js +21 -0
- package/src/OpenMeteo/daily/useForecastDaily.js +38 -0
- package/src/OpenMeteo/helpers.js +19 -0
- package/src/OpenMeteo/hourly/fetchParams.js +12 -0
- package/src/OpenMeteo/hourly/useForecastHourly.js +42 -0
- package/src/OpenMeteo/weatherSymbol/lib/WeatherCodesEn.js +89 -0
- package/src/OpenMeteo/weatherSymbol/lib/WeatherCodesEs.js +81 -0
- package/src/OpenMeteo/weatherSymbol/lib/getWeatherCodeEntry.js +12 -0
- package/src/OpenMeteo/weatherSymbol/weatherSymbol.js +60 -0
- package/src/astronomy/moon/MoonCalc.js +171 -0
- package/src/astronomy/moon/parseBasicData.js +38 -0
- package/src/astronomy/sun/SunCalc.js +167 -0
- package/src/astronomy/sun/helpers.js +0 -0
- package/src/astronomy/timeZoneInfo.js +89 -0
- package/src/astronomy/timehelpers.js +69 -0
- package/src/astronomy/types.js +30 -0
- package/src/geolocation/getGeolocation.js +49 -0
- package/src/geolocation/lib/geolocation.js +44 -0
- package/src/geolocation/lib/geolocationCapacitor.js +21 -0
- package/src/geolocation/lib/reversegeocoding.js +109 -0
- package/src/index.js +38 -0
- package/src/utils/degreesToCompass.js +51 -0
- package/src/utils/warning.js +130 -0
- package/src/utils/wind/WindArrow.jsx +53 -0
- package/src/utils/wind/windArrowTx.js +24 -0
- package/src/utils/wind/windLevel.js +49 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse Geocoding with GoogleMaps API
|
|
3
|
+
*
|
|
4
|
+
* @see https://developers.google.com/maps/documentation/geocoding/requests-reverse-geocoding?hl=es-419
|
|
5
|
+
* @see https://maps.googleapis.com/maps/api/geocode/json?latlng=36.7248,-4.541&key=[api_key]&result_type=sublocality
|
|
6
|
+
*
|
|
7
|
+
* @param {number} latitude - Latitude of the location.
|
|
8
|
+
* @param {number} longitude - Longitude of the location.
|
|
9
|
+
* @param {string} api_key - Google Maps API key.
|
|
10
|
+
* @returns {Promise<{ sublocality: string; locality: string; country: string; country_short: string; formatted_address: string; }>} - The address components.
|
|
11
|
+
* @throws {Error} - If the API key is invalid or the request fails.
|
|
12
|
+
*/
|
|
13
|
+
export default async function reverseGeocoding(latitude, longitude, api_key) {
|
|
14
|
+
// Validate --
|
|
15
|
+
if (!api_key) {
|
|
16
|
+
console.error("ReverseGeocoding: invalid api key");
|
|
17
|
+
throw new Error("ReverseGeocoding: invalid api key");
|
|
18
|
+
}
|
|
19
|
+
if (!latitude || !longitude) {
|
|
20
|
+
console.error("ReverseGeocoding: invalid coordinates");
|
|
21
|
+
throw new Error("ReverseGeocoding: invalid coordinates");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// API URL --
|
|
25
|
+
const host =
|
|
26
|
+
"https://maps.googleapis.com/maps/api/geocode/json?key=" +
|
|
27
|
+
api_key +
|
|
28
|
+
"&latlng=" +
|
|
29
|
+
latitude +
|
|
30
|
+
"," +
|
|
31
|
+
longitude;
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* 'result_type' param:
|
|
35
|
+
* street_address,
|
|
36
|
+
* colloquial_area,
|
|
37
|
+
* locality,
|
|
38
|
+
* sublocality,
|
|
39
|
+
* administrative_area_level_1,
|
|
40
|
+
* administrative_area_level_2
|
|
41
|
+
*/
|
|
42
|
+
const urlApi = host + "&result_type=sublocality";
|
|
43
|
+
|
|
44
|
+
// Fetch data --
|
|
45
|
+
const response = await fetch(urlApi);
|
|
46
|
+
devLog("Geocoding", urlApi);
|
|
47
|
+
|
|
48
|
+
// Check response --
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const message =
|
|
51
|
+
"Failed to Reverse Geocoding: " +
|
|
52
|
+
response.status +
|
|
53
|
+
" " +
|
|
54
|
+
response.statusText;
|
|
55
|
+
console.error(message);
|
|
56
|
+
throw new Error(message);
|
|
57
|
+
}
|
|
58
|
+
const data = await response.json();
|
|
59
|
+
|
|
60
|
+
// No results --
|
|
61
|
+
if (data.status === "ZERO_RESULTS") {
|
|
62
|
+
// plus_code: { compound_code: 'G4F9+72P Marbella, Spain', }
|
|
63
|
+
const country = data.plus_code.compound_code.split(",")[1];
|
|
64
|
+
const address = data.plus_code.compound_code.split(" ").slice(1).join(" ");
|
|
65
|
+
// throw new Error('Failed to Reverse Geocoding');
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
sublocality: "",
|
|
69
|
+
locality: "",
|
|
70
|
+
country: country,
|
|
71
|
+
country_short: country,
|
|
72
|
+
formatted_address: address,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (data.status !== "OK") {
|
|
77
|
+
let message = "Failed to Reverse Geocoding: " + data.status;
|
|
78
|
+
if (data.error_message) {
|
|
79
|
+
message += " " + data.error_message;
|
|
80
|
+
}
|
|
81
|
+
console.error(message);
|
|
82
|
+
throw new Error(message);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Formatted address --
|
|
86
|
+
let formatted_address = data.results[0].formatted_address.split(",");
|
|
87
|
+
formatted_address.pop();
|
|
88
|
+
formatted_address = formatted_address.join(",");
|
|
89
|
+
|
|
90
|
+
// Return --
|
|
91
|
+
return {
|
|
92
|
+
sublocality: data.results[0].address_components[0].long_name,
|
|
93
|
+
locality: data.results[0].address_components[1].long_name,
|
|
94
|
+
country: data.results[0].address_components[4].long_name,
|
|
95
|
+
country_short: data.results[0].address_components[4].short_name,
|
|
96
|
+
formatted_address: formatted_address,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} title
|
|
102
|
+
* @param {any} url
|
|
103
|
+
*/
|
|
104
|
+
function devLog(title, url) {
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
if (import.meta.env.MODE === "development") {
|
|
107
|
+
console.log(`> fetch [${title}]:`, url);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author: Jose Angel Romero Vegas
|
|
3
|
+
* @license: MIT
|
|
4
|
+
* @description:
|
|
5
|
+
* Several utility libraries for the Tierracolora project:
|
|
6
|
+
* - Forecast API (OpenWeather)
|
|
7
|
+
* - Astronomy utilities
|
|
8
|
+
*
|
|
9
|
+
* I use public libraries for the following:
|
|
10
|
+
* - Astronomy calculations: suncalc3
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Astronomy
|
|
14
|
+
export { default as MoonCalc } from "./astronomy/moon/MoonCalc.js";
|
|
15
|
+
export { default as SunCalc } from "./astronomy/sun/SunCalc.js";
|
|
16
|
+
export {
|
|
17
|
+
dateFormat,
|
|
18
|
+
getLocalTimeFromTz,
|
|
19
|
+
nowString,
|
|
20
|
+
timeString,
|
|
21
|
+
} from "./astronomy/timehelpers.js";
|
|
22
|
+
export { getLocalTimeInfo as getLocalTime } from "./astronomy/timeZoneInfo.js";
|
|
23
|
+
|
|
24
|
+
// Geolocation
|
|
25
|
+
export { getGeolocation } from "./geolocation/getGeolocation.js";
|
|
26
|
+
|
|
27
|
+
// OpenMeteo
|
|
28
|
+
export { useForecastCurrent } from "./OpenMeteo/current/useForecastCurrent.js";
|
|
29
|
+
export { useForecastDaily } from "./OpenMeteo/daily/useForecastDaily.js";
|
|
30
|
+
export { useForecastHourly } from "./OpenMeteo/hourly/useForecastHourly.js";
|
|
31
|
+
export { weatherSymbol } from "./OpenMeteo/weatherSymbol/weatherSymbol.js";
|
|
32
|
+
|
|
33
|
+
// utils
|
|
34
|
+
export { degreesToCompass } from "./utils/degreesToCompass.js";
|
|
35
|
+
export { getWarning, getWarningByDays } from "./utils/warning.js";
|
|
36
|
+
export { WindArrow } from "./utils/wind/WindArrow.jsx";
|
|
37
|
+
export { windArrowTx } from "./utils/wind/windArrowTx.js";
|
|
38
|
+
export { getWindLevel } from "./utils/wind/windLevel.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const directions = [
|
|
6
|
+
{ short: "N", full: "North" },
|
|
7
|
+
{ short: "NE", full: "Northeast" },
|
|
8
|
+
{ short: "E", full: "East" },
|
|
9
|
+
{ short: "SE", full: "Southeast" },
|
|
10
|
+
{ short: "S", full: "South" },
|
|
11
|
+
{ short: "SW", full: "Southwest" },
|
|
12
|
+
{ short: "W", full: "West" },
|
|
13
|
+
{ short: "NW", full: "Northwest" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const directionsEs = [
|
|
17
|
+
{ short: "N", full: "Norte" },
|
|
18
|
+
{ short: "NE", full: "Noreste" },
|
|
19
|
+
{ short: "E", full: "Este" },
|
|
20
|
+
{ short: "SE", full: "Sureste" },
|
|
21
|
+
{ short: "S", full: "Sur" },
|
|
22
|
+
{ short: "SO", full: "Suroeste" },
|
|
23
|
+
{ short: "O", full: "Oeste" },
|
|
24
|
+
{ short: "NO", full: "Noroeste" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert degrees to compass designation
|
|
29
|
+
*
|
|
30
|
+
* @param {number} degrees
|
|
31
|
+
* @param {string} language - Language code ("en-US", "es-ES", "auto")
|
|
32
|
+
* @returns {{short: string, full: string}}
|
|
33
|
+
*/
|
|
34
|
+
export function degreesToCompass(degrees, language = "auto") {
|
|
35
|
+
if (degrees == null) return { short: "?", full: "?" };
|
|
36
|
+
|
|
37
|
+
const index = Math.round(degrees / 45) % 8;
|
|
38
|
+
|
|
39
|
+
// auto detect language
|
|
40
|
+
let lang = language;
|
|
41
|
+
if (language === "auto") {
|
|
42
|
+
lang = navigator.language;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse the language
|
|
46
|
+
if (lang.startsWith("es")) {
|
|
47
|
+
return directionsEs[index];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return directions[index];
|
|
51
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// AEMET (12 horas)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} AlertLevel
|
|
9
|
+
* @property {number} levelNum - The alert level number.
|
|
10
|
+
* @property {string} level - The alert level string (e.g., "red", "orange", "yellow").
|
|
11
|
+
* @property {number} precipitation - The precipitation threshold for the alert level.
|
|
12
|
+
* @property {number} showers - The showers threshold for the alert level.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Alert levels for rain and showers
|
|
17
|
+
*
|
|
18
|
+
* @type {AlertLevel[]}
|
|
19
|
+
*/
|
|
20
|
+
const ALERT_LEVEL = [
|
|
21
|
+
{ levelNum: 3, level: "red", precipitation: 120, showers: 60 },
|
|
22
|
+
{ levelNum: 2, level: "orange", precipitation: 80, showers: 30 },
|
|
23
|
+
{ levelNum: 1, level: "yellow", precipitation: 50, showers: 18 },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get warning by today and tomorrow
|
|
28
|
+
*
|
|
29
|
+
* @param {number} precipitationSumToday
|
|
30
|
+
* @param {number} precipitationSumTomorrow
|
|
31
|
+
* @param {number} showersSumToday
|
|
32
|
+
* @param {number} showersSumTomorrow
|
|
33
|
+
* @returns {{ levelNum: number, level: string, message: string, day: number } | null}
|
|
34
|
+
*/
|
|
35
|
+
export function getWarningByDays(
|
|
36
|
+
precipitationSumToday,
|
|
37
|
+
precipitationSumTomorrow,
|
|
38
|
+
showersSumToday,
|
|
39
|
+
showersSumTomorrow,
|
|
40
|
+
) {
|
|
41
|
+
// Today
|
|
42
|
+
const wToday = getWarning(precipitationSumToday, showersSumToday);
|
|
43
|
+
if (wToday) {
|
|
44
|
+
return {
|
|
45
|
+
...wToday,
|
|
46
|
+
day: 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Tomorrow
|
|
51
|
+
const wTomorrow = getWarning(precipitationSumTomorrow, showersSumTomorrow);
|
|
52
|
+
if (wTomorrow) {
|
|
53
|
+
return {
|
|
54
|
+
...wTomorrow,
|
|
55
|
+
day: 1,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get warning by precipitation and showers
|
|
64
|
+
*
|
|
65
|
+
* @export
|
|
66
|
+
* @param {number} precipitation
|
|
67
|
+
* @param {number} showers
|
|
68
|
+
* @returns {{ levelNum: number, level: string, message: string } | null}
|
|
69
|
+
*/
|
|
70
|
+
export function getWarning(precipitation, showers) {
|
|
71
|
+
const warningRain = getWarningRain(precipitation);
|
|
72
|
+
const warningShowers = getWarningShowers(showers);
|
|
73
|
+
// console.log(warningRain);
|
|
74
|
+
// console.log(warningShowers);
|
|
75
|
+
|
|
76
|
+
if (warningRain && warningShowers) {
|
|
77
|
+
return warningRain.levelNum > warningShowers.levelNum
|
|
78
|
+
? warningRain
|
|
79
|
+
: warningShowers;
|
|
80
|
+
}
|
|
81
|
+
if (warningRain) {
|
|
82
|
+
return warningRain;
|
|
83
|
+
}
|
|
84
|
+
if (warningShowers) {
|
|
85
|
+
return warningShowers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//----------------------------------------------------------------
|
|
92
|
+
// PRIVATE FUNCTIONS
|
|
93
|
+
//----------------------------------------------------------------
|
|
94
|
+
/**
|
|
95
|
+
* Get warning by rain
|
|
96
|
+
*
|
|
97
|
+
* @param {number} precipitation
|
|
98
|
+
* @returns {{ levelNum: number, level: string, message: string } | null}
|
|
99
|
+
*/
|
|
100
|
+
function getWarningRain(precipitation) {
|
|
101
|
+
for (let i = 0; i < ALERT_LEVEL.length; i++) {
|
|
102
|
+
if (precipitation >= ALERT_LEVEL[i].precipitation) {
|
|
103
|
+
return {
|
|
104
|
+
levelNum: ALERT_LEVEL[i].levelNum,
|
|
105
|
+
level: ALERT_LEVEL[i].level,
|
|
106
|
+
message: "Por precipitacion acumulada en 24 horas.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get warning by showers
|
|
115
|
+
*
|
|
116
|
+
* @param {number} showers
|
|
117
|
+
* @returns {{ levelNum: number, level: string, message: string } | null}
|
|
118
|
+
*/
|
|
119
|
+
function getWarningShowers(showers) {
|
|
120
|
+
for (let i = 0; i < ALERT_LEVEL.length; i++) {
|
|
121
|
+
if (showers >= ALERT_LEVEL[i].showers) {
|
|
122
|
+
return {
|
|
123
|
+
levelNum: ALERT_LEVEL[i].levelNum,
|
|
124
|
+
level: ALERT_LEVEL[i].level,
|
|
125
|
+
message: "Chubascos intensos.",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils:WindArrow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} WindArrowProps
|
|
7
|
+
* @property {number} props.deg - Wind direction in degrees
|
|
8
|
+
* @property {string} [props.size] - Size of the arrow (Tailwind CSS size: 'size-10')
|
|
9
|
+
* @property {number} [props.strokeWidth] - Stroke width of the arrow (1 to 6)
|
|
10
|
+
* @property {string} [props.className]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wind direction arrow component (svg image).
|
|
15
|
+
*
|
|
16
|
+
* @component
|
|
17
|
+
* @param {WindArrowProps} props
|
|
18
|
+
*/
|
|
19
|
+
export function WindArrow({
|
|
20
|
+
deg,
|
|
21
|
+
size = "size-5",
|
|
22
|
+
strokeWidth = 1.9,
|
|
23
|
+
className = "",
|
|
24
|
+
}) {
|
|
25
|
+
if (deg == null) {
|
|
26
|
+
// console.warn("WindArrow: deg is undefined");
|
|
27
|
+
/* @ts-ignore */
|
|
28
|
+
return <div>deg?</div>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Render arrow ---
|
|
32
|
+
return (
|
|
33
|
+
/* @ts-ignore */
|
|
34
|
+
<svg
|
|
35
|
+
className={`${size} ${className}`}
|
|
36
|
+
style={{ transform: `rotate(${deg}deg)` }}
|
|
37
|
+
fill="none"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
strokeWidth={strokeWidth}
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
>
|
|
44
|
+
{/* @ts-ignore */}
|
|
45
|
+
<path
|
|
46
|
+
strokeLinecap="round"
|
|
47
|
+
strokeLinejoin="round"
|
|
48
|
+
d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3"
|
|
49
|
+
/>
|
|
50
|
+
{/* @ts-ignore */}
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert wind direction in degrees to an arrow representation.
|
|
7
|
+
*
|
|
8
|
+
* @param {number} deg - Wind direction in degrees.
|
|
9
|
+
*/
|
|
10
|
+
export function windArrowTx(deg) {
|
|
11
|
+
if (deg == null) {
|
|
12
|
+
console.warn("WindDegToArrow.jsx: deg is undefined");
|
|
13
|
+
return "deg?";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (deg < 22.5) return "↓";
|
|
17
|
+
if (deg < 67.5) return "↙";
|
|
18
|
+
if (deg < 112.5) return "←";
|
|
19
|
+
if (deg < 157.5) return "↖";
|
|
20
|
+
if (deg < 202.5) return "↑";
|
|
21
|
+
if (deg < 247.5) return "↗";
|
|
22
|
+
if (deg < 292.5) return "→";
|
|
23
|
+
if (deg < 337.5) return "↘";
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} WindLevel
|
|
7
|
+
* @property {number} id - Level ID
|
|
8
|
+
* @property {number} speed - Minimum wind speed for this level
|
|
9
|
+
* @property {string} color - Color representing this level
|
|
10
|
+
* @property {string} tx - Text representing this level
|
|
11
|
+
* @property {string} txEn - Text representing this level
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ordered list of wind levels.
|
|
16
|
+
*
|
|
17
|
+
* @type {WindLevel[]}
|
|
18
|
+
*/
|
|
19
|
+
const WIND_LEVELS = [
|
|
20
|
+
{ id: 1, speed: 8, color: "green", tx: "Brisa", txEn: "Breeze" },
|
|
21
|
+
{ id: 2, speed: 16, color: "orange", tx: "Ligero", txEn: "Light" },
|
|
22
|
+
{ id: 3, speed: 23, color: "red", tx: "Moderado", txEn: "Moderate" },
|
|
23
|
+
{ id: 4, speed: 30, color: "red", tx: "Fuerte", txEn: "Strong" },
|
|
24
|
+
{ id: 5, speed: 50, color: "fuchsia", tx: "Muy fuerte", txEn: "Very strong" },
|
|
25
|
+
{
|
|
26
|
+
id: 6,
|
|
27
|
+
speed: Number.POSITIVE_INFINITY,
|
|
28
|
+
color: "fuchsia",
|
|
29
|
+
tx: "Peligroso",
|
|
30
|
+
txEn: "Dangerous",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return the wind level based on the speed.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} speed Wind speed in km/h
|
|
38
|
+
* @return {WindLevel | null} Wind level object or null if speed is null
|
|
39
|
+
*/
|
|
40
|
+
export function getWindLevel(speed) {
|
|
41
|
+
if (typeof speed !== "number") return null;
|
|
42
|
+
|
|
43
|
+
const theSpeed = Math.round(speed);
|
|
44
|
+
|
|
45
|
+
for (const level of WIND_LEVELS) {
|
|
46
|
+
if (theSpeed < level.speed) return level;
|
|
47
|
+
}
|
|
48
|
+
return WIND_LEVELS[WIND_LEVELS.length - 1]; // fallback defensivo
|
|
49
|
+
}
|