CoreDijkstra.java

/*  This file is part of Openrouteservice.
 *
 *  Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
 *  GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.

 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License along with this library;
 *  if not, see <https://www.gnu.org/licenses/>.
 */
package org.heigit.ors.routing.graphhopper.extensions.core;

import com.carrotsearch.hppc.IntObjectMap;
import com.graphhopper.coll.GHIntObjectHashMap;
import com.graphhopper.routing.ch.CHEntry;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.RoutingCHEdgeExplorer;
import com.graphhopper.storage.RoutingCHEdgeIterator;
import com.graphhopper.storage.RoutingCHGraph;
import com.graphhopper.util.Parameters;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.PriorityQueue;

/**
 * Calculates best path using core routing algorithm.
 * <p>
 * This code is based on that from GraphHopper GmbH.
 *
 * @author Peter Karich
 * @author Andrzej Oles
 */

public class CoreDijkstra extends AbstractCoreRoutingAlgorithm {
    PriorityQueue<CHEntry> fromPriorityQueueCH;
    PriorityQueue<CHEntry> toPriorityQueueCH;
    PriorityQueue<CHEntry> fromPriorityQueueCore;
    PriorityQueue<CHEntry> toPriorityQueueCore;

    IntObjectMap<CHEntry> bestWeightMapFromCH;
    IntObjectMap<CHEntry> bestWeightMapToCH;
    IntObjectMap<CHEntry> bestWeightMapOtherCH;

    IntObjectMap<List<CHEntry>> bestWeightMapFromCore;
    IntObjectMap<List<CHEntry>> bestWeightMapToCore;
    IntObjectMap<List<CHEntry>> bestWeightMapOtherCore;

    CHEntry currFrom;
    CHEntry currTo;

    public CoreDijkstra(RoutingCHGraph graph, Weighting weighting) {
        super(graph, weighting);
    }

    @Override
    protected void initCollections(int size) {
        fromPriorityQueueCH = new PriorityQueue<>(size);
        toPriorityQueueCH = new PriorityQueue<>(size);
        fromPriorityQueueCore = new PriorityQueue<>(size);
        toPriorityQueueCore = new PriorityQueue<>(size);

        bestWeightMapFromCH = new GHIntObjectHashMap<>(size);
        bestWeightMapToCH = new GHIntObjectHashMap<>(size);
        bestWeightMapFromCore = new GHIntObjectHashMap<>(size);
        bestWeightMapToCore = new GHIntObjectHashMap<>(size);
    }

    @Override
    public void initFrom(int from, double weight, long time) {
        currFrom = createCHEntry(from, weight, time);
        fromPriorityQueueCH.add(currFrom);
        bestWeightMapFromCH.put(from, currFrom);
        if (currTo != null) {
            bestWeightMapOtherCH = bestWeightMapToCH;
            updateBestPathCH(currTo, from, false);
        }
    }

    @Override
    public void initTo(int to, double weight, long time) {
        currTo = createCHEntry(to, weight, time);
        toPriorityQueueCH.add(currTo);
        bestWeightMapToCH.put(to, currTo);
        if (currFrom != null) {
            bestWeightMapOtherCH = bestWeightMapFromCH;
            updateBestPathCH(currFrom, to, true);
        }
    }

    @Override
    public boolean fillEdgesFrom() {
        if (fromPriorityQueueCH.isEmpty())
            return false;

        currFrom = fromPriorityQueueCH.poll();

        if (isCoreNode(currFrom.adjNode)) {
            // core entry point, do not relax its edges
            fromPriorityQueueCore.add(currFrom);
            // for regular CH Dijkstra we don't expect an entry to exist because the picked node is supposed to be already settled
            if (considerTurnRestrictions(currFrom.adjNode))
                initBestWeightMapEntryList(bestWeightMapFromCore, currFrom.adjNode).add(currFrom);
        } else {
            bestWeightMapOtherCH = bestWeightMapToCH;
            fillEdges(currFrom, fromPriorityQueueCH, bestWeightMapFromCH, null, outEdgeExplorer, false);
            visitedCountFrom1++;
        }

        return true;
    }

