/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.reader.gtfs;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.util.EdgeIterator;
import com.graphhopper.util.EdgeIteratorState;
import java.time.Instant;
import java.util.*;
/**
* Implements a Multi-Criteria Label Setting (MLS) path finding algorithm
* with the criteria earliest arrival time and number of transfers.
* <p>
*
* @author Michael Zilske
* @author Peter Karich
*/
class MultiCriteriaLabelSetting {
private final PtFlagEncoder flagEncoder;
private final PtTravelTimeWeighting weighting;
private final SetMultimap<Integer, Label> fromMap;
private final PriorityQueue<Label> fromHeap;
private final int maxVisitedNodes;
private final boolean reverse;
private final double maxWalkDistancePerLeg;
private final double maxTransferDistancePerLeg;
private final boolean mindTransfers;
private long rangeQueryEndTime;
private int visitedNodes;
private final GraphExplorer explorer;
MultiCriteriaLabelSetting(GraphExplorer explorer, Weighting weighting, boolean reverse, double maxWalkDistancePerLeg, double maxTransferDistancePerLeg, boolean mindTransfers, int maxVisitedNodes) {
this.weighting = (PtTravelTimeWeighting) weighting;
this.flagEncoder = (PtFlagEncoder) weighting.getFlagEncoder();
this.maxVisitedNodes = maxVisitedNodes;
this.explorer = explorer;
this.reverse = reverse;
this.maxWalkDistancePerLeg = maxWalkDistancePerLeg;
this.maxTransferDistancePerLeg = maxTransferDistancePerLeg;
this.mindTransfers = mindTransfers;
fromHeap = new PriorityQueue<>(new Comparator<Label>() {
@Override
public int compare(Label o1, Label o) {
return Long.compare(queueCriterion(o1), queueCriterion(o));
}
@Override
public boolean equals(Object obj) {
return false;
}
});
fromMap = HashMultimap.create();
}
private long queueCriterion(Label o1) {
return currentTimeCriterion(o1) + o1.nTransfers + o1.nWalkDistanceConstraintViolations;
}
Set<Label> calcPaths(int from, Set<Integer> to, Instant startTime, Instant rangeQueryEndTime) {
this.rangeQueryEndTime = rangeQueryEndTime.toEpochMilli();
Set<Label> targetLabels = new HashSet<>();
Label label = new Label(startTime.toEpochMilli(), EdgeIterator.NO_EDGE, from, 0, 0, 0.0, Long.MAX_VALUE, null);
fromMap.put(from, label);
if (to.contains(from)) {
targetLabels.add(label);
}
while (true) {
visitedNodes++;
if (maxVisitedNodes < visitedNodes)
break;
for (EdgeIteratorState edge : explorer.exploreEdgesAround(label)) {
GtfsStorage.EdgeType edgeType = flagEncoder.getEdgeType(edge.getFlags());
long nextTime;
if (reverse) {
nextTime = label.currentTime - explorer.calcTravelTimeMillis(edge, label.currentTime);
} else {
nextTime = label.currentTime + explorer.calcTravelTimeMillis(edge, label.currentTime);
}
int nTransfers = label.nTransfers + weighting.calcNTransfers(edge);
long firstPtDepartureTime = label.firstPtDepartureTime;
if (!reverse && edgeType == GtfsStorage.EdgeType.BOARD && firstPtDepartureTime == Long.MAX_VALUE) {
firstPtDepartureTime = nextTime;
}
if (reverse && edgeType == GtfsStorage.EdgeType.ALIGHT && firstPtDepartureTime == Long.MAX_VALUE) {
firstPtDepartureTime = nextTime;
}
double walkDistanceOnCurrentLeg = (!reverse && edgeType == GtfsStorage.EdgeType.BOARD || reverse && edgeType == GtfsStorage.EdgeType.ALIGHT) ? 0 : (label.walkDistanceOnCurrentLeg + weighting.getWalkDistance(edge));
boolean isTryingToReEnterPtAfterTransferWalking = (!reverse && edgeType == GtfsStorage.EdgeType.ENTER_PT || reverse && edgeType == GtfsStorage.EdgeType.EXIT_PT) && label.nTransfers > 0 && label.walkDistanceOnCurrentLeg > maxTransferDistancePerLeg;
int nWalkDistanceConstraintViolations = Math.min(1, label.nWalkDistanceConstraintViolations + (
isTryingToReEnterPtAfterTransferWalking ? 1 : (label.walkDistanceOnCurrentLeg <= maxWalkDistancePerLeg && walkDistanceOnCurrentLeg > maxWalkDistancePerLeg ? 1 : 0)));
Set<Label> sptEntries = fromMap.get(edge.getAdjNode());
Label nEdge = new Label(nextTime, edge.getEdge(), edge.getAdjNode(), nTransfers, nWalkDistanceConstraintViolations, walkDistanceOnCurrentLeg, firstPtDepartureTime, label);
if (isNotEqualToAnyOf(nEdge, sptEntries) && isNotDominatedByAnyOf(nEdge, sptEntries) && isNotDominatedWithoutTieBreaksByAnyOf(nEdge, targetLabels)) {
removeDominated(nEdge, sptEntries);
if (to.contains(edge.getAdjNode())) {
removeDominated(nEdge, targetLabels);
}
fromMap.put(edge.getAdjNode(), nEdge);
if (to.contains(edge.getAdjNode())) {
targetLabels.add(nEdge);
}
fromHeap.add(nEdge);
}
}
if (fromHeap.isEmpty())
break;
label = fromHeap.poll();
}
return filterTargetLabels(targetLabels);
}
private boolean isNotEqualToAnyOf(Label me, Set<Label> sptEntries) {
for (Label they : sptEntries) {
if (me.currentTime == they.currentTime && me.nTransfers == they.nTransfers && me.nWalkDistanceConstraintViolations == they.nWalkDistanceConstraintViolations && me.firstPtDepartureTime == they.firstPtDepartureTime) {
return false;
}
}
return true;
}
private Set<Label> filterTargetLabels(Set<Label> targetLabels) {
HashSet<Label> filteredLabels = new HashSet<>(targetLabels);
for (Label me : new ArrayList<>(filteredLabels)) {
filteredLabels.removeIf(they -> dominatesForFiltering(me, they));
}
filteredLabels.removeIf(they -> they.nWalkDistanceConstraintViolations > 0);
return filteredLabels;
}
private boolean dominatesForFiltering(Label me, Label they) {
if (currentTimeCriterion(me) > currentTimeCriterion(they)) {
return false;
}
if (mindTransfers) {
if (me.nTransfers > they.nTransfers) {
return false;
}
}
if (me.firstPtDepartureTime != Long.MAX_VALUE && they.firstPtDepartureTime != Long.MAX_VALUE
&& firstPtDepartureTimeCriterion(me) < firstPtDepartureTimeCriterion(they)) {
return false;
}
if (me.nWalkDistanceConstraintViolations > they.nWalkDistanceConstraintViolations) {
return false;
}
if (currentTimeCriterion(me) < currentTimeCriterion(they)) {
return true;
}
if (me.nTransfers < they.nTransfers) {
return true;
}
if (me.firstPtDepartureTime != Long.MAX_VALUE && they.firstPtDepartureTime != Long.MAX_VALUE
&& firstPtDepartureTimeCriterion(me) > firstPtDepartureTimeCriterion(they)) {
return true;
}
if (me.nWalkDistanceConstraintViolations > they.nWalkDistanceConstraintViolations) {
return true;
}
return false;
}
private boolean isNotDominatedByAnyOf(Label me, Set<Label> sptEntries) {
for (Label they : sptEntries) {
if (dominates(they, me)) {
return false;
}
}
return true;
}
private boolean isNotDominatedWithoutTieBreaksByAnyOf(Label me, Set<Label> sptEntries) {
for (Label they : sptEntries) {
if (dominatesWithoutTieBreaks(they, me)) {
return false;
}
}
return true;
}
private void removeDominated(Label me, Set<Label> sptEntries) {
for (Iterator<Label> iterator = sptEntries.iterator(); iterator.hasNext();) {
Label sptEntry = iterator.next();
if (dominatesWithoutTieBreaks(me, sptEntry)) {
fromHeap.remove(sptEntry);
iterator.remove();
}
}
}
private boolean dominates(Label me, Label they) {
if (currentTimeCriterion(me) + currentTimeSlack(me, they) > currentTimeCriterion(they))
return false;
if (mindTransfers) {
if (me.nTransfers + nTransfersSlack(me, they) > they.nTransfers)
return false;
}
if (me.nWalkDistanceConstraintViolations + nWalkDistanceConstraintViolationsSlack(me, they) > they.nWalkDistanceConstraintViolations)
return false;
if (currentTimeCriterion(me) + currentTimeSlack(me, they) < currentTimeCriterion(they))
return true;
if (mindTransfers) {
if (me.nTransfers + nTransfersSlack(me, they) < they.nTransfers)
return true;
}
if (me.nWalkDistanceConstraintViolations + nWalkDistanceConstraintViolationsSlack(me, they) < they.nWalkDistanceConstraintViolations)
return true;
if (!reverse) {
// Break ties: Fewer transfers is better
if (me.nTransfers < they.nTransfers) {
return true;
}
// Break ties: Leaving later / arriving earlier is better
if (firstPtDepartureTimeCriterion(me) != Long.MAX_VALUE
&& firstPtDepartureTimeCriterion(they) != Long.MAX_VALUE
&& firstPtDepartureTimeCriterion(me) > firstPtDepartureTimeCriterion(they)) {
return true;
}
}
return false;
}
private boolean dominatesWithoutTieBreaks(Label me, Label they) {
if (currentTimeCriterion(me) + currentTimeSlack(me, they) > currentTimeCriterion(they))
return false;
if (mindTransfers) {
if (me.nTransfers + nTransfersSlack(me, they) > they.nTransfers)
return false;
}
if (me.nWalkDistanceConstraintViolations + nWalkDistanceConstraintViolationsSlack(me, they) > they.nWalkDistanceConstraintViolations)
return false;
if (currentTimeCriterion(me) + currentTimeSlack(me, they) < currentTimeCriterion(they))
return true;
if (mindTransfers) {
if (me.nTransfers + nTransfersSlack(me, they) < they.nTransfers)
return true;
}
if (me.nWalkDistanceConstraintViolations + nWalkDistanceConstraintViolationsSlack(me, they) < they.nWalkDistanceConstraintViolations)
return true;
return false;
}
private long currentTimeCriterion(Label label) {
return reverse ? -label.currentTime : label.currentTime;
}
private double nWalkDistanceConstraintViolationsSlack(Label me, Label they) {
return profileQuerySlackComponent(me, they);
}
private double profileQuerySlackComponent(Label me, Label they) {
if ((they.firstPtDepartureTime == Long.MAX_VALUE && me.firstPtDepartureTime != Long.MAX_VALUE && currentTimeCriterion(they) <= rangeQueryEndTimeConstraint()) ||
(they.firstPtDepartureTime != Long.MAX_VALUE && me.firstPtDepartureTime != Long.MAX_VALUE &&
firstPtDepartureTimeCriterion(they) > firstPtDepartureTimeCriterion(me) &&
firstPtDepartureTimeCriterion(they) <= rangeQueryEndTimeConstraint())) {
return Double.POSITIVE_INFINITY;
} else {
return 0;
}
}
private long rangeQueryEndTimeConstraint() {
return reverse ? -rangeQueryEndTime : rangeQueryEndTime;
}
private long firstPtDepartureTimeCriterion(Label label) {
return reverse ? -label.firstPtDepartureTime : label.firstPtDepartureTime;
}
private double nTransfersSlack(Label me, Label they) {
return profileQuerySlackComponent(me, they);
}
private double currentTimeSlack(Label me, Label they) {
return profileQuerySlackComponent(me, they);
}
int getVisitedNodes() {
return visitedNodes;
}
}