Skip to content

Commit ad8a059

Browse files
committed
Quick fix /observations
API broke due to the external APIs no longer available Weather@SG app is gone, so they got rid of their API
1 parent dea9d63 commit ad8a059

3 files changed

Lines changed: 694 additions & 56 deletions

File tree

api/observations.js

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,77 @@
11
const got = require('got');
2+
const { DateTime } = require('luxon');
23

3-
const stationsURL =
4-
'http://www.weather.gov.sg/mobile/json/rest-get-all-climate-stations.json';
5-
let stationsData;
6-
const getStations = async () => {
7-
console.log('GET STATIONS start');
8-
console.time('GET STATIONS');
9-
if (stationsData) {
10-
console.timeEnd('GET STATIONS');
11-
return stationsData;
12-
}
13-
const { body } = await got(stationsURL, {
14-
responseType: 'json',
15-
timeout: 3 * 1000,
16-
headers: { 'user-agent': undefined },
17-
});
18-
console.timeEnd('GET STATIONS');
19-
stationsData = body;
20-
return body;
21-
};
4+
// Have to be X minutes in the past, else it's too recent and lack of data
5+
const datetime = () =>
6+
DateTime.local()
7+
.minus({ minutes: 10 })
8+
.set({ second: 0 })
9+
.setZone('Asia/Singapore')
10+
.toISO()
11+
.replace(/\..*$/, ''); // Remove the mili-seconds from the ISO-8601 timestamp
2212

23-
const dataURL =
24-
'http://www.weather.gov.sg/mobile/json/rest-get-latest-observation-for-all-locs.json';
25-
// const observationsCache = new Map();
26-
const numberRegexp = /[\d.]+/;
27-
const getObservations = async () => {
28-
const climateStations = await getStations();
13+
const apiURLs = {
14+
temp_celcius: 'https://api.data.gov.sg/v1/environment/air-temperature',
15+
rain_mm: 'https://api.data.gov.sg/v1/environment/rainfall',
16+
relative_humidity: 'https://api.data.gov.sg/v1/environment/relative-humidity',
17+
wind_direction: 'https://api.data.gov.sg/v1/environment/wind-direction',
18+
wind_speed: 'https://api.data.gov.sg/v1/environment/wind-speed',
19+
};
20+
const apiKeys = Object.keys(apiURLs);
2921

30-
console.log('GET OBS start');
31-
console.time('GET OBS');
32-
const { body: observations } = await got(dataURL, {
22+
const fetch = (url) => {
23+
const u = `${url}?date_time=${datetime()}`;
24+
console.log(`Fetching ${u}`);
25+
return got(u, {
3326
responseType: 'json',
3427
timeout: 2 * 1000,
3528
retry: 2,
36-
// cache: observationsCache,
3729
maxRedirects: 1,
3830
calculateDelay: () => 1000,
3931
headers: { 'user-agent': undefined },
4032
});
41-
console.timeEnd('GET OBS');
33+
};
4234

43-
const obs = [];
44-
Object.entries(observations.data.station).forEach(([stationID, obj]) => {
45-
const { id, long, lat } = climateStations.data.find(
46-
(d) => d.id === stationID,
47-
);
48-
const values = {};
49-
for (let k in obj) {
50-
const v = obj[k];
51-
if (numberRegexp.test(v)) {
52-
const val = Number(v);
53-
if (val) values[k] = val;
54-
}
55-
}
35+
// id, lng, lat, temp_celcius, relative_humidity, rain_mm, wind_direction, wind_speed
5636

57-
// Special case for S121 overlapping with S23
58-
if (id === 'S121') {
59-
delete values.temp_celcius;
60-
delete values.relative_humidity;
37+
const getObservations = async () => {
38+
const climateStations = {};
39+
const observations = {};
40+
const apiFetches = Object.values(apiURLs).map((url) => fetch(url));
41+
const results = await Promise.allSettled(apiFetches);
42+
results.forEach((result, i) => {
43+
if (result.status !== 'fulfilled') {
44+
console.log('API fetch failed:', apiKeys[i]);
45+
return;
6146
}
62-
63-
if (Object.keys(values).length) {
64-
obs.push({
65-
id,
66-
lng: +(+long).toFixed(4),
67-
lat: +(+lat).toFixed(4),
68-
...values,
47+
const { body } = result.value;
48+
body.metadata.stations.forEach((station) => {
49+
climateStations[station.id] = {
50+
lng: station.location.longitude,
51+
lat: station.location.latitude,
52+
};
53+
});
54+
body.items.forEach((item) => {
55+
item.readings.forEach((reading) => {
56+
if (!reading.value) return;
57+
if (observations[reading.station_id]) {
58+
observations[reading.station_id][apiKeys[i]] = reading.value;
59+
} else {
60+
observations[reading.station_id] = {
61+
[apiKeys[i]]: reading.value,
62+
};
63+
}
6964
});
70-
}
65+
});
66+
});
67+
68+
const obs = Object.entries(observations).map(([stationID, observation]) => {
69+
return {
70+
id: stationID,
71+
lng: +climateStations[stationID].lng.toFixed(4),
72+
lat: +climateStations[stationID].lat.toFixed(4),
73+
...observation,
74+
};
7175
});
7276

7377
return obs;

0 commit comments

Comments
 (0)