    @Override
    public boolean fillEdgesTo() {
        if (toPriorityQueueCH.isEmpty())
            return false;

        currTo = toPriorityQueueCH.poll();

        if (isCoreNode(currTo.adjNode)) {
            // core entry point, do not relax its edges
            toPriorityQueueCore.add(currTo);
            // for regular CH Dijkstra we don't expect an entry to exist because the picked node is supposed to be already settled
            if (considerTurnRestrictions(currTo.adjNode))
                initBestWeightMapEntryList(bestWeightMapToCore, currTo.adjNode).add(currTo);
        } else {
            bestWeightMapOtherCH = bestWeightMapFromCH;
            fillEdges(currTo, toPriorityQueueCH, bestWeightMapToCH, null, inEdgeExplorer, true);
            visitedCountTo1++;
        }

        return true;
    }

    List<CHEntry> initBestWeightMapEntryList(IntObjectMap<List<CHEntry>> bestWeightMap, int traversalId) {
        if (bestWeightMap.get(traversalId) != null)
            throw new IllegalStateException("Core entry point already exists in best weight map.");

        List<CHEntry> entryList = new ArrayList<>(5);// TODO: Proper assessment of the optimal size
        bestWeightMap.put(traversalId, entryList);

        return entryList;
    }

    public boolean fillEdgesFromCore() {
        if (fromPriorityQueueCore.isEmpty())
            return false;

        currFrom = fromPriorityQueueCore.poll();

        bestWeightMapOtherCH = bestWeightMapToCH;
        bestWeightMapOtherCore = bestWeightMapToCore;
        fillEdges(currFrom, fromPriorityQueueCore, bestWeightMapFromCH, bestWeightMapFromCore, outEdgeExplorer, false);
        visitedCountFrom2++;

        return true;
    }

    public boolean fillEdgesToCore() {
        if (toPriorityQueueCore.isEmpty())
            return false;

        currTo = toPriorityQueueCore.poll();

        bestWeightMapOtherCH = bestWeightMapFromCH;
        bestWeightMapOtherCore = bestWeightMapFromCore;
        fillEdges(currTo, toPriorityQueueCore, bestWeightMapToCH, bestWeightMapToCore, inEdgeExplorer, true);
        visitedCountTo2++;

        return true;
    }

    @Override
    public boolean finishedPhase1() {
        // we need to finish BOTH searches for CH!
        if (finishedFrom && finishedTo)
            return true;

        double fromWeight = currFrom.weight;
        double toWeight = currTo.weight;

        // changed also the final finish condition for CH
        if (!fromPriorityQueueCore.isEmpty())
            fromWeight = Math.min(fromPriorityQueueCore.peek().weight, fromWeight);
        if (!toPriorityQueueCore.isEmpty())
            toWeight = Math.min(toPriorityQueueCore.peek().weight, toWeight);

        return fromWeight >= bestWeight && toWeight >= bestWeight;
    }

    @Override
    void runPhase2() {
        finishedFrom = fromPriorityQueueCore.isEmpty();
        if (!finishedFrom)
            currFrom = fromPriorityQueueCore.peek();

        finishedTo = toPriorityQueueCore.isEmpty();
        if (!finishedTo)
            currTo = toPriorityQueueCore.peek();

        while (!finishedPhase2() && !isMaxVisitedNodesExceeded()) {
            finishedFrom = !fillEdgesFromCore();
            finishedTo = !fillEdgesToCore();
        }
    }

    @Override
    public boolean finishedPhase2() {
        if (finishedFrom || finishedTo)
            return true;

        return currFrom.weight + currTo.weight >= bestWeight;
    }

    void fillEdges(CHEntry currEdge, PriorityQueue<CHEntry> prioQueue, IntObjectMap<CHEntry> bestWeightMap, IntObjectMap<List<CHEntry>> bestWeightMapCore, RoutingCHEdgeExplorer explorer, boolean reverse) {
        RoutingCHEdgeIterator iter = explorer.setBaseNode(currEdge.adjNode);
        while (iter.next()) {
            if (!accept(iter, currEdge, reverse))
                continue;

            int traversalId = iter.getAdjNode();
            double tmpWeight = calcEdgeWeight(iter, currEdge, reverse);
            if (Double.isInfinite(tmpWeight))
                continue;

            if (inCore && considerTurnRestrictions(iter.getAdjNode())) {
                List<CHEntry> entries = bestWeightMapCore.get(traversalId);
                CHEntry ee = null;

                if (entries == null) {
                    entries = initBestWeightMapEntryList(bestWeightMapCore, traversalId);
                } else {
                    ListIterator<CHEntry> it = entries.listIterator();
                    while (it.hasNext()) {
                        CHEntry entry = it.next();
                        if (entry.edge == iter.getEdge()) {
                            ee = entry;
                            break;
                        }
                    }
                }

                if (ee == null) {
                    ee = new CHEntry(iter.getEdge(), getIncEdge(iter, reverse), iter.getAdjNode(), tmpWeight);
                    ee.originalEdge = iter.getOrigEdge();
                    entries.add(ee);
                } else if (ee.weight > tmpWeight) {
                    prioQueue.remove(ee);
                    ee.edge = iter.getEdge();
                    ee.originalEdge = iter.getOrigEdge();
                    ee.incEdge = getIncEdge(iter, reverse);
                    ee.weight = tmpWeight;
                } else
                    continue;

                ee.parent = currEdge;
                ee.time = calcEdgeTime(iter, currEdge, reverse);
                prioQueue.add(ee);

                updateBestPathCore(ee, traversalId, reverse);
            } else {
                CHEntry ee = bestWeightMap.get(traversalId);
                if (ee == null) {
                    ee = new CHEntry(iter.getEdge(), getIncEdge(iter, reverse), iter.getAdjNode(), tmpWeight);
                    ee.originalEdge = iter.getOrigEdge();
                    bestWeightMap.put(traversalId, ee);
                } else if (ee.weight > tmpWeight) {
                    prioQueue.remove(ee);
                    ee.edge = iter.getEdge();
                    ee.originalEdge = iter.getOrigEdge();
                    ee.incEdge = getIncEdge(iter, reverse);
                    ee.weight = tmpWeight;
                } else
                    continue;

                ee.parent = currEdge;
                ee.time = calcEdgeTime(iter, currEdge, reverse);
                prioQueue.add(ee);

                updateBestPathCH(ee, traversalId, reverse);
            }
        }
    }

    protected void updateBestPathCH(CHEntry entryCurrent, int traversalId, boolean reverse) {
        CHEntry entryOther = bestWeightMapOtherCH.get(traversalId);
        if (entryOther == null)
            return;

        double newWeight = entryCurrent.weight + entryOther.weight;

        if (newWeight < bestWeight)
            updateBestPath(entryCurrent, entryOther, newWeight, reverse);
    }

    protected void updateBestPathCore(CHEntry entryCurrent, int traversalId, boolean reverse) {
        List<CHEntry> entries = bestWeightMapOtherCore.get(traversalId);
        if (entries == null)
            return;

        ListIterator<CHEntry> it = entries.listIterator();
        while (it.hasNext()) {
            CHEntry entryOther = it.next();
            // u-turn check neccessary because getTurnWeight discards them based on originalEdge which is -1 for shortcuts
            if (entryCurrent.edge == entryOther.edge)
                continue;

            double newWeight = entryCurrent.weight + entryOther.weight;
            if (newWeight < bestWeight) {
                double turnWeight = getTurnWeight(entryCurrent.originalEdge, entryCurrent.adjNode, entryOther.originalEdge, reverse);
                // currently only infinite (u-)turn costs are supported
                if (Double.isInfinite(turnWeight))
                    continue;

                updateBestPath(entryCurrent, entryOther, newWeight, reverse);
            }
        }
    }

    @Override
    public String getName() {
        return Parameters.Algorithms.DIJKSTRA_BI;
    }
}