package edu.kit.pse.ws2013.routekit.precalculation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import edu.kit.pse.ws2013.routekit.map.EdgeBasedGraph;
import edu.kit.pse.ws2013.routekit.map.Graph;
import edu.kit.pse.ws2013.routekit.models.ArcFlags;
import edu.kit.pse.ws2013.routekit.models.ProfileMapCombination;
import edu.kit.pse.ws2013.routekit.models.ProgressReporter;
import edu.kit.pse.ws2013.routekit.models.Weights;
import edu.kit.pse.ws2013.routekit.routecalculation.FibonacciHeap;
import edu.kit.pse.ws2013.routekit.routecalculation.FibonacciHeapEntry;
/**
* A working {@link ArcFlagsCalculator}.
*
* @author Fabian Hafner
* @version 1.0
*
*/
public class ArcFlagsCalculatorImpl implements ArcFlagsCalculator {
protected int[] flagsArray;
protected Graph graph;
protected EdgeBasedGraph edgeBasedGraph;
protected Weights weights;
protected final int nPartitions = 32;
@Override
public void calculateArcFlags(final ProfileMapCombination combination,
final ProgressReporter reporter) {
final long t1 = System.currentTimeMillis();
initData(combination);
reporter.setSubTasks(new float[] { .1f, .9f });
reporter.pushTask("Bereite Partitionen vor");
final List<Set<Integer>> partitions = new ArrayList<Set<Integer>>(
nPartitions);
for (int i = 0; i < nPartitions; i++) {
partitions.add(new HashSet<Integer>());
}
for (int i = 0; i < graph.getNumberOfEdges(); i++) {
partitions.get(edgeBasedGraph.getPartition(i)).add(i);
}
reporter.nextTask("Baue Kürzeste-Pfade-Bäume zu den Schnittkanten der Partitionen auf");
int i = 1;
for (int currentPartition = 0; currentPartition < partitions.size(); currentPartition++) {
final Set<Integer> edgesWithTurnsToOtherPartitions = new HashSet<Integer>();
for (final Integer edge : partitions.get(currentPartition)) {
final Set<Integer> incomingTurns = edgeBasedGraph
.getIncomingTurns(edge);
for (final Integer turn : incomingTurns) {
final int startEdgePartition = edgeBasedGraph
.getPartition(edgeBasedGraph.getStartEdge(turn));
if (startEdgePartition == currentPartition) {
setFlag(turn, currentPartition, flagsArray);
} else {
edgesWithTurnsToOtherPartitions.add(edge);
}
}
}
for (final Integer edge : edgesWithTurnsToOtherPartitions) {
buildReverseShortestPathsTreeAndSetArcFlags(edge, flagsArray);
}
reporter.setProgress(i / (float) nPartitions);
i++;
}
reporter.popTask();
final long t2 = System.currentTimeMillis();
final int time = (int) (t2 - t1);
combination.setArcFlags(new ArcFlags(flagsArray), time);
}
protected void initData(final ProfileMapCombination combination) {
graph = combination.getStreetMap().getGraph();
edgeBasedGraph = combination.getStreetMap().getEdgeBasedGraph();
weights = combination.getWeights();
flagsArray = new int[edgeBasedGraph.getNumberOfTurns()];
}
/**
* Builds the reverse shortest paths tree starting from the given edge using
* a reverse Dijkstra and then sets arc flags accordingly.
*
* @param edge
* the start edge of the shortest paths tree
*/
protected void buildReverseShortestPathsTreeAndSetArcFlags(
final Integer edge, final int[] flagsArray) {
final int edgeCount = edgeBasedGraph.getNumberOfEdges();
final int[] distance = new int[edgeCount];
final int[] next = new int[edgeCount];
final FibonacciHeap fh = new FibonacciHeap();
final FibonacciHeapEntry[] fhList = new FibonacciHeapEntry[edgeCount];
final int edgePartition = edgeBasedGraph.getPartition(edge);
Arrays.fill(distance, Integer.MAX_VALUE);
Arrays.fill(next, -1);
distance[edge] = 0;
fhList[edge] = fh.add(edge, 0);
while (!fh.isEmpty()) {
final int currentEdge = fh.deleteMin().getValue();
fhList[currentEdge] = null;
if (distance[currentEdge] == Integer.MAX_VALUE) {
break;
}
final Set<Integer> incomingTurns = edgeBasedGraph
.getIncomingTurns(currentEdge);
for (final Integer currentTurn : incomingTurns) {
final int startEdge = edgeBasedGraph.getStartEdge(currentTurn);
final int startPartition = edgeBasedGraph
.getPartition(startEdge);
final int endEdge = edgeBasedGraph.getTargetEdge(currentTurn);
final int endPartition = edgeBasedGraph.getPartition(endEdge);
if (!(startPartition == edgePartition && startPartition == endPartition)) {
final int weight = weights.getWeight(currentTurn);
if (weight == Integer.MAX_VALUE) {
continue;
}
final int newDistance = distance[currentEdge] + weight;
if (newDistance < distance[startEdge]) {
distance[startEdge] = newDistance;
next[startEdge] = currentEdge;
final FibonacciHeapEntry toDecrease = fhList[startEdge];
if (toDecrease == null) {
fhList[startEdge] = fh.add(startEdge, newDistance);
} else {
fh.decreaseKey(toDecrease, newDistance);
}
}
}
}
}
for (int currentEdge = 0; currentEdge < next.length; currentEdge++) {
final int nextEdge = next[currentEdge];
if (nextEdge != -1) {
for (final Integer turn : edgeBasedGraph
.getOutgoingTurns(currentEdge)) {
if (edgeBasedGraph.getTargetEdge(turn) == nextEdge) {
setFlag(turn, edgePartition, flagsArray);
break;
}
}
}
}
}
/**
* Sets the given turn's arc flag for the given partition to 1.
*
* @param turn
* the turn
* @param partition
* the partition
*/
protected void setFlag(final int turn, final int partition,
final int[] flagsArray) {
flagsArray[turn] |= 0x1 << partition;
}
}