package edu.kit.pse.ws2013.routekit.map; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.IntBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel.MapMode; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import edu.kit.pse.ws2013.routekit.models.Weights; import edu.kit.pse.ws2013.routekit.profiles.Profile; /** * An edge-based graph of the street network. The nodes of this graph are * identical to to the edges of the corresponding {@link Graph} and are * therefore referred to as "edges". The actual edges of this graph represent * possible turns and are referred to as "turns". * * <p> * Note that this graph data structure is profile-independent and is built after * the corresponding {@link Graph} when importing a new map. Only in combination * of profile-specific {@link Weights} it can be used for route calculation. * * @see StreetMap */ public class EdgeBasedGraph { private int[] partitions; private int[] edges; private int[] turns; private byte[] turnTypes; private Map<Integer, Restriction> restrictions; private int[] turnsReverse; private EdgeBasedGraph(int[] edges, int[] turns, byte[] turnTypes, Map<Integer, Restriction> restrictions) { this.edges = edges; this.turns = turns; this.turnTypes = turnTypes; this.restrictions = restrictions; this.turnsReverse = new int[turns.length]; int currentEdge = 0; for (int i = 0; i < turnsReverse.length; i++) { while (currentEdge + 1 < edges.length && edges[currentEdge + 1] <= i) { currentEdge++; } turnsReverse[i] = currentEdge; } reverseGraph(); } /** * Creates a new {@code EdgeBasedGraph} from the given adjacency field. * * @param edges * the node array (edges in {@link Graph}) of the adjacency field * @param turns * the edge array (turns) of the adjacency field * @param turnTypes * the types of the turns * @param restrictions * the restrictions of the turns */ public EdgeBasedGraph(int[] edges, int[] turns, TurnType[] turnTypes, Map<Integer, Restriction> restrictions) { this(edges, turns, turnTypesToBytes(turnTypes), restrictions); } private static byte[] turnTypesToBytes(TurnType[] turnTypes) { byte[] byteTurnTypes = new byte[turnTypes.length]; for (int i = 0; i < turnTypes.length; i++) { byteTurnTypes[i] = (byte) turnTypes[i].ordinal(); } return byteTurnTypes; } /** * Array[edgeId] of rturns-index. index of the first turn for this edge in * {@link #rturns}; */ private int[] redges; /** * Array[rturs-index] of turnIds. The incoming turns. */ private int[] rturns; /** * Ensures that the reverse access structure (for * {@link #getIncomingTurns(int)}) is built. */ private void reverseGraph() { if (redges != null) { return; } redges = new int[edges.length]; rturns = new int[turns.length]; for (int i = 0; i < turns.length; i++) { redges[turns[i]]++; } int sum = 0; for (int i = 0; i < redges.length; i++) { sum += redges[i]; redges[i] = sum; } for (int i = 0; i < turns.length; i++) { rturns[--redges[turns[i]]] = i; } } /** * Returns the partition in which the given edge (i. e. the node in * {@link Graph}) is located. * * @param edge * an edge * @return the partition in which the edge lies, or a standard partition if * no partitions have been set yet */ public int getPartition(int edge) { return partitions[edge]; } /** * Sets the partitions of this graph. The indices of {@code partitions} * correspond to the edges of the graph. * * @param partitions * the new partitions */ public void setPartitions(int[] partitions) { this.partitions = partitions; } /** * Returns the edge onto which the given turn leads. * * @param turn * a turn * @return the target edge of the turn */ public int getTargetEdge(int turn) { return turns[turn]; } /** * Returns the {@link TurnType} of the given turn. * * @param turn * a turn * @return the type of the turn */ public TurnType getTurnType(int turn) { return TurnType.values()[turnTypes[turn]]; } /** * Returns the edge from which the given turn exists. * * @param turn * a turn * @return the start edge of the turn */ public int getStartEdge(int turn) { return turnsReverse[turn]; } /** * Determines whether the use of the given turn is allowed according to the * specified profile. * * @param turn * the turn in question * @param profile * the profile in use * @return {@code true} if the turn can be used, otherwise {@code false} */ public boolean allowsTurn(int turn, Profile profile) { Restriction restriction = restrictions.get(turn); return restriction == null || restriction.allows(profile); } /** * Returns a set of all turns <i>from</i> the given edge. * * @param edge * an edge * @return all outgoing turns of the edge */ public Set<Integer> getOutgoingTurns(int edge) { return new IntIntervalSet(edges[edge], (edge == edges.length - 1 ? turns.length : edges[edge + 1]) - edges[edge]); } /** * Returns a set of all turns <i>onto</i> the given edge. * * @param edge * an edge * @return all incoming turns of the edge */ public Set<Integer> getIncomingTurns(int edge) { return new IntArraySet(redges[edge], (edge == redges.length - 1 ? rturns.length : redges[edge + 1]) - redges[edge], rturns); } /** * Returns the number of turns in this graph. * * @return the number of turns */ public int getNumberOfTurns() { return turns.length; } @Override public boolean equals(Object obj) { if (obj instanceof EdgeBasedGraph) { EdgeBasedGraph other = (EdgeBasedGraph) obj; return Arrays.equals(edges, other.edges) && Arrays.equals(turns, other.turns) && Arrays.equals(turnTypes, other.turnTypes) && Arrays.equals(partitions, other.partitions) && restrictions.equals(other.restrictions); } return false; } @Override public int hashCode() { return edges[0] ^ turns[0]; } /** * Save this edge-based graph to the specified file. * <p> * The only thing you need to know about the format of the file is that * {@link #load(File)} can parse it; the rest of this documentation is * purely informational, as the format may change in the future (but we’ll * try and maintain backwards compatibility). * <h4>The file format</h4> * <h5>Header</h5> * <ul> * <li>Magic: bytes {@code 0x00}, {@code 0x02}, ASCII {@code routeKIT}</li> * <li>ASCII {@code e} (for "edge-based graph")</li> * <li>byte {@code 0x01} (for version 1, but future versions won’t * necessarily use the same header format)</li> * <li>ASCII {@code End of Transmission}</li> * </ul> * <h5>Edges, Turns, Partitions</h5> * <ul> * <li>int number of edges, int number of turns (both four bytes, high byte * first)</li> * <li>ints edges (all four bytes, high byte first)</li> * <li>ints turns (all four bytes, high byte first)</li> * <li>ints partitions (all four bytes, high byte first)</li> * <li>bytes turn types (all one byte)</li> * </ul> * <h5>Restrictions</h5> * <ul> * <li>For each {@link Restriction}, int number of turns with this * restriction (four bytes, high byte first), then the {@link Restriction} * (see {@link Restriction#save(java.io.DataOutput)}), then ints turns (all * four bytes, high byte first)</li> * <li>int {@code 0} (four bytes, high byte first)</li> * </ul> * * @param file * The file. * @throws IOException * If an I/O error occurs. */ public void save(File file) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { raf.writeUTF("routeKIT"); raf.writeByte(0x65); // 'e', for "edge-based graph" raf.writeByte(1); // version 1 raf.writeByte(0x04); // End of Transmission int nEdges = edges.length; int nTurns = turns.length; raf.writeInt(nEdges); raf.writeInt(nTurns); final int headerLength = 2 + "routeKIT".length() + 3 + 8; assert (raf.getFilePointer() == headerLength); int intsLength = nEdges * 4 + nTurns * 4 + nEdges * 4; int bytesLength = nTurns; final int dataLength = intsLength + bytesLength; MappedByteBuffer b = raf.getChannel().map(MapMode.READ_WRITE, headerLength, dataLength); IntBuffer ints = b.asIntBuffer(); ints.put(edges, 0, nEdges); ints.put(turns, 0, nTurns); ints.put(partitions, 0, nEdges); assert (ints.position() == intsLength / 4); b.position(intsLength); b.put(turnTypes, 0, nTurns); assert (b.position() == dataLength); raf.seek(headerLength + dataLength); Map<Restriction, Set<Integer>> reverseRestrictions = new HashMap<>(); for (int i = 0; i < nTurns; i++) { Restriction r = restrictions.get(i); if (r != null) { Set<Integer> current = reverseRestrictions.get(r); if (current == null) { current = new HashSet<>(); } current.add(i); reverseRestrictions.put(r, current); } } for (Entry<Restriction, Set<Integer>> entry : reverseRestrictions .entrySet()) { raf.writeInt(entry.getValue().size()); entry.getKey().save(raf); for (int i : entry.getValue()) { raf.writeInt(i); } } raf.writeInt(0); } } /** * Loads a graph from the specified file. * <p> * This method can parse files generated by the current version of * {@link #save(File)}, and it will also attempt to parse files generated by * earlier versions of that method. For the file format, see there. * * @param file * The file. * @return A {@link Graph} loaded from {@code file}. * @throws IOException * If an I/O error occurs. */ public static EdgeBasedGraph load(File file) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { if (!raf.readUTF().equals("routeKIT")) { throw new IOException("Wrong magic!"); } if (raf.readByte() != 0x65) { // 'e', for "edge-based graph" throw new IOException("Wrong file type!"); } if (raf.readByte() != 1) { // version 1 throw new IOException("Unsupported version!"); } if (raf.readByte() != 0x04) { // End of Transmission throw new IOException("Wrong magic!"); } int nEdges = raf.readInt(); int nTurns = raf.readInt(); final int headerLength = 2 + "routeKIT".length() + 3 + 8; assert (raf.getFilePointer() == headerLength); int intsLength = nEdges * 4 + nTurns * 4 + nEdges * 4; int bytesLength = nTurns; final int dataLength = intsLength + bytesLength; int[] edges = new int[nEdges]; int[] turns = new int[nTurns]; int[] partitions = new int[nEdges]; byte[] turnTypes = new byte[nTurns]; MappedByteBuffer b = raf.getChannel().map(MapMode.READ_ONLY, headerLength, dataLength); IntBuffer ints = b.asIntBuffer(); ints.get(edges, 0, nEdges); ints.get(turns, 0, nTurns); ints.get(partitions, 0, nEdges); b.position(intsLength); b.get(turnTypes, 0, nTurns); assert (b.position() == dataLength); raf.seek(headerLength + dataLength); Map<Integer, Restriction> restrictions = new HashMap<>(); int length; while ((length = raf.readInt()) != 0) { Restriction r = Restriction.load(raf); while (length-- > 0) { restrictions.put(raf.readInt(), r); } } EdgeBasedGraph ret = new EdgeBasedGraph(edges, turns, turnTypes, restrictions); ret.setPartitions(partitions); return ret; } } /** * Returns the number of street sections. * * @return the number of street sections */ public int getNumberOfEdges() { return edges.length; } }