atriusmaps-node-sdk
Version:
This project provides an API to Atrius Personal Wayfinder maps within a Node environment. See the README.md for more information
328 lines (322 loc) • 10.9 kB
JavaScript
;
var R = require('ramda');
var Zousan = require('zousan');
var buildStructureLookup = require('../../../src/utils/buildStructureLookup.js');
var configUtils = require('../../../src/utils/configUtils.js');
var i18n = require('../../../src/utils/i18n.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var R__namespace = /*#__PURE__*/_interopNamespaceDefault(R);
async function create(app) {
const log = app.log.sublog("poiDataManager");
const init = () => {
app.bus.send("venueData/loadPoiData");
};
let poisLoaded = new Zousan();
const fixPositionInfo = (poi, structuresLookup) => {
const { position } = poi;
const structure = structuresLookup.floorIdToStructure(position.floorId);
if (!structure) {
log.error(`No structure found for floorId: ${position.floorId} for POI ${poi.poiId}`);
return { ...poi };
}
const floor = structuresLookup.floorIdToFloor(position.floorId);
const detailedPosition = {
...position,
structureName: structure.name,
buildingId: structure.id,
floorName: floor.name,
floorOrdinal: floor.ordinal
};
return { ...poi, position: detailedPosition };
};
const formatPois = (pois, structuresLookup) => {
return R__namespace.pipe(
R__namespace.values,
R__namespace.map((poi) => {
poi.distance = null;
poi.staticName = poi.name;
poi.isNavigable = poi.isNavigable === void 0 || poi.isNavigable === true;
if (poi.capacity) {
addToRoomInfo(poi, {
name: `Seats ${poi.capacity.join("-")}`,
svgId: "number-of-seats"
});
}
if (poi.category.startsWith("meeting")) {
addToRoomInfo(poi, {
name: app.gt()("poiView:Conference Room"),
svgId: "conference-room"
});
}
const roomId = getRoomId(poi);
if (roomId) {
poi.roomId = roomId;
}
return [poi.poiId, fixPositionInfo(poi, structuresLookup)];
}),
R__namespace.fromPairs
)(pois);
};
const addToRoomInfo = (poi, value) => {
if (!poi.roomInfo) {
poi.roomInfo = [];
}
poi.roomInfo.push(value);
};
const getRoomId = R__namespace.pipe(
R__namespace.propOr([], "externalIds"),
R__namespace.find(R__namespace.propEq("roomId", "type")),
R__namespace.prop("id"),
R__namespace.unless(R__namespace.isNil, R__namespace.tail)
);
async function checkNavgraph(pois) {
const start = Date.now();
const badPois = [];
const navGraph = await app.bus.get("wayfinder/_getNavGraph");
Object.values(pois).forEach((poi) => {
try {
const pos = poi.position;
const n = navGraph.findClosestNode(pos.floorId, pos.latitude, pos.longitude);
if (!n) {
badPois.push({ id: poi.poiId, e: "No closest Navgraph Node" });
}
} catch (e) {
log.error(e);
badPois.push({ id: poi.poiId, e: e.message });
}
});
if (badPois.length) {
log.warn("badPois:", badPois);
}
log(`Total time for navgraph POI check: ${Date.now() - start}ms`);
}
function filterBadPois(pois) {
const badPois = [];
Object.values(pois).forEach((poi) => {
try {
const pos = poi.position;
if (!pos) {
badPois.push({ poi, e: "No position information" });
} else {
["buildingId", "structureName", "floorId", "floorName", "floorOrdinal", "latitude", "longitude"].forEach(
(k) => {
if (pos[k] === null || pos[k] === void 0) {
badPois.push({
id: poi.poiId,
e: `invalid position property: ${k}: ${pos[k]}`
});
}
}
);
}
} catch (e) {
log.error(e);
badPois.push({ id: poi.poiId, e: e.message });
}
});
if (badPois.length) {
log.warn("badPois:", badPois);
badPois.forEach((ob) => {
delete pois[ob.id];
});
}
return pois;
}
async function enhanceImages(pois) {
for (const poi of Object.values(pois)) {
await addImages(poi);
}
return pois;
}
app.bus.on("venueData/poiDataLoaded", async ({ pois, structures }) => {
const structuresLookup = buildStructureLookup.buildStructuresLookup(structures);
pois = formatPois(pois, structuresLookup);
if (configUtils.debugIsTrue(app, "pseudoTransPois")) {
for (const id in pois) {
pois[id] = pseudoTransPoi(pois[id], app.i18n().language);
}
}
pois = filterBadPois(pois);
await enhanceImages(pois);
poisLoaded.resolve(pois);
if (app.config.debug && app.env.isBrowser) {
window._pois = pois;
}
if (app.config.debug) {
checkNavgraph(pois);
}
});
app.bus.on("poi/getById", async ({ id }) => {
return poisLoaded.then((pois) => pois[id]);
});
app.bus.on(
"poi/getByFloorId",
async ({ floorId }) => poisLoaded.then(R__namespace.pickBy(R__namespace.pathEq(floorId, ["position", "floorId"])))
);
app.bus.on("poi/getByCategoryId", async ({ categoryId }) => {
const categoryMatch = (poi) => poi.category === categoryId || poi.category.startsWith(categoryId + ".");
return poisLoaded.then(R__namespace.pickBy(categoryMatch));
});
app.bus.on("poi/getAll", async () => poisLoaded);
const checkpointPath = ["queue", "primaryQueueId"];
const addOtherSecurityLanesIfNeeded = async (poi) => {
const primaryCheckpointId = R__namespace.path(checkpointPath, poi);
if (!primaryCheckpointId) {
return poi;
}
const queueTypes = await app.bus.get("venueData/getQueueTypes");
const securityPoisMap = await app.bus.get("poi/getByCategoryId", {
categoryId: "security"
});
const securityPoisList = Object.values(securityPoisMap);
poi.queue.otherQueues = prepareOtherSecurityLanes(queueTypes, poi, securityPoisList);
return poi;
};
const prepareOtherSecurityLanes = (queueTypes, currentPoi, securityPois) => {
const queueTypePath = ["queue", "queueType"];
const queueType = R__namespace.path(queueTypePath, currentPoi);
if (!queueType) {
return null;
}
const queueSubtypes = queueTypes[queueType];
const primaryCheckpointId = R__namespace.path(checkpointPath, currentPoi);
return securityPois.filter(R__namespace.pathEq(primaryCheckpointId, checkpointPath)).filter((poi) => poi.poiId !== currentPoi.poiId).map((poi) => {
const laneId = R__namespace.path(["queue", "queueSubtype"], poi);
const lane = getLaneData(laneId)(queueSubtypes);
return { poiId: poi.poiId, ...lane };
});
};
const ensureDefined = (errorMsg) => (value) => {
if (value != null) {
return value;
}
throw Error(errorMsg);
};
const getLaneData = (laneId) => R__namespace.pipe(
R__namespace.find(R__namespace.propEq(laneId, "id")),
ensureDefined(`No queue found with ID: ${laneId}`),
R__namespace.pick(["displayText", "imageId"])
);
app.bus.on("poi/addOtherSecurityLanes", ({ poi }) => addOtherSecurityLanesIfNeeded(poi));
const isFullUrl = (url) => !!url?.startsWith("https:");
async function addImages(poi) {
if (!poi) {
return;
}
const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
const size = `${Math.round(351 * dpr)}x${Math.round(197 * dpr)}`;
if (!R__namespace.length(poi.images)) {
poi.images = [];
} else if (!isFullUrl(poi.images[0])) {
poi.images = await Zousan.all(
poi.images.map((imageName) => app.bus.get("venueData/getPoiImageUrl", { imageName, size }))
);
}
if (!R__namespace.length(poi.fullImages)) {
poi.fullImages = [];
} else if (!isFullUrl(poi.fullImages[0]?.url)) {
poi.fullImages = await Zousan.all(
poi.fullImages.map(async ({ url, ...rest }) => {
const fullUrl = await app.bus.get("venueData/getPoiImageUrl", {
imageName: url,
size
});
return { url: fullUrl, ...rest };
})
);
}
return poi;
}
const getUniqueCategories = R__namespace.memoizeWith(R__namespace.identity, R__namespace.pipe(R__namespace.pluck("category"), R__namespace.values, R__namespace.uniq));
app.bus.on("poi/getAllCategories", async () => poisLoaded.then(getUniqueCategories));
app.bus.on("venueData/loadNewVenue", () => {
poisLoaded = new Zousan();
init();
});
app.bus.on("poi/setDynamicData", ({ plugin, idValuesMap }) => {
poisLoaded.then((pois) => {
for (const poiId in idValuesMap) {
const dynamicData = pois[poiId].dynamicData || {};
dynamicData[plugin] = { ...idValuesMap[poiId] };
const newPoi = R__namespace.mergeRight(pois[poiId], { dynamicData });
pois[poiId] = newPoi;
}
});
});
app.bus.on("poi/setCoreAttributes", (idAttributesMap) => {
poisLoaded.then((pois) => {
const updatedPois = [];
for (const poiId in idAttributesMap) {
const changed = applyPoiNameOverride(pois, poiId, idAttributesMap[poiId]?.name);
if (changed) {
app.bus.send("map/mutateFeature", { poiId, name: pois[poiId].name });
updatedPois.push(pois[poiId]);
}
}
if (updatedPois.length > 0) {
app.bus.send("poi/updated", { pois: updatedPois });
}
});
});
const runTest = async (testRoutine) => {
await testRoutine();
return poisLoaded;
};
return {
init,
runTest,
internal: {
addImages,
pseudoTransPoi,
applyPoiNameOverride
}
};
}
function applyPoiNameOverride(pois, poiId, name) {
if (!pois[poiId]) {
return false;
}
if (!name) {
pois[poiId].name = pois[poiId].staticName;
} else {
pois[poiId].name = name;
}
return true;
}
function pseudoTransPoi(poi, lang) {
["description", "nearbyLandmark", "name", "phone", "operationHours"].forEach((p) => {
if (poi[p]) {
poi[p] = i18n.toLang(poi[p], lang);
}
});
if (poi.keywords) {
poi.keywords = poi.keywords.map((keyword) => {
keyword.name = i18n.toLang(keyword.name, lang);
return keyword;
});
}
if (poi.position.floorName) {
poi.position.floorName = i18n.toLang(poi.position.floorName, lang);
}
if (poi.position.structureName) {
poi.position.structureName = i18n.toLang(poi.position.structureName, lang);
}
return poi;
}
exports.applyPoiNameOverride = applyPoiNameOverride;
exports.create = create;