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
302 lines (255 loc) • 10.1 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, config) {
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 }
};
// todo R.map may be enough to update dictionary values
const formatPois = (pois, structuresLookup) => {
return R__namespace.pipe(
R__namespace.values,
R__namespace.map(poi => {
poi.distance = null;
poi.isNavigable = poi.isNavigable === undefined || poi.isNavigable === true; // isNavigable is true as default, and is optional in the POI data
// poi.isNavigable = /^[a-mA-M]+/.test(poi.name) // uncomment for easy testing of isNavigable
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] === undefined)
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); // this doesn't change the pois, but warns in the console...
});
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 }) => {
// returns true for exact category matches or parent category matches
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 only connected security checkpoints
.filter(poi => poi.poiId !== currentPoi.poiId) // skip current poi
.map(poi => {
const laneId = R__namespace.path(['queue', 'queueSubtype'], poi);
const lane = getLaneData(laneId)(queueSubtypes);
return { poiId: poi.poiId, ...lane }
})
};
// if the value passed is non-null, simply return it. Else throw the error with the message specified.
// This is useful to insert into pipelines.
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'])
);
/**
* Updates security checkpoint POI with a list of related security checkpoints.
*/
app.bus.on('poi/addOtherSecurityLanes', ({ poi }) => addOtherSecurityLanesIfNeeded(poi));
async function addImages (poi) {
if (!poi) return
const dpr = typeof window === 'undefined' ? 1 : (window.devicePixelRatio || 1);
if (!R__namespace.length(poi.images)) {
poi.images = [];
} else if (!poi.images[0].startsWith('https:')) { // then images are not yet transformed
poi.images = await Zousan.all(
poi.images.map(imageName =>
app.bus.get('venueData/getPoiImageUrl', { imageName, size: `${Math.round(351 * dpr)}x${Math.round(197 * dpr)}` })));
}
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();
});
// See architectural document at https://docs.google.com/document/d/1NoBAboHR9BiX_vvLef-vp3ButrIQDWYofcTsdilEWvs/edit#
app.bus.on('poi/setDynamicData', ({ plugin, idValuesMap }) => {
poisLoaded
.then(pois => {
for (const poiId in idValuesMap) {
// const dd = { [plugin]: idValuesMap[poiId] }
const dynamicData = pois[poiId].dynamicData || {};
dynamicData[plugin] = { ...idValuesMap[poiId] };
const newPoi = R__namespace.mergeRight(pois[poiId], { dynamicData });
pois[poiId] = newPoi;
}
});
});
const runTest = async (testRoutine) => {
await testRoutine();
return poisLoaded
};
return {
init,
runTest,
internal: {
addImages,
pseudoTransPoi
}
}
}
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.create = create;