UNPKG

hkopendata

Version:

Access different Opendata API and data in Hong Kong

173 lines (158 loc) 5.3 kB
// https://www.aqhi.gov.hk/psi/dd/hk_apien_en.pdf const cmn = require("../../common"); const moment = require("../../moment"); const BASE_URL = "http://www.aqhi.gov.hk/api_history/download/hourly/{lang}/hr{month}{year}{langabbr}.csv"; const EPDStation = require("../../_class").EPDStation; const BUFFER_STRING = { en: ",,Roadside API,,,General API", tc: ",,路邊 ,,,一般" } const VALID = { lang: /^(eng|tc_chi)$/, year: /^[0-9]{4}$/, month: /^[0-9]{1,2}$/, } const VALID_OPT = { station: /^[A-Z]{2,3}$/, day: /^[0-9]{1,2}$/, hour: /^[0-9]{1,2}$/, type: /^(g|r)$/, } const PARAMS = { lang: "en", year: "1999", month: "07" } const SEARCH_CONFIG = { value: { lang: { accepted: { en: "eng", tc: "tc_chi", } }, type: { accepted: ["g", "r"] }, }, } function validateParameters(params) { params = cmn.ParseSearchFields(params, SEARCH_CONFIG); let result = cmn.ValidateParameters(params, VALID, VALID_OPT), hasStation = true; if ("station" in params) { hasStation = !cmn.HasDataJson("aqhi") || cmn.SearchDataJson("aqhi", { "code": params.station }).length != 0; if (!hasStation) { result.error = true; result.message = "Invalid station code"; } } if ("month" in params) params.month = `00${params.month}`.slice(-2); let y = parseInt(params.year) || 1999, m = parseInt(params.month) || 7, d = parseInt(params.day) || 1, h = parseInt(params.hour) || 1, dateArr = [y, m - 1, d, h], date = moment(dateArr); if (!date.isBetween(moment([1999, 6, 1, 1]), moment([2014, 0, 1, 0]), "h", "[]")) { result.error = true; result.message = "Invalid `year`, `month`, `day` or `hour`"; } else if ("day" in params) { let start = dateArr.slice(0), end = dateArr.slice(0); start[3] = "hour" in params ? start[3] : 1; end[3] = "hour" in params ? end[3] : 24; result.range = { start, end }; } if (!result.error) { result.data = { ...params, langabbr: params.lang !== "eng" ? "c" : "" } } return result; } function search(data, opts) { return new Promise((resolve, reject) => { let processed = validateParameters({ ...PARAMS, ...data }), range, params; if (processed.range) { range = JSON.parse(JSON.stringify(processed.range)); delete processed.range; } if (processed.error) { reject(processed); } else { params = processed.data; opts = { ...opts, ...params, range }; cmn.CSVFetch(cmn.ReplaceURL(BASE_URL, params), { encoding: "big5", preprocess: data => { let idx = data.indexOf(BUFFER_STRING.en); if (idx === -1) idx = data.indexOf(BUFFER_STRING.tc); if (idx !== -1) idx = data.indexOf("\n", idx); return data.slice(idx + 1, data.length); } }) .then((res) => { resolve(processData(res, opts)) }) .catch((err) => reject(err)) } }) } function processData(data, opts) { let stations = data.header.slice(2, data.header.length) .map(name => new EPDStation(name)), result = Array(stations.length), date = ""; data.body.map(row => { let curr = row.shift().trim(), hour = row.shift().trim(); if (!hour || isNaN(hour = parseInt(hour))) return false; if (curr !== "") date = moment(curr, ["DD/MM/YYYY", "YYYY-MM-DD"]).format("YYYY-MM-DD"); hour = `00${hour + 1}`.slice(-2); let mdate = moment(`${date} ${hour}:00`); if (opts.range && !mdate.isBetween(moment(opts.range.start), moment(opts.range.end), "h", "[]")) return false; if (opts.hour && parseInt(hour) !== parseInt(opts.hour)) return false; row.map((value, i) => { if (value === "") return; if (opts.station && opts.station !== stations[i].code) return; if (opts.type && opts.type !== stations[i].type) return; if (!result[i]) result[i] = { station: stations[i], data: {} } if (!(date in result[i].data)) result[i].data[date] = {}; result[i].data[date][hour] = parseInt(value); }) }) return result.filter(row => row) .map(row => { let temp = [] for (let date in row.data) { temp.push(...Object.keys(row.data[date]) .map(hour => ({ station: row.station, date, time: `${hour}:00`, value: row.data[date][hour] }))) } return temp; }).flat(); } module.exports = search;