diff options
author | Jakob Stendahl <jakob.stendahl@outlook.com> | 2022-04-27 00:32:41 +0200 |
---|---|---|
committer | Jakob Stendahl <jakob.stendahl@outlook.com> | 2022-04-27 00:32:41 +0200 |
commit | 9dc247a861be845ff79ce38430ace2591d187975 (patch) | |
tree | 48295989955a9a201219368d9dd0c9a31597fbf2 | |
parent | d85c9433d565714daf3118533f7d7108d16927f6 (diff) | |
download | Aurora-data-9dc247a861be845ff79ce38430ace2591d187975.tar.gz Aurora-data-9dc247a861be845ff79ce38430ace2591d187975.zip |
Move all data fetching to a store, add spinner for loading
-rw-r--r-- | src/components/PredictedSpaceWeather.svelte | 101 | ||||
-rw-r--r-- | src/components/PredictedSpaceWeatherThing.svelte | 13 | ||||
-rw-r--r-- | src/components/Spinner/SpinnerRoller.svelte | 88 | ||||
-rw-r--r-- | src/components/WeatherCurrent.svelte | 153 | ||||
-rw-r--r-- | src/routes/index.svelte | 3 | ||||
-rw-r--r-- | src/stores.ts | 198 |
6 files changed, 375 insertions, 181 deletions
diff --git a/src/components/PredictedSpaceWeather.svelte b/src/components/PredictedSpaceWeather.svelte index b350d3c..35f1a84 100644 --- a/src/components/PredictedSpaceWeather.svelte +++ b/src/components/PredictedSpaceWeather.svelte @@ -1,90 +1,59 @@ <script lang="ts"> import PredictedSpaceWeatherThing from './PredictedSpaceWeatherThing.svelte'; + import { onMount } from 'svelte'; + import { earth_weather, space_weather } from '../stores'; const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; - let longitude; - let latitude; - let locationSupported = false; - let dataLoading = true; - let predictions; - async function getWeather(longitude, latitude) { - let yr_data; - if (locationSupported) { - yr_data = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`).then(res => res.json()); + space_weather.subscribe(assembleWeatherData); + earth_weather.subscribe(assembleWeatherData); + + async function assembleWeatherData() { + if ($space_weather.updating || $earth_weather.updating) { + predictions = null; + return; } - let kp_data = await fetch("https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json").then(res => res.json()); - kp_data.shift(); - let updatedPredictions = []; - kp_data.forEach((pred, i) => { - if (pred[2] != "observed") { + // First just reorganize the space_weather data + predictions = $space_weather.usnoaa_data_raw.noaa_planetary_k_index_forecast.map( + pred => ({ + "time": pred.time, + "kp": pred.kp, + "temp": null, + "clouds": null, + "hasNOMETData": $earth_weather.available + }) + ); + + // Add earth weather data if it is available + if ($earth_weather.available) { + predictions.forEach((pred, i) => { + let closestDate = new Date(0,0,0); let temp; let clouds; - let cDate = new Date(pred[0]); - let closestDate = new Date(0,0,0); - - if (locationSupported) { - yr_data["properties"]["timeseries"].forEach((pred, i) => { - let predDate = new Date(pred["time"]); - if (Math.abs(predDate.getTime() - cDate.getTime()) < Math.abs(closestDate.getTime() - cDate.getTime())) { - closestDate = predDate; - temp = (pred["data"]["instant"]["details"]["air_temperature"]); - clouds = pred["data"]["instant"]["details"]["cloud_area_fraction"]; - } - }); - } - updatedPredictions.push({ - "time": pred[0], - "kp": pred[1], - "temp": temp, - "clouds": clouds, - "hasNOMETData": locationSupported + $earth_weather.yr_data_raw.properties.timeseries.forEach((earth_pred, i) => { + let predDate = new Date(earth_pred.time); + if (Math.abs(predDate.getTime() - pred.time.getTime()) < Math.abs(closestDate.getTime() - pred.time.getTime())) { + closestDate = predDate; + temp = (earth_pred["data"]["instant"]["details"]["air_temperature"]); + clouds = earth_pred["data"]["instant"]["details"]["cloud_area_fraction"]; + } }); - } - }); - predictions = updatedPredictions; - } - function getLocation() { - if (navigator.geolocation) { - dataLoading = true - locationSupported = true - navigator.geolocation.getCurrentPosition(setLocation, locationError) - } else { - locationSupported = false - noLocation() + predictions[i] = { + ...predictions[i], "temp": temp, "clouds": clouds + } + }); } - } - - function setLocation(position) { - longitude = position.coords.longitude - latitude = position.coords.latitude - getWeather(longitude, latitude) - } - function locationError(err) { - locationSupported = false - noLocation() - } - - function noLocation() { - longitude = 28.283333 - latitude = -15.416667 - getWeather(0, 0); - toggleLoading() - } - function toggleLoading() { - dataLoading = !dataLoading } - onMount(getLocation); </script> <style> diff --git a/src/components/PredictedSpaceWeatherThing.svelte b/src/components/PredictedSpaceWeatherThing.svelte index 5c4cb5b..7c343e9 100644 --- a/src/components/PredictedSpaceWeatherThing.svelte +++ b/src/components/PredictedSpaceWeatherThing.svelte @@ -5,10 +5,17 @@ "July", "August", "September", "October", "November", "December" ]; + function zpad(value, n=2) { + let r = value; + for (let i = 0; i < n - value.length; i++) { + r = "0" + r; + } + return r; + } + let kp = prediction["kp"]; - let dateTime = prediction["time"].split(" "); - let date = Number(dateTime[0].split("-")[2]) + ". " + monthNames[Number(dateTime[0].split("-")[1])]; - let time = dateTime[1].substring(0,5); + let date = prediction["time"].getDate() + ". " + monthNames[prediction["time"].getMonth()]; + let time = zpad(prediction["time"].getHours().toString()) + ":" + zpad(prediction["time"].getMinutes().toString()); let temp = prediction["temp"]; let clouds = prediction["clouds"]; let hasNOMETData = prediction["hasNOMETData"]; diff --git a/src/components/Spinner/SpinnerRoller.svelte b/src/components/Spinner/SpinnerRoller.svelte new file mode 100644 index 0000000..b2b1ef9 --- /dev/null +++ b/src/components/Spinner/SpinnerRoller.svelte @@ -0,0 +1,88 @@ +<style> + .lds-roller { + display: inline-block; + position: relative; + width: 80px; + height: 80px; + } + .lds-roller div { + animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + transform-origin: 40px 40px; + } + .lds-roller div:after { + content: " "; + display: block; + position: absolute; + width: 7px; + height: 7px; + border-radius: 50%; + background: #fff; + margin: -4px 0 0 -4px; + } + .lds-roller div:nth-child(1) { + animation-delay: -0.036s; + } + .lds-roller div:nth-child(1):after { + top: 63px; + left: 63px; + } + .lds-roller div:nth-child(2) { + animation-delay: -0.072s; + } + .lds-roller div:nth-child(2):after { + top: 68px; + left: 56px; + } + .lds-roller div:nth-child(3) { + animation-delay: -0.108s; + } + .lds-roller div:nth-child(3):after { + top: 71px; + left: 48px; + } + .lds-roller div:nth-child(4) { + animation-delay: -0.144s; + } + .lds-roller div:nth-child(4):after { + top: 72px; + left: 40px; + } + .lds-roller div:nth-child(5) { + animation-delay: -0.18s; + } + .lds-roller div:nth-child(5):after { + top: 71px; + left: 32px; + } + .lds-roller div:nth-child(6) { + animation-delay: -0.216s; + } + .lds-roller div:nth-child(6):after { + top: 68px; + left: 24px; + } + .lds-roller div:nth-child(7) { + animation-delay: -0.252s; + } + .lds-roller div:nth-child(7):after { + top: 63px; + left: 17px; + } + .lds-roller div:nth-child(8) { + animation-delay: -0.288s; + } + .lds-roller div:nth-child(8):after { + top: 56px; + left: 12px; + } + @keyframes lds-roller { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +</style> + +<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div> diff --git a/src/components/WeatherCurrent.svelte b/src/components/WeatherCurrent.svelte index 7ca6ac4..45bfe11 100644 --- a/src/components/WeatherCurrent.svelte +++ b/src/components/WeatherCurrent.svelte @@ -1,100 +1,11 @@ <script lang="ts"> - import { onMount } from 'svelte'; + import SpinnerRoller from './Spinner/SpinnerRoller.svelte'; + import { onMount } from 'svelte'; + import { navigator_location, earth_weather, space_weather } from '../stores'; const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; - - let longitude; - let latitude; - let locationSupported; - let dataLoading = true; - - let location = "-"; - let date = "-"; - let kp_now = "-"; - let kp_min = "-"; - let kp_max = "-"; - let bz = "-"; - let clouds = "-"; - let temp = "-"; - - async function getMETNorData(longitude, latitude) { - let yr_data = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`).then(res => res.json()); - clouds = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["cloud_area_fraction"]; - temp = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["air_temperature"]; - } - - async function getLocation(longitude, latitude) { - console.log(`https://geocode.xyz/${longitude},${latitude}?geoit=json`); - let locDat = await fetch(`https://geocode.xyz/${latitude},${longitude}?geoit=json`).then(r => r.json()); - console.log(locDat); - location = locDat["city"]; - } - - async function getUSNOAAData() { - bz = (await fetch("https://services.swpc.noaa.gov/products/summary/solar-wind-mag-field.json").then(res => res.json()))["Bz"]; - let kp_data = (await fetch('https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json').then(res => res.json())); - kp_data.shift(); - - let cDate = new Date(); - let closestDate = new Date(0,0,0); - let minkp = 1000; // Just a larger number than any plausable value - let maxkp = 0; - - kp_data.forEach((pred, i) => { - if (pred[1] > maxkp) { - maxkp = pred[1]; - } - if (pred[1] < minkp) { - minkp = pred[1]; - } - let predDate = new Date(pred[0]); - if (Math.abs(predDate.getTime() - cDate.getTime()) < Math.abs(closestDate.getTime() - cDate.getTime())) { - closestDate = predDate; - kp_now = pred[1]; - } - }); - kp_min = minkp.toString(); - kp_max = maxkp.toString(); - - date = closestDate.getDay() + ". " + monthNames[closestDate.getMonth()] + " " + closestDate.getHours() + ":" + closestDate.getMinutes(); - } - - function fetchData() { - getUSNOAAData(); - - if (navigator.geolocation) { - dataLoading = true - locationSupported = true - navigator.geolocation.getCurrentPosition(setLocation, locationError) - } else { - locationSupported = false - noLocation() - } - } - - function setLocation(position) { - longitude = position.coords.longitude - latitude = position.coords.latitude - getMETNorData(longitude, latitude) - getLocation(longitude, latitude); - } - - function locationError(err) { - noLocation() - } - - function noLocation() { - longitude = 28.283333 - latitude = -15.416667 - toggleLoading() - } - function toggleLoading() { - dataLoading = !dataLoading - } - - onMount(fetchData); </script> <style> @@ -138,6 +49,7 @@ display: flex; justify-content: center; align-content: center; + align-items: flex-end; } .weatherCurrent-data-location .symbol { @@ -181,35 +93,52 @@ <div class="weatherCurrent-wrapper"> <div class="weatherCurrent-data"> <div class="weatherCurrent-data-location"> - <i class="symbol fas fa-map-marker-alt"></i> - <h1>{location}</h1> + {#if !$navigator_location.updating && $navigator_location.available} + <i class="symbol fas fa-map-marker-alt"></i> + <h1>{$navigator_location.city}</h1> + {/if} </div> <div class="weatherCurrent-data-date"> - <p>{date}</p> + {#if $earth_weather.updating || $space_weather.updating} + {:else} + {#if Math.abs($earth_weather.updated - $space_weather.updated) > 60*10*1000} + <p>There is more than 10 minutes difference between data updates</p> + {:else} + <p>{$earth_weather.updated.toLocaleString("no-NO", {dateStyle: "medium", timeStyle: "short"})}</p> + {/if} + {/if} </div> <div class="weatherCurrent-data-kp"> - <h2>KP {kp_now}</h2> - <p> - <span className="pr-2">↑ KP {kp_max}</span> - <span className="pl-2">↓ KP {kp_min}</span> - </p> + {#if $space_weather.updating || $earth_weather.updating} + <SpinnerRoller /> + {:else} + <h2>KP {$space_weather.now.kp}</h2> + <p> + <span className="pr-2">↑ KP {$space_weather.now.kp_max}</span> + <span className="pl-2">↓ KP {$space_weather.now.kp_min}</span> + </p> + {/if} </div> <div class="current-details"> - <div> - <p>BZ</p> - <p>{bz}</p> - </div> - <div> - <p>Temp</p> - <p>{temp}°C</p> - </div> - <div> - <p>Clouds</p> - <p>{clouds}%</p> - </div> + {#if !$space_weather.updating && !$earth_weather.updating} + <div> + <p>BZ</p> + <p>{$space_weather.now.bz}</p> + </div> + {#if $earth_weather.available} + <div> + <p>Temp</p> + <p>{$earth_weather.now.temp}°C</p> + </div> + <div> + <p>Clouds</p> + <p>{$earth_weather.now.clouds}%</p> + </div> + {/if} + {/if} </div> </div> diff --git a/src/routes/index.svelte b/src/routes/index.svelte index bf54b6a..7efda23 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -1,6 +1,9 @@ <script lang="ts"> import WeatherCurrent from '../components/WeatherCurrent.svelte'; import PredictedSpaceWeather from '../components/PredictedSpaceWeather.svelte'; + + //import { theme, fetchingData, fetchDataError, weatherData } from "../stores.ts"; + //weatherData.subscribe(console.log); </script> diff --git a/src/stores.ts b/src/stores.ts new file mode 100644 index 0000000..d6e5cbc --- /dev/null +++ b/src/stores.ts @@ -0,0 +1,198 @@ +import { writable, readable } from 'svelte/store'; + +export const theme = writable('light'); + +/* + * the different data sources are + * - Geocode Used to get user location based on coordinates, this is needed for METNor data. + * - METNor (Yr) Earth weather + * - USNOAA (Nasa ish) Space weather, Aurora info + * + * The methots here does exit early if the code is running server-side and not + * in the browser. This is to attempt to fix. + **/ +const base_attributes = {"updated": false, "updating": true} +export const navigator_location = writable({...base_attributes, "available": false, "longitude": null, "latitude": null, "city": null}); +export const earth_weather = writable({...base_attributes, "available": false}); +export const space_weather = writable({...base_attributes}); + +// Kickstart store updates +updateNavigatorLocation(); +navigator_location.subscribe(updateEarthWeather); +updateSpaceWeather(); + +/** + * Will update the store "navigator_location" with the users geolocation if + * possible. + */ +async function updateNavigatorLocation() { + if(typeof window === "undefined") { return; } + setUpdated(navigator_location, true); + + let coords; + try { + coords = await getBrowserGeolocation(); + } catch (e) { + console.log(e); + navigator_location.update(v => ({...v, "available": false, "city": null, "longitude": null, "latitude": null})); + setUpdated(navigator_location, false); + return; + } + + let res = await fetch(`https://geocode.xyz/${coords.latitude},${coords.longitude}?geoit=json`); + let locDat = await res.json(); + navigator_location.update(v => ({...v, "available": true, "city": locDat["city"], ...coords})); + + setUpdated(navigator_location, false); +} + +/** + * Wraps navigator.geolocation in a promise, this promise will be rejected if + * code is not run in a browser. + */ +async function getBrowserGeolocation() { + return new Promise((resolve, reject) => { + if (typeof navigator !== "undefined") { + navigator.geolocation.getCurrentPosition( + position => { + resolve({ + "longitude": position.coords.longitude, + "latitude": position.coords.latitude + }); + }, + error => { + reject(error); + } + ); + } else { + reject(Error()); + } + }); +} + +/** + * Update the "earth_weather" store with weather information about earth :) + */ +async function updateEarthWeather(location=null) { + if (typeof window === "undefined") { return; } + if (location === null) { return; } + if (location.updating) { return; } + if (!location.available) { + earth_weather.update(v => ({ + ...v, + "available": false + })); + setUpdated(earth_weather, false); + return; + } + setUpdated(earth_weather, true); + + let res = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${location.latitude}&lon=${location.longitude}`); + let yr_data = await res.json(); + let current_weather = {"clouds": null, "temp": null} + try { + // @TODO: Make a similar thing as for kp-index to get the closest time to now. + current_weather.clouds = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["cloud_area_fraction"]; + current_weather.temp = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["air_temperature"]; + + yr_data["properties"]["timeseries"] = yr_data["properties"]["timeseries"].map(x => ({...x, "time": new Date(x.time)})); + } catch (e) {} + + earth_weather.update(v => ({ + ...v, + "yr_data_raw": yr_data, + "now": current_weather, + "available": true + })); + console.log(yr_data); + + setUpdated(earth_weather, false); +} + +/** + * This will update the "space_weather" store with the newest data. + */ +async function updateSpaceWeather() { + if(typeof window === "undefined") { return; } + setUpdated(space_weather, true); + let spaceWeather = await getSpaceWeather(); + space_weather.update(v => ({...v, ...spaceWeather})); + setUpdated(space_weather, false); +} + +/** + * This will return all the data we are interested in for the space weather + * store. + */ +async function getSpaceWeather() { + let ret = { + "now": { + "bz": "-", + "kp": "-", + "kp_min": "-", + "kp_max": "-" + }, + "usnoaa_data_raw": { + "solar_wind_mag_field": false, + "noaa_planetary_k_index_forecast": false + } + }; + + let res = await fetch("https://services.swpc.noaa.gov/products/summary/solar-wind-mag-field.json"); + ret.usnoaa_data_raw.solar_wind_mag_field = await res.json(); + ret.usnoaa_data_raw.solar_wind_mag_field.TimeStamp = new Date(ret.usnoaa_data_raw.solar_wind_mag_field.TimeStamp + " UTC"); + ret.now.bz = ret.usnoaa_data_raw.solar_wind_mag_field["Bz"]; + + res = await fetch("https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json") + ret.usnoaa_data_raw.noaa_planetary_k_index_forecast = await res.json() + ret.usnoaa_data_raw.noaa_planetary_k_index_forecast.shift(); + + let cDate = new Date(); + let closestDate = new Date(0,0,0); + let minkp = 1000; // Just a larger number than any plausable value + let maxkp = 0; + + ret.usnoaa_data_raw.noaa_planetary_k_index_forecast.forEach((pred, i) => { + if (pred[1] > maxkp) { + maxkp = pred[1]; + } + if (pred[1] < minkp) { + minkp = pred[1]; + } + + let predDate = new Date(pred[0] + " UTC"); + + if (Math.abs(predDate.getTime() - cDate.getTime()) < Math.abs(closestDate.getTime() - cDate.getTime())) { + closestDate = predDate; + ret.now.kp = pred[1]; + } + + ret.usnoaa_data_raw.noaa_planetary_k_index_forecast[i] = { + "time": predDate, "kp": pred[1], "observed": pred[2] + }; + }); + + ret.now.kp_min = minkp.toString(); + ret.now.kp_max = maxkp.toString(); + console.log(ret.usnoaa_data_raw); + + return ret; +} + +/** + * Sets the parameters of a store that has to do with the last update. + * It is important that the store value is a object, which has at least the + * attributes in the base_attributes constant. + * @param {store} s The store that should be changed. + * @param {boolean} updating Wether the store is currently in the process of + * being updated. + */ +function setUpdated(s, updating=false) { + let updated = updating ? {} : {"updated": (new Date())}; + s.update(v => ({ + ...v, + ...updated, + "updating": updating + })); +} + |