UNPKG

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
'use strict'; 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;