package org.insa.graph.io; import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import org.insa.graph.AccessRestrictions; import org.insa.graph.AccessRestrictions.AccessMode; import org.insa.graph.AccessRestrictions.AccessRestriction; import org.insa.graph.Arc; import org.insa.graph.Graph; import org.insa.graph.GraphStatistics; import org.insa.graph.Node; import org.insa.graph.Point; import org.insa.graph.RoadInformation; import org.insa.graph.RoadInformation.RoadType; /** * Implementation of {@link GraphReader} to read graph in binary format. * */ public class BinaryGraphReader extends BinaryReader implements GraphReader { // Map version and magic number targeted for this reader. private static final int VERSION = 5; private static final int MAGIC_NUMBER = 0x208BC3B3; // Length of the map id field (in bytes) protected static final int MAP_ID_FIELD_LENGTH = 32; // List of observers protected List observers = new ArrayList<>(); /** * Parse the given long value into a new instance of AccessRestrictions. * * @param access The value to parse. * * @return New instance of access restrictions parsed from the given value. */ protected static AccessRestrictions toAccessInformation(final long access) { // See the following for more information: // https://github.com/Holt59/OSM2Graph/blob/master/src/main/org/laas/osm2graph/model/AccessData.java // The order of values inside this array is VERY IMPORTANT: For allRestrictions, // the order correspond to the 4 bits value (i.e. FORBIDDEN is 0 or PRIVATE is // 2) - UKNOWN is not included because value above 6 (FORESTRY) are all // considered unknown. final AccessRestriction[] allRestrictions = new AccessRestriction[]{ AccessRestriction.FORBIDDEN, AccessRestriction.ALLOWED, AccessRestriction.PRIVATE, AccessRestriction.DESTINATION, AccessRestriction.DELIVERY, AccessRestriction.CUSTOMERS, AccessRestriction.FORESTRY }; // The order of values inside this array is VERY IMPORTANT: The order is such // that each 4-bits group of the long value is processed in the correct order, // i.e. FOOT is processed first (4 lowest bits), and so on. final AccessMode[] allModes = new AccessMode[]{ AccessMode.FOOT, null, AccessMode.BICYCLE, AccessMode.SMALL_MOTORCYCLE, AccessMode.AGRICULTURAL, AccessMode.MOTORCYCLE, AccessMode.MOTORCAR, AccessMode.HEAVY_GOODS, null, AccessMode.PUBLIC_TRANSPORT }; // fill maps... EnumMap restrictions = new EnumMap<>(AccessMode.class); long copyAccess = access; for (AccessMode mode: allModes) { if (mode == null) { continue; // filling cells } int value = (int) (copyAccess & 0xf); if (value < allRestrictions.length) { restrictions.put(mode, allRestrictions[value]); } else { restrictions.put(mode, AccessRestriction.UNKNOWN); } copyAccess = copyAccess >> 4; } return new AccessRestrictions(restrictions); } /** * Convert a character to its corresponding road type. * * @param ch Character to convert. * * @return Road type corresponding to the given character. * */ protected static RoadType toRoadType(char ch) { switch (ch) { case 'a': return RoadType.MOTORWAY; case 'b': return RoadType.TRUNK; case 'c': return RoadType.PRIMARY; case 'd': return RoadType.SECONDARY; case 'e': return RoadType.MOTORWAY_LINK; case 'f': return RoadType.TRUNK_LINK; case 'g': return RoadType.PRIMARY_LINK; case 'h': return RoadType.SECONDARY_LINK; case 'i': return RoadType.TERTIARY; case 'j': return RoadType.RESIDENTIAL; case 'k': case 'l': return RoadType.UNCLASSIFIED; case 'm': return RoadType.LIVING_STREET; case 'n': return RoadType.SERVICE; case 'o': return RoadType.ROUNDABOUT; case 'p': return RoadType.PEDESTRIAN; case 'r': return RoadType.CYCLEWAY; case 's': return RoadType.TRACK; case 'z': return RoadType.COASTLINE; } return RoadType.UNCLASSIFIED; } /** * Create a new BinaryGraphReader that read from the given input stream. * * @param dis Input stream to read from. */ public BinaryGraphReader(DataInputStream dis) { super(MAGIC_NUMBER, VERSION, dis); } @Override public void addObserver(GraphReaderObserver observer) { observers.add(observer); } @Override public Graph read() throws IOException { // Read and check magic number and file version. checkMagicNumberOrThrow(dis.readInt()); checkVersionOrThrow(dis.readInt()); // Read map id. String mapId; String mapName = ""; if (getCurrentVersion() < 6) { mapId = "0x" + Integer.toHexString(dis.readInt()); } else { mapId = readFixedLengthString(MAP_ID_FIELD_LENGTH, "UTF-8"); mapName = dis.readUTF(); } observers.forEach((observer) -> observer.notifyStartReading(mapId)); // Number of descriptors and nodes. int nbDesc = dis.readInt(); int nbNodes = dis.readInt(); // Number of successors for each nodes. int[] nbSuccessors = new int[nbNodes]; int nbTotalSuccessors = 0; // Construct an array list with initial capacity of nbNodes. ArrayList nodes = new ArrayList(nbNodes); // Read nodes. observers.forEach((observer) -> observer.notifyStartReadingNodes(nbNodes)); for (int node = 0; node < nbNodes; ++node) { float longitude = ((float) dis.readInt()) / 1E6f; float latitude = ((float) dis.readInt()) / 1E6f; nbSuccessors[node] = dis.readUnsignedByte(); nbTotalSuccessors += nbSuccessors[node]; final Node aNode = new Node(node, new Point(longitude, latitude)); nodes.add(aNode); observers.forEach((observer) -> observer.notifyNewNodeRead(aNode)); } // Check format. checkByteOrThrow(255); // Read descriptors. RoadInformation[] descs = new RoadInformation[nbDesc]; // Read observers.forEach((observer) -> observer.notifyStartReadingDescriptors(nbDesc)); int maxSpeed = 0; for (int descr = 0; descr < nbDesc; ++descr) { final RoadInformation roadinf = readRoadInformation(); descs[descr] = roadinf; observers.forEach((observer) -> observer.notifyNewDescriptorRead(roadinf)); // Update max speed maxSpeed = Math.max(roadinf.getMaximumSpeed(), maxSpeed); } // Check format. checkByteOrThrow(254); // Read successors and convert to arcs. float maxLength = 0; final int copyNbTotalSuccesors = nbTotalSuccessors; // Stupid Java... observers.forEach((observer) -> observer.notifyStartReadingArcs(copyNbTotalSuccesors)); for (int node = 0; node < nbNodes; ++node) { for (int succ = 0; succ < nbSuccessors[node]; ++succ) { // Read target node number. int destNode = this.read24bits(); // Read information number. int descrNum = this.read24bits(); // Length of the arc. float length; if (getCurrentVersion() < 8) { length = dis.readUnsignedShort(); } else { length = dis.readInt() / 1000.0f; } maxLength = Math.max(length, maxLength); // Number of segments. int nbSegments = dis.readUnsignedShort(); // Chain of points corresponding to the segments. ArrayList points = new ArrayList(nbSegments + 2); points.add(nodes.get(node).getPoint()); for (int seg = 0; seg < nbSegments; ++seg) { Point lastPoint = points.get(points.size() - 1); float dlon = (dis.readShort()) / 2.0e5f; float dlat = (dis.readShort()) / 2.0e5f; points.add(new Point(lastPoint.getLongitude() + dlon, lastPoint.getLatitude() + dlat)); } points.add(nodes.get(destNode).getPoint()); RoadInformation info = descs[descrNum]; Node orig = nodes.get(node); Node dest = nodes.get(destNode); // Add successor to initial arc. Arc arc = Node.linkNodes(orig, dest, length, info, points); observers.forEach((observer) -> observer.notifyNewArcRead(arc)); } } // Check format. checkByteOrThrow(253); observers.forEach((observer) -> observer.notifyEndReading()); this.dis.close(); return new Graph(mapId, mapName, nodes, new GraphStatistics(maxSpeed, maxLength)); } /** * Read the next road information from the stream. * * @return The next RoadInformation in the stream. * * @throws IOException if an error occurs while reading from the stream. */ private RoadInformation readRoadInformation() throws IOException { char type = (char) dis.readUnsignedByte(); int x = dis.readUnsignedByte(); AccessRestrictions access = new AccessRestrictions(); if (getCurrentVersion() >= 7) { access = toAccessInformation(dis.readLong()); } else if (getCurrentVersion() >= 6) { // TODO: Try to create something... dis.readUnsignedShort(); } return new RoadInformation(toRoadType(type), access, (x & 0x80) > 0, (x & 0x7F) * 5, dis.readUTF()); } }