hkopendata
Version:
Access different Opendata API and data in Hong Kong
287 lines (277 loc) • 10.1 kB
JavaScript
const cmn = require("../../common");
const LOCALE = require("../../locale");
const Coordinate = require("../../_class").Coordinate;
const GEODATA = [];
const BASE_URL = "https://geodata.gov.hk/gs/api/v1.0.0/geoDataQuery";
const VALID = {
id: /^[a-f0-9-]{36}$/,
lang: /^(ALL|CHI|ENG)$/,
v: /^[0-9.]+$/,
};
const VALID_OPT = {
longMin: /^[0-9.]+$/,
longMax: /^[0-9.]+$/,
latMin: /^[0-9.]+$/,
latMax: /^[0-9.]+$/,
hk80MinX: /^[0-9.]+$/,
hk80MaxX: /^[0-9.]+$/,
hk80MinY: /^[0-9.]+$/,
hk80MaxY: /^[0-9.]+$/,
}
const LANG = {
...LOCALE.GetRaw('common'),
...LOCALE.GetRaw('geo')
};
const PARAMS = {
lang: "ALL",
v: "1.0.0",
}
const FIELDS = {}
for (let key in LANG) {
let type = LANG[key].type || "text";
if (!(type in FIELDS)) FIELDS[type] = {};
if (typeof LANG[key] === "string") {
FIELDS[type][LANG[key]] = key;
} else {
for (let lang in LANG[key]) {
let val;
if (/^(fax|tel|email|schoolID|siteArea|daysBeforeEstCompletionDate)$/.test(key) || /(UnitsNo|Year|Month)$/.test(key)) {
val = key
} else {
val = key + "_" + lang
}
if (typeof LANG[key][lang] === "string") {
if (LANG[key][lang] in FIELDS[type]) continue;
FIELDS[type][LANG[key][lang]] = val
} else if (Array.isArray(LANG[key][lang])) {
LANG[key][lang].map(v => {
if (!(v in FIELDS[type])) FIELDS[type][v] = val
})
}
}
}
}
const SEARCH_CONFIG = {
value: {
lang: {
accepted: {
tc: "CHI",
en: "ENG",
all: "ALL"
}
},
},
boundary: ["boundary", "boundaryHK"]
}
function beforeSearch() {
if (GEODATA.length === 0) {
let _geodata = cmn.GetDataJson("geodata", true);
_geodata.map(item => GEODATA.push(item));
}
}
function parseSearchFields(params) {
let temp = cmn.ParseSearchFields(params, SEARCH_CONFIG);
if ("boundary" in temp) {
temp.longMin = temp.boundary[0][0];
temp.longMax = temp.boundary[1][0];
temp.latMin = temp.boundary[0][1];
temp.latMax = temp.boundary[1][1];
delete temp.boundary;
}
if ("boundaryHK" in temp) {
temp.hk80MinX = temp.boundaryHK[0][0];
temp.hk80MaxX = temp.boundaryHK[1][0];
temp.hk80MinY = temp.boundaryHK[0][1];
temp.hk80MaxY = temp.boundaryHK[1][1];
delete temp.boundaryHK;
}
return temp;
}
function validateParameters(params, opts) {
params = parseSearchFields(params);
let result = cmn.ValidateParameters(params, VALID, VALID_OPT);
if ("id" in params && GEODATA.length > 0) {
let valid = GEODATA.filter(v => (v.set || v.dataset).filter(u => u.id == params.id).length > 0).length > 0;
if (!valid) {
result.error = true;
result.message = "Invalid dataset id."
}
}
if (!result.error) {
result.data = {
...params
}
}
return result;
}
function search(data, opts) {
beforeSearch();
return new Promise((resolve, reject) => {
let processed = validateParameters({
...PARAMS,
...data
}),
params;
if (processed.error) {
reject(processed);
} else {
params = {
q: JSON.stringify(processed.data)
};
cmn.APIRequest(BASE_URL, params)
.then((res) => {
resolve(processData(res));
})
.catch((err) => reject(err))
}
})
}
function processData(data) {
let result = [];
data.features.map(v => {
let temp = {},
prop = v.properties,
preprocess = [{
text: ["偶到服務", "Drop-in Service"],
key: ["openingTeleInterp", "openingDropIn"]
},
{
text: ["訪客中心", "Visitor Centre"],
key: ["openingVisitorCentre", "openingManagementOffice"]
},
{
text: ["東區入境船隻認可碇泊處", "Eastern Immigration Anchorage"],
key: ["openingAnchorage1", "openingAnchorage2"]
},
{
text: ["診症時間", "Consulation Hours"],
key: ["hourRegistration", "hourConsulation"]
},
{
text: ["夜間賽馬日", "非賽馬日", "冬季時段", "Sha Tin Night Race", "Non-racedays", "Winter Time"],
key: ["openingSummerDayRace", "openingSummerNightRace", "openingSummerNoRace", "openingWinterDayRace", "openingWinterNightRace", "openingWinterNoRace"]
},
{
text: ["其他公園範圍", "Other park area"],
key: ["openingExhibitionGallery", "openingOtherParkArea"]
},
{
text: ["十一月至三月", "11月至3月", "11 月至3 月", "November to March"],
key: ["openingSummerPool", "openingWinterPool"]
}
],
tempItem,
openingTemp = {};
if (v.geometry.type == "Polygon") {
temp.coordinates = [];
v.geometry.coordinates[0].map(u => temp.coordinates.push(new Coordinate({
longitude: u[0],
latitude: u[1],
})))
} else if (v.geometry.type == "Point") {
temp.coordinate = new Coordinate({
longitude: v.geometry.coordinates[0],
latitude: v.geometry.coordinates[1],
})
}
if ("Northing" in prop && "Easting" in prop) {
temp.coordinateHK = new Coordinate({
_type: "tmerc",
_system: "hk1980",
northing: prop.Northing,
easting: prop.Easting,
});
delete prop.Northing;
delete prop.Easting;
}
// Pre-process prop
if ("District" in prop && !("Sub District" in prop)) {
prop["Sub District"] = prop["District"];
delete prop["District"];
}
tempItem = cmn.RenameFields(prop, FIELDS);
// Pre-process time/opening
for (let key in tempItem) {
let replace = [
"每月第一和第三個星期一上午7時至下午1時",
"7 am – 1 pm on the 1st and 3rd Mondays of each month",
/<br[\/ ]*>/g
],
n;
if (n = key.match(/(time|opening|hour)/i)) {
if (tempItem[key] != "N.A.") {
let n2 = key.match(/(.*)_(en|tc)/);
if (!(n2[1] in openingTemp)) openingTemp[n2[1]] = {};
openingTemp[n2[1]][n2[2]] = tempItem[key];
}
replace.map(v => tempItem[key] = tempItem[key].replace(v, " "))
if (/[^ ]\s/.test(tempItem[key]) && tempItem[key].length - 2 * tempItem[key].match(/\s/g).length < 5) {
tempItem[key] = tempItem[key].replace(/\s/g, "")
}
preprocess.map(v => {
let m, r = new RegExp(v.text.join("|"), "i");
if (r.test(tempItem[key])) {
m = tempItem[key].split(r);
m.map((u, i) => {
tempItem[key.replace(n[1], v.key[i])] = u;
})
}
})
}
}
for (let key in tempItem) {
let m;
if (tempItem[key] != "N.A.") {
let val = tempItem[key];
if (!/opening_/.test(key) && /time|opening|hour/i.test(key)) {
val = cmn.StrToWeekTime(val)
} else if (typeof val === "string") {
val = val.split(/<br[\/ ]?>/i).filter(v => v.trim() != "").map(v => v.trim());
if (val.length == 1) val = val[0];
}
if (m = key.match(/(.*)_(en|tc)/)) {
let k = m[1],
l = m[2];
if (!(k in temp)) temp[k] = {};
if (!/opening_/.test(key) && /time|opening|hour/i.test(key) && typeof val === "object") {
temp[k] = {
...temp[k],
...val,
};
if (!("_raw" in temp[k])) temp[k]._raw = {}
temp[k]._raw[l] = tempItem[key];
} else {
temp[k][l] = val;
}
} else {
temp[key.toCamelCase()] = val;
}
}
}
for (let key in temp) {
if (!(/coordinates?/.test(key)) && typeof temp[key] === "object") {
if ("en" in temp[key] && "tc" in temp[key] && temp[key].en == temp[key].tc) {
temp[key] = temp[key].en;
} else if ("en" in temp[key] && !("tc" in temp[key])) {
temp[key] = temp[key].en;
} else if (!("en" in temp[key]) && "tc" in temp[key]) {
temp[key] = temp[key].tc;
}
}
let m = key.match(/(time|opening|hour)/i);
if (m && !/(time|opening|hour)$/i.test(key)) {
temp[m[1]][key] = temp[key];
delete temp[key]
}
}
for (let key in openingTemp) {
for (let lang in openingTemp[key]) {
if (!(key in temp)) temp[key] = {};
temp[key][lang] = openingTemp[key][lang];
}
}
result.push(temp)
})
return result
}
module.exports = search