Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapter fix/ust ist #1096

Merged
merged 15 commits into from
Mar 28, 2024
155 changes: 88 additions & 67 deletions src/adapters/ust-ist.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,72 @@

import { acceptableParameters } from '../lib/utils.js';
import { DateTime } from 'luxon';

import client from '../lib/requests.js';
import log from '../lib/logger.js';

export const name = 'ust-ist';

export async function fetchData (source, cb) {
// the dataUrl only works for the current date - 1 day.
// a list of endpoints can be found at https://api.ust.is/aq/a
// the old endpoint https://api.ust.is/aq/a/getLatest is not returning data anymore
const yesterday = DateTime.utc().minus({ days: 1 }).toISODate(); // format "YYYY-MM-DD"
const stationsUrl = 'https://api.ust.is/aq/a/getStations';
const dataUrl = `https://api.ust.is/aq/a/getDate/date/${yesterday}`;

/**
* Fetches air quality data for Iceland from a specific date and compiles it into a structured format.
*
* @param {Object} source - The source configuration object, including name and URL.
* @param {Function} cb - A callback function that is called with the final dataset or an error.
*/
export async function fetchData(source, cb) {
try {
const headers = {
accept: "application/json, text/javascript, */*; q=0.01",
"accept-language": "en-US,en;q=0.9",
"cache-control": "no-cache",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
pragma: "no-cache"
const headers = {
accept: "application/json, text/javascript, */*; q=0.01",
"accept-language": "en-US,en;q=0.9",
"cache-control": "no-cache",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
pragma: "no-cache"
};
const allData = await client({ url: dataUrl, headers: headers});
const allMeta = await client({ url: stationsUrl, headers: headers});
const stations = Object.keys(allData);

const measurements = stations.reduce((acc, stationId) => {
const stationData = allData[stationId];
const stationMeta = allMeta.find(s => s.local_id === stationData.local_id);

// Skip processing this station if metadata is missing
if (!stationMeta) {
console.warn(`Metadata missing for station ID: ${stationId}. Skipping...`);
return acc;
}

const baseMeta = {
location: stationData.name,
city: stationMeta.municipality,
coordinates: {
latitude: parseFloat(stationMeta.latitude),
longitude: parseFloat(stationMeta.longitude)
},
attribution: [{
name: source.name,
url: source.sourceURL
}]
};
const allData = await client({ url: source.url, headers });

const allMeta = await client({ url: 'https://api.ust.is/aq/a/getStations', headers });

// Generate an array of station IDs there is data for.
const stations = Object.keys(allData);

const measurements = stations.reduce((acc, stationId) => {
const stationData = allData[stationId];
const stationMeta = allMeta.find(s => s.local_id === stationData.local_id);

const baseMeta = {
location: stationData.name,
city: stationMeta.municipality,
coordinates: {
latitude: parseFloat(stationMeta.latitude),
longitude: parseFloat(stationMeta.longitude)
},
attribution: [{
name: source.name,
url: source.sourceURL
}]
};
const latestMeasurements = parseParams(stationData.parameters);

return acc.concat(latestMeasurements.map(m => ({ ...baseMeta, ...m })));
}, []);

cb(null, {name: 'unused', measurements});
} catch (e) {
log.error(`Error fetching data: ${e.message}`);
cb(e);

const latestMeasurements = parseParams(stationData.parameters);

return acc.concat(latestMeasurements.map(m => ({ ...baseMeta, ...m })));
}, []);
log.debug(measurements[0]);
cb(null, { name: 'unused', measurements });
} catch (e) {
cb(e);
}
}
}


/**
* Parse object with parameters, each with a series of measurements.
Expand All @@ -78,31 +97,33 @@ export async function fetchData (source, cb) {
* @returns [ { value: 0.154717, date: 2020-01-03 03:00:00. parameter: 'no2' }]
*
*/
function parseParams(params) {
// Array with the valid parameters in the object
const validParams = Object.keys(params).filter(p => acceptableParameters.includes(p.toLowerCase().replace('.', '')));

function parseParams (params) {
// Array with the valid parameters in the object
const validParams = Object.keys(params).filter(p => acceptableParameters.includes(p.toLowerCase().replace('.', '')));

return validParams.map(p => {
// Assumes that '0' is always latest
const latestM = params[p]['0'];

const date = DateTime.fromFormat(latestM.endtime.trimEnd(), 'yyyy-LL-dd HH:mm:ss', { zone: 'Atlantic/Reykjavik' });

// Resolution is reported as 1h. Anything else will break.
const resolution = params[p].resolution === '1h'
? { value: 1, unit: 'hours' }
: {};

return {
date: {
utc: date.toUTC().toISO({ suppressMilliseconds: true }),
local: date.toISO({ suppressMilliseconds: true })
},
parameter: p.toLowerCase().replace('.', ''),
value: parseFloat(latestM.value),
unit: params[p].unit,
averagingPeriod: resolution
};
});
}
return validParams.flatMap(p => {
const measurements = Object.keys(params[p])
.filter(key => !isNaN(parseInt(key))) // Filter out keys that are not indices
.map(index => {
const measurement = params[p][index];
const date = DateTime.fromFormat(measurement.endtime.trimEnd(), 'yyyy-LL-dd HH:mm:ss', { zone: 'Atlantic/Reykjavik' });

const resolution = params[p].resolution === '1h'
? { value: 1, unit: 'hours' }
: {};

return {
date: {
utc: date.toUTC().toISO({ suppressMilliseconds: true }),
local: date.toISO({ suppressMilliseconds: true })
},
parameter: p.toLowerCase().replace('.', ''),
value: parseFloat(measurement.value),
unit: params[p].unit,
averagingPeriod: resolution
};
});

return measurements;
});
}
Loading