UNPKG

@hassanalthaf/indoormaps

Version:

A graph-based indoor mapping and pathfinding library in vanilla JavaScript.

444 lines (349 loc) 15.5 kB
(function (global) { var IndoorMaps = function (options) { return new IndoorMaps.init(options); }; let svgRootNode; let doors = {}; let hallwayPoints = {}; let currentZIndex = 0; let validZIndexes = [0]; let showHallwayPoints = false; //let svgDimensions = {}; let floorPlan = []; let mapSelection = null; IndoorMaps.init = function (options) { this.parseConfiguration(options || {}); this.parseFloorPlan(options.floorSections || {}); this.parseHallwayPoints(options.hallway || {}); //svgDimensions = this.calculateSVGWidthAndHeight(); svgRootNode = this.createSvgNode("svg", { width: '100%', height: '100%' }); this.appendToBody(svgRootNode); // Render the Map this.drawMap(floorPlan[currentZIndex] || {}); if (showHallwayPoints) { this.displayHallwayPoints(options.hallway || {}); } return svgRootNode; }; IndoorMaps.init.prototype = { createSvgNode: function (elementName, attributes = {}, value) { if (elementName === undefined) { throw "Element name not found!"; } let node = document.createElementNS("http://www.w3.org/2000/svg", elementName); for (let attributeKey in attributes) { node.setAttribute(attributeKey, attributes[attributeKey]); } if (value !== undefined) { node.innerHTML = value; } return node; }, appendToBody: function (node) { window.document.body.appendChild(node); }, draw: function (node) { let panZoom = svgRootNode.querySelector('.svg-pan-zoom_viewport'); if (!panZoom) { svgRootNode.append(node); } else { panZoom.append(node); } }, addDoors: function(roomId, doorPoints) { doors[roomId] = doorPoints; for (let i = 0; i < doorPoints.length; i++) { let coordinates = doorPoints[i]; let attributes = { x: coordinates.x, y: coordinates.y, width: coordinates.width || 0, height: coordinates.height || 0, fill: "#FF0000" }; if (coordinates.width > coordinates.height) { attributes.x -= coordinates.width / 2; attributes.y -= coordinates.height; } if (coordinates.height > coordinates.width) { attributes.x -= coordinates.width; attributes.y -= coordinates.height / 2; } let rect = this.createSvgNode("rect", attributes); this.draw(rect); } }, drawMap: function (floorSections) { for (let i = 0; i < floorSections.length; i++) { let section = floorSections[i]; if ( section.x === undefined || section.y === undefined || section.width === undefined || section.height === undefined ) { throw "The x,y coordinates and the width and height are required fields."; } let node = this.createSvgNode("rect", { 'data-id': section.id || '', x: section.x, y: section.y, width: section.width, height: section.height, fill: section.backgroundColor || "#EEEEEE" }); let thisObject = this; node.addEventListener("click", function (e) { if (mapSelection === null) { thisObject.removeRouteIndicators(); mapSelection = this; this.classList.add('selected'); } else { console.log(mapSelection.dataset.id + " : " + this.dataset.id); let paths = thisObject.findPaths(mapSelection.dataset.id, this.dataset.id); let shortestPath = thisObject.findShortestPath(paths); thisObject.drawPath(shortestPath); mapSelection = null; this.classList.add('destination'); } }); this.draw(node); this.addDoors(section.id, section.doors); if (section.label === undefined) { continue; } if (section.label.text === undefined) { throw "The text must be included for the label to be shown. Set label as null if you do not wish to display a label."; } xCoordinate = section.x + (section.label.x || 0); yCoordinate = section.y + (section.label.y || 0); let attributes = { 'x': xCoordinate, 'y': yCoordinate, 'fill': section.label.color || '#333333', 'font-family': section.label.fontStyle || 'Verdana', 'font-size': section.label.fontSize || '10' }; if (section.label.alignment === undefined) { section.label.alignment = "center|center"; } if (section.label.alignment !== undefined) { switch (section.label.alignment) { case "center|center": attributes['text-anchor'] = "middle"; attributes['dominant-baseline'] = "middle"; attributes['x'] = (section.width / 2) + section.x; attributes['y'] = (section.height / 2) + section.y; break; case "horizontal_center": attributes['text-anchor'] = "middle"; attributes['x'] = (section.width / 2) + section.x; break; case "vertical_center": attributes['dominant-baseline'] = "middle"; attributes['y'] = (section.height / 2) + section.y; break; default: throw "Alignment mode requested was not found."; } } let textNode = this.createSvgNode("text", attributes, section.label.text); this.draw(textNode); } }, drawPath: function(path) { for (let i = 0; i < (path.length - 1); i++) { let lineNode = this.createSvgNode('line', { x1: path[i].x, y1: path[i].y, x2: path[i + 1].x, y2: path[i + 1].y, stroke: "green", 'data-type': "route" }); this.draw(lineNode); } }, removeRouteIndicators: function () { let nodes = svgRootNode.querySelectorAll('[data-type="route"]'); let panZoom = svgRootNode.querySelector('.svg-pan-zoom_viewport'); for (let index = 0; index < nodes.length; index++) { if (!panZoom) { svgRootNode.removeChild(nodes[index]); } else { panZoom.removeChild(nodes[index]); } } nodes = svgRootNode.getElementsByClassName('selected'); for (let index = 0; index < nodes.length; index++) { nodes[index].classList.remove('selected'); } nodes = svgRootNode.getElementsByClassName('destination'); for (let index = 0; index < nodes.length; index++) { nodes[index].classList.remove('destination'); } }, displayHallwayPoints: function (hallwayNodes) { for (let i = 0; i < hallwayNodes.length; i++) { this.draw( this.createSvgNode("circle", { cx: hallwayNodes[i].x, cy: hallwayNodes[i].y, r: 2, fill: hallwayNodes[i].fill || "black", 'data-id': hallwayNodes[i].id }) ); } }, findPaths: function (startId, endId) { let paths = []; let startDoors = doors[startId]; let endDoors = doors[endId]; for (let i = 0; i < startDoors.length; i++) { let startingPoint = this.findPointByCoordinates(startDoors[i]); console.log("Starting Point: " + JSON.stringify(startingPoint)); for (let j = 0; j < doors[endId].length; j++) { let endingPoint = this.findPointByCoordinates(endDoors[j]); console.log("Ending Point: " + JSON.stringify(endingPoint)); let potentialPath = this.recursiveIterationOfPoints([startingPoint], endingPoint); console.log(potentialPath); paths = paths.concat(potentialPath); } } for (let id in paths) { paths[id] = this.linkRoomsWithPath(paths[id], startId, endId); } return paths; }, recursiveIterationOfPoints: function(path, end) { let results = []; let current = path[path.length - 1]; console.log(current); if (current.id === end.id) { return [path]; } for (let i = 0; i < current.connected.length; i++) { let nextPoint = hallwayPoints[current.connected[i]]; let found = false; path.forEach( function (point, index) { if (point.id === nextPoint.id) { found = true; } }); if (found) continue; var newPath = path.slice(); newPath.push(nextPoint); results = results.concat(this.recursiveIterationOfPoints(newPath, end)); } return results; }, findPointByCoordinates: function (coordinates) { for (let id in hallwayPoints) { let point = hallwayPoints[id]; if (point.x === coordinates.x && point.y === coordinates.y) { return point; } } }, findActualCoordinatesOfDoorByCoordinates: function (coordinates, roomId) { let validDoors = doors[roomId]; for (let id in validDoors) { if (validDoors[id].x === coordinates.x && validDoors[id].y === coordinates.y) { return validDoors[id].actual; } } }, parseHallwayPoints: function (points) { for (let i = 0; i < points.length; i++) { hallwayPoints[points[i].id] = points[i]; } }, findShortestPath: function(paths) { if (paths.length === 1) return paths[0]; // Set benchmark range let shortestDistance = this.calculateDistanceForPath(paths[0]); let shortestPath = paths[0]; // Skip the first since it's already calculated. for (let i = 1; i < paths.length; i++) { let distance = this.calculateDistanceForPath(paths[i]); if (distance < shortestDistance) { shortestDistance = distance; shortestPath = paths[i]; } } return shortestPath; }, calculateDistanceForPath: function(path) { let totalDistance = 0; for (let i = 0; i < (path.length - 1); i++) { // Formula: sqrt[(x0 - x1)^2 + (y0 - y1)^2] totalDistance += Math.sqrt(Math.pow(path[i].x - path[i + 1].x, 2) + Math.pow(path[i].y - path[i + 1].y, 2)); } return totalDistance; }, linkRoomsWithPath: function (path, startId, endId) { let first = this.findActualCoordinatesOfDoorByCoordinates(path[0], startId); let last = this.findActualCoordinatesOfDoorByCoordinates(path[path.length - 1], endId); path.unshift(first); path.push(last); return path; }, parseConfiguration: function (options) { currentZIndex = options.config.defaultZIndex || 0; validZIndexes = options.config.validZIndexes || [0]; showHallwayPoints = options.config.showHallwayPoints || false; }, parseFloorPlan: function (floorLayout) { for (let i in floorLayout) { if (floorLayout[i].z === undefined) { floorLayout[i].z = 0; } if (!(floorLayout[i].z in floorPlan)) { floorPlan[floorLayout[i].z] = []; } floorPlan[floorLayout[i].z].push(floorLayout[i]); } }, calculateSVGWidthAndHeight: function () { let dimensions = { width: 0, height: 0 }; let floorLayout = floorPlan[currentZIndex]; let first = true; for (let i in floorLayout) { let calculatedWidth = floorLayout[i].width + floorLayout[i].x; let calculatedHeight = floorLayout[i].height + floorLayout[i].y; if (first) { dimensions.width = calculatedWidth; dimensions.height = calculatedHeight; first = false; } if (calculatedWidth > dimensions.width) { dimensions.width = calculatedWidth; } if (calculatedHeight > dimensions.height) { dimensions.height = calculatedHeight; } } for (let i in hallwayPoints) { let currentWidth = hallwayPoints[i].x; let currentHeight = hallwayPoints[i].y; if (currentWidth > dimensions.width) { dimensions.width = currentWidth; } if (currentHeight > dimensions.height) { dimensions.height = currentHeight; } } return dimensions; } }; window.indoorMaps = IndoorMaps; }(window));