package org.insa.graph.io; import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.Map; import org.insa.graph.Arc; import org.insa.graph.Graph; import org.insa.graph.GraphInformation; import org.insa.graph.Node; import org.insa.graph.Point; import org.insa.graph.RoadInformation; import org.insa.graph.RoadInformation.AccessMode; import org.insa.graph.RoadInformation.AccessRestriction; import org.insa.graph.RoadInformation.RoadType; public class BinaryGraphReaderInsa2018 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; // Some masks... private static final int MASK_UNKNOWN = 0x01; private static final int MASK_PRIVATE = 0x02; @SuppressWarnings("unused") private static final int MASK_AGRICULTURAL = 0x04; @SuppressWarnings("unused") private static final int MASK_SERVICE = 0x08; private static final int MASK_PUBLIC_TRANSPORT = 0x10; private static final int MASK_FOOT = 0x01 << 8; private static final int MASK_BICYCLE = 0x02 << 8; private static final int MASK_MOTORCYCLE = 0x0C << 8; private static final int MASK_SMALL_MOTORCYCLE = 0x08 << 8; private static final int MASK_MOTORCAR = 0x10 << 8; private static final int MASK_BUS = 0x20 << 8; /** * Create a new access information by parsing the given value. * * @param access * @return */ protected static AccessRestriction toAccessInformation(int access) { // Unknown -> Return default access information. if ((access & MASK_UNKNOWN) != 0) { return new AccessRestriction(); } Map allowedModes = new EnumMap<>(AccessMode.class); allowedModes.put(AccessMode.FOOT, (access & MASK_FOOT) != 0); allowedModes.put(AccessMode.BICYCLE, (access & MASK_BICYCLE) != 0); allowedModes.put(AccessMode.SMALL_MOTORCYCLE, (access & MASK_SMALL_MOTORCYCLE) != 0); allowedModes.put(AccessMode.MOTORCYCLE, (access & MASK_MOTORCYCLE) != 0); allowedModes.put(AccessMode.MOTORCAR, (access & MASK_MOTORCAR) != 0); allowedModes.put(AccessMode.BUS, (access & MASK_BUS) != 0); return new AccessRestriction((access & MASK_PRIVATE) != 0, (access & MASK_PUBLIC_TRANSPORT) != 0, allowedModes); } /** * Convert a character to its corresponding road type. * * @param ch Character to convert. * * @return Road type corresponding to ch. * * @see http://wiki.openstreetmap.org/wiki/Highway_tag_usage. */ 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': return RoadType.UNCLASSIFIED; case 'l': return RoadType.ROAD; case 'm': return RoadType.LIVING_STREET; case 'n': return RoadType.SERVICE; case 'o': return RoadType.ROUNDABOUT; case 'z': return RoadType.COASTLINE; } return RoadType.UNCLASSIFIED; } /** * Create a new BinaryGraphReader using the given DataInputStream. * * @param dis */ public BinaryGraphReaderInsa2018(DataInputStream dis) { super(MAGIC_NUMBER, VERSION, dis); } @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. int 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. int length = dis.readUnsignedShort(); 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 = new Arc(orig, dest, length, info, points); // And reverse arc if its a two-way road. if (!info.isOneWay()) { // Add without segments. ArrayList rPoints = new ArrayList(points); Collections.reverse(rPoints); new Arc(dest, orig, length, info, rPoints); } 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 GraphInformation(maxSpeed, maxLength)); } /** * Read the next road information from the stream. * * @throws IOException */ private RoadInformation readRoadInformation() throws IOException { char type = (char) dis.readUnsignedByte(); int x = dis.readUnsignedByte(); AccessRestriction access = new AccessRestriction(); if (getCurrentVersion() >= 6) { access = toAccessInformation(dis.readUnsignedShort()); } return new RoadInformation(toRoadType(type), access, (x & 0x80) > 0, (x & 0x7F) * 5, dis.readUTF()); } }