aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Stendahl <jakob.stendahl@outlook.com>2022-04-27 00:32:41 +0200
committerJakob Stendahl <jakob.stendahl@outlook.com>2022-04-27 00:32:41 +0200
commit9dc247a861be845ff79ce38430ace2591d187975 (patch)
tree48295989955a9a201219368d9dd0c9a31597fbf2
parentd85c9433d565714daf3118533f7d7108d16927f6 (diff)
downloadAurora-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.svelte101
-rw-r--r--src/components/PredictedSpaceWeatherThing.svelte13
-rw-r--r--src/components/Spinner/SpinnerRoller.svelte88
-rw-r--r--src/components/WeatherCurrent.svelte153
-rw-r--r--src/routes/index.svelte3
-rw-r--r--src/stores.ts198
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">&uarr; KP {kp_max}</span>
- <span className="pl-2">&darr; 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">&uarr; KP {$space_weather.now.kp_max}</span>
+ <span className="pl-2">&darr; 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
+ }));
+}
+