hkopendata
Version:
Access different Opendata API and data in Hong Kong
263 lines (247 loc) • 8.23 kB
JavaScript
// https://data.etagmb.gov.hk/static/GMB_ETA_API_Specification.pdf
const cmn = require("../../common");
const moment = require("../../moment");
const Coordinate = require("../../_class").Coordinate;
const BASE_URL = "https://data.etagmb.gov.hk/{type}/{data}";
const ENDPOINT = {
"route": "{routeData}",
"stop-route": "{stop}",
"route-stop": "{routeId}/{routeSeq}",
"stop": "{stop}",
"eta": "{etaData}",
}
const VALID = {
type: /^(route|stop-route|route-stop|stop|eta|stop-eta)$/
}
const VALID_OPT = {
region: /^(HKI|KLN|NT)$/,
route: /^[A-z0-9]+$/,
routeId: /^[0-9]+$/,
routeSeq: /^[0-9]+$/,
stop: /^[0-9]+$/,
stopSeq: /^[0-9]+$/,
}
const PARAMS = {
type: "route"
}
const FIELDS = {
regex: {
"^orig": "origin",
"^dest": "destination",
"^.+_seq$": "seq",
"^route_": "",
"^stop_": "",
},
other: {
"directions": "direction",
"headways": "headway",
}
}
const SEARCH_CONFIG = {
value: {
type: {
accepted: ["route", "stop-route", "route-stop", "stop", "eta", "stop-eta"]
},
region: {
accepted: ["HKI", "KLN", "NT"]
},
},
}
function validateParameters(params) {
params = cmn.ParseSearchFields(params, SEARCH_CONFIG);
let result = cmn.ValidateParameters(params, VALID, VALID_OPT);
params._type = params.type;
if (!result.error && params.type === "route") {
if ("routeId" in params) {
params.routeData = '{routeId}';
params._type = "route-info";
} else if ("region" in params) {
params.routeData = '{region}';
if ("route" in params) {
params.routeData += `/{route}`;
params._type = "route-info";
}
} else {
result.error = true;
result.message = "Missing region";
}
}
if (!result.error && params.type === "route-stop") {
let error = result.error;
if (!("routeId" in params)) {
error = true;
result.message = "Missing route id";
} else if (!("routeSeq" in params)) {
error = true;
result.message = "Missing route sequence";
}
result.error = error;
}
if (!result.error && /^(stop|stop-eta|stop-route)$/.test(params.type) && !("stop" in params)) {
result.error = true;
result.message = "Missing stop";
}
if (!result.error && params.type === "eta") {
if (!("routeId" in params)) {
result.error = true;
result.message = "Missing route id";
} else {
if ("stop" in params) {
params.etaData = `{stop}`;
params._type = "eta-list";
} else if (!("routeSeq" in params)) {
result.error = true;
result.message = "Missing route sequence";
} else if (!("stopSeq" in params)) {
result.error = true;
result.message = "Missing stop sequence";
} else {
params.etaData = `{routeSeq}/{stopSeq}`;
}
}
if (!result.error && params.etaData) {
params.etaData = `route-stop/{routeId}/${params.etaData}`;
}
}
if (!result.error && params.type === "stop-eta") {
params.type = "eta";
params.etaData = "stop/{stop}";
}
if (!result.error) {
result.data = {
...{
data: ENDPOINT[params.type]
},
...params
}
}
return result;
}
function search(data, opts) {
return new Promise((resolve, reject) => {
let processed = validateParameters({
...PARAMS,
...data
}),
params;
if (processed.error) {
reject(processed);
} else {
params = processed.data;
cmn.APIRequest(cmn.ReplaceURL(BASE_URL, params))
.then((res) => {
resolve(processData(res.data, params._type))
})
.catch((err) => reject(err))
}
})
}
function groupData(data) {
if (Array.isArray(data)) return data.map(v => groupData(v));
else if (typeof data !== "object" || data === null) return data;
let temp = {};
data = cmn.RenameFields(data, FIELDS);
for (let key in data) {
let m;
if (typeof data[key] === "object") {
data[key] = groupData(data[key]);
}
if (m = key.match(/^(.+)_(en|tc|sc)$/)) {
if (!(m[1] in temp)) temp[m[1]] = {};
temp[m[1]][m[2]] = data[key];
} else if (key !== "data_timestamp") {
temp[key] = data[key];
}
}
return temp;
}
const Weekdays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
function processData(data, type) {
let result = [];
if (type === "route") {
result = data.routes;
} else if (type === "stop-route") {
data.map(route => {
let temp = {
routeId: route.route_id,
routeSeq: route.route_seq,
stopSeq: route.stop_seq,
},
item = groupData(route);
for (let key in item) {
if (!/(id|seq)$/.test(key)) {
temp[key] = item[key];
}
}
result.push(temp);
});
} else if (type === "route-info") {
data.map(item => {
item = groupData(item);
item.direction = item.direction.map(dir => {
dir.headway = dir.headway.map(headway => {
let temp = {
applicableDay: {},
period: `${headway.start_time.slice(0, -3)}-${headway.end_time.slice(0, -3)}`,
};
headway.weekdays.map((day, i) => temp.applicableDay[Weekdays[i]] = day);
temp.applicableDay.ph = headway.public_holiday;
if (headway.frequency) {
let freq = headway.frequency;
if (headway.frequency_upper) freq += `-${headway.frequency_upper}`;
temp.frequency = freq;
}
return temp;
})
return dir;
})
result.push(item)
})
} else if (type === "route-stop") {
result = groupData(data.route_stops);
} else if (type === "stop") {
let item = groupData(data),
temp = {};
for (let key in item) {
if (key === "coordinates") {
if (item[key].wgs84) {
temp.coordinate = new Coordinate(item[key].wgs84);
}
if (item[key].hk80) {
temp.coordinateHK = new Coordinate({
_type: "tmerc",
_system: "hk1980",
northing: item[key].hk80.longitude,
easting: item[key].hk80.latitude,
});
}
} else if (key === "enabled") {
temp.suspended = !item[key];
} else {
temp[key] = item[key];
}
}
result.push(temp);
} else if (/eta/.test(type)) {
data = !Array.isArray(data) ? [data] : data;
data.map(item => {
item.eta.map(eta => {
eta = groupData(eta);
let temp = {};
if (item.route_id) temp.routeId = item.route_id;
if (item.route_seq) temp.routeSeq = item.route_seq;
if (item.stop_seq) temp.stopSeq = item.stop_seq;
for (let key in eta) {
if (key === 'timestamp') {
temp.eta = moment(eta[key]).format('YYYY-MM-DD HH:mm:ss')
} else if (!/^(seq|diff)$/.test(key)) {
temp[key] = eta[key];
}
}
result.push(temp)
})
})
}
return result
}
module.exports = search;