diff --git a/README.md b/README.md index 6e5941e..f3576d7 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,47 @@ let pathFinder = aStar(graph, { }); ``` +## blocked paths + +In scenarios where a path might be temporarily blocked between two nodes a `blocked()` function +may be supplied to resolve blocked routes during path finding. + +For example, train routes with service disruptions could be modelled as follows: + +``` js +let createGraph = require('ngraph.graph'); +let graph = createGraph(); + +// Our graph has cities: +graph.addNode('NYC'); +graph.addNode('Philadelphia'); +graph.addNode('Baltimore'); +graph.addNode('Pittsburgh'); +graph.addNode('Washington'); + +// and railroads: +graph.addLink('NYC', 'Philadelphia', { disruption: false }); +graph.addLink('Philadelphia', 'Baltimore', { disruption: true }); +graph.addLink('Philadelphia', 'Pittsburgh', { disruption: false }); +graph.addLink('Pittsburgh', 'Washington', { disruption: false }); +graph.addLink('Baltimore', 'Washington', { disruption: false }); +``` + +While the Philadelphia to Baltimore route is facing a service disruption, the alternative +route to Washington is via Pittsburgh. The following is an example `blocked()` function implementation +that may be supplied to yield this result: + +``` js +let path = require('ngraph.path'); + +let pathFinder = path.aStar(graph, { + blocked(fromNode, toNode, link) { + return link.data.disruption; + }, +}); +let result = pathFinder.find('NYC', 'Washington'); +``` + ## available finders The library implements a few A* based path finders: diff --git a/a-star/a-greedy-star.js b/a-star/a-greedy-star.js index 4f8cb53..a4f200f 100644 --- a/a-star/a-greedy-star.js +++ b/a-star/a-greedy-star.js @@ -27,6 +27,9 @@ module.exports.l1 = heuristics.l1; * @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph * * @param {Object} options that configures search + * @param {Function(a, b, link)} options.blocked - a function that returns `true` if the link between + * nodes `a` and `b` are blocked paths. This function is useful for temporarily blocking routes + * while allowing the graph to be reused without rebuilding. * @param {Function(a, b)} options.heuristic - a function that returns estimated distance between * nodes `a` and `b`. Defaults function returns 0, which makes this search equivalent to Dijkstra search. * @param {Function(a, b)} options.distance - a function that returns actual distance between two diff --git a/a-star/a-star.js b/a-star/a-star.js index 6a3b4e3..bbcd79e 100644 --- a/a-star/a-star.js +++ b/a-star/a-star.js @@ -23,6 +23,9 @@ module.exports.l1 = heuristics.l1; * * @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph * @param {Object} options that configures search + * @param {Function(a, b, link)} options.blocked - a function that returns `true` if the link between + * nodes `a` and `b` are blocked paths. This function is useful for temporarily blocking routes + * while allowing the graph to be reused without rebuilding. * @param {Function(a, b)} options.heuristic - a function that returns estimated distance between * nodes `a` and `b`. This function should never overestimate actual distance between two * nodes (otherwise the found path will not be the shortest). Defaults function returns 0, diff --git a/a-star/nba/index.js b/a-star/nba/index.js index 168329b..f78cd60 100644 --- a/a-star/nba/index.js +++ b/a-star/nba/index.js @@ -22,6 +22,9 @@ module.exports.l1 = heuristics.l1; * * @param {ngraph.graph} graph instance. See https://github.com/anvaka/ngraph.graph * @param {Object} options that configures search + * @param {Function(a, b, link)} options.blocked - a function that returns `true` if the link between + * nodes `a` and `b` are blocked paths. This function is useful for temporarily blocking routes + * while allowing the graph to be reused without rebuilding. * @param {Function(a, b)} options.heuristic - a function that returns estimated distance between * nodes `a` and `b`. This function should never overestimate actual distance between two * nodes (otherwise the found path will not be the shortest). Defaults function returns 0, @@ -37,6 +40,9 @@ function nba(graph, options) { var oriented = options.oriented; var quitFast = options.quitFast; + var blocked = options.blocked; + if (!blocked) blocked = defaultSettings.blocked; + var heuristic = options.heuristic; if (!heuristic) heuristic = defaultSettings.heuristic; @@ -178,6 +184,8 @@ function nba(graph, options) { if (otherSearchState.closed) return; + if (blocked(cameFrom.node, otherNode, link)) return; + var tentativeDistance = cameFrom.g1 + distance(cameFrom.node, otherNode, link); if (tentativeDistance < otherSearchState.g1) { @@ -206,6 +214,8 @@ function nba(graph, options) { if (otherSearchState.closed) return; + if (blocked(cameFrom.node, otherNode, link)) return; + var tentativeDistance = cameFrom.g2 + distance(cameFrom.node, otherNode, link); if (tentativeDistance < otherSearchState.g2) { diff --git a/test/nba.js b/test/nba.js index 36410c2..0987bed 100644 --- a/test/nba.js +++ b/test/nba.js @@ -24,6 +24,29 @@ test('it can find path', t => { t.end(); }); +test('it can find path with blocked links', t => { + let graph = createGraph(); + + graph.addLink('a', 'b', {blocked: true}); + graph.addLink('a', 'c', {blocked: false}); + graph.addLink('c', 'd', {blocked: false}); + graph.addLink('b', 'd', {blocked: false}); + + + var pathFinder = nba(graph, { + blocked(a, b, link) { + return link.data.blocked; + } + }); + let path = pathFinder.find('a', 'd'); + + t.equal(path[0].id, 'd', 'd is here'); + t.equal(path[1].id, 'c', 'c is here'); + t.equal(path[2].id, 'a', 'a is here'); + t.end(); +}); + + test('it can find directed path', t => { let graph = createGraph();