1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
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
}));
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": "-",
"bt": "-",
"kp": "-",
"kp_min": "-",
"kp_max": "-"
},
"usnoaa_data_raw": {
"solar_wind_mag_field": false,
"noaa_planetary_k_index_forecast": false,
"geospace_pred_est_kp_1_hour": false,
"outlook_27_day": false
}
};
let tmp;
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"];
ret.now.bt = ret.usnoaa_data_raw.solar_wind_mag_field["Bt"];
res = await fetch("https://services.swpc.noaa.gov/json/geospace/geospace_pred_est_kp_1_hour.json");
tmp = await res.json();
tmp = tmp.map(x => ({
...x, "model_prediction_time": new Date(x.model_prediction_time)
}));
ret.usnoaa_data_raw.geospace_pred_est_kp_1_hour = tmp
res = await fetch("https://services.swpc.noaa.gov/text/27-day-outlook.txt");
tmp = await res.text();
tmp = [...tmp.matchAll(
/^(?<time>\d{4}\s.{3}\s\d{2})\s+(?<flux107>\d+)\s+(?<aindex>\d+)\s+(?<kindex>\d+)$/gm
)];
tmp = tmp.map(x => ({...x.groups, "time": new Date(x.groups.time + " UTC")}));
ret.usnoaa_data_raw.outlook_27_day = tmp;
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);
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
}));
}
|