import {GenericBinaryHeapPriorityQueue} from '../structs/genericBinaryHeapPriorityQueue' import {compareNumbers} from '../utils/compare' import {TollFreeVisibilityEdge} from './visibility/TollFreeVisibilityEdge' import {VisibilityEdge} from './visibility/VisibilityEdge' import {VisibilityGraph} from './visibility/VisibilityGraph' import {VisibilityVertex} from './visibility/VisibilityVertex' export class SingleSourceMultipleTargetsShortestPathOnVisibilityGraph { // we are not using the A* algorithm since it does not make much sense for muliple targets // but we use the upper bound heuristic private source: VisibilityVertex private targets: Set private current: VisibilityVertex private closestTarget: VisibilityVertex private upperBound: number = Number.POSITIVE_INFINITY private _visGraph: VisibilityGraph constructor(sourceVisVertex: VisibilityVertex, targetVisVertices: Array, visibilityGraph: VisibilityGraph) { this._visGraph = visibilityGraph this._visGraph.ClearPrevEdgesTable() for (const v of visibilityGraph.Vertices()) v.Distance = Number.POSITIVE_INFINITY this.source = sourceVisVertex this.targets = new Set(targetVisVertices) this.source.Distance = 0 } // Returns a path GetPath(): Array { const pq = new GenericBinaryHeapPriorityQueue(compareNumbers) this.source.Distance = 0 pq.Enqueue(this.source, 0) while (!pq.IsEmpty()) { this.current = pq.Dequeue() if (this.targets.has(this.current)) { break } for (const e of this.current.OutEdges) if (this.PassableOutEdge(e)) this.ProcessNeighbor(pq, e, e.Target) for (const e of this.current.InEdges) if (this.PassableInEdge(e)) this.ProcessNeighbor(pq, e, e.Source) } return this._visGraph.PreviosVertex(this.current) == null ? null : this.CalculatePath() } private PassableOutEdge(e: VisibilityEdge): boolean { return ( e.Source === this.source || this.targets.has(e.Target) || !SingleSourceMultipleTargetsShortestPathOnVisibilityGraph.IsForbidden(e) ) } private PassableInEdge(e: VisibilityEdge): boolean { return ( this.targets.has(e.Source) || e.Target === this.source || !SingleSourceMultipleTargetsShortestPathOnVisibilityGraph.IsForbidden(e) ) } private static IsForbidden(e: VisibilityEdge): boolean { return (e.IsPassable != null && !e.IsPassable()) || e instanceof TollFreeVisibilityEdge } private ProcessNeighbor(pq: GenericBinaryHeapPriorityQueue, l: VisibilityEdge, v: VisibilityVertex) { const len = l.Length const c = this.current.Distance + len if (c >= this.upperBound) { return } if (this.targets.has(v)) { this.upperBound = c this.closestTarget = v } if (v !== this.source && this._visGraph.PreviosVertex(v) == null) { v.Distance = c this._visGraph.SetPreviousEdge(v, l) pq.Enqueue(v, c) } else if (c < v.Distance) { // This condition should never hold for the dequeued nodes. // However because of a very rare case of an epsilon error it might! // In this case DecreasePriority will fail to find "v" and the algorithm will continue working. // Since v is not in the queue changing its .Distance will not mess up the queue. // Changing v.Prev is fine since we come up with a path with an insignificantly // smaller distance. v.Distance = c this._visGraph.SetPreviousEdge(v, l) pq.DecreasePriority(v, c) } } private CalculatePath(): Array { if (this.closestTarget == null) { return null } const ret = new Array() let v = this.closestTarget do { ret.push(v) v = this._visGraph.PreviosVertex(v) } while (v !== this.source) ret.push(this.source) return ret.reverse() } }