trNodes = new ArrayList<>(nodes.size());
+ for (Node node : nodes) {
+ trNodes.add(new Node(node.getId(), node.getPoint()));
+ }
+ for (Node node : nodes) {
+ final Node orig = trNodes.get(node.getId());
+ for (Arc arc : node.getSuccessors()) {
+ if (arc.getRoadInformation().isOneWay()) {
+ final Node dest = trNodes.get(arc.getDestination().getId());
+ dest.addSuccessor(
+ new ArcBackward(new ArcForward(orig, dest, arc.getLength(),
+ arc.getRoadInformation(), arc.getPoints())));
+ }
+ else if (arc instanceof ArcForward) {
+ final Node dest = trNodes.get(arc.getDestination().getId());
+ Arc newArc = new ArcForward(orig, dest, arc.getLength(),
+ arc.getRoadInformation(), arc.getPoints());
+ dest.addSuccessor(new ArcBackward(newArc));
+ orig.addSuccessor(newArc);
+ }
+ }
+ }
+ return new Graph("R/" + mapId, mapName, trNodes, graphStatistics);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[id=%s, name=%s, #nodes=%d]",
+ getClass().getCanonicalName(), getMapId(), getMapName(), size());
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/GraphStatistics.java b/be-graphes-model/src/main/java/org/insa/graphs/model/GraphStatistics.java
new file mode 100644
index 0000000..408457a
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/GraphStatistics.java
@@ -0,0 +1,194 @@
+package org.insa.graphs.model;
+
+/**
+ *
+ * Utility class that stores some statistics of graphs that are not easy to access.
+ *
+ *
+ * This class is used to provide constant ({@code O(1)}) access to information in graph
+ * that do not change, and that usually require linear complexity to compute.
+ *
+ */
+public class GraphStatistics {
+
+ /**
+ * Special value used to indicate that the graph has no maximum speed limit (some
+ * roads are not limited).
+ */
+ public static final int NO_MAXIMUM_SPEED = -1;
+
+ /**
+ * Class representing a bounding box for a graph (a rectangle that contains all
+ * nodes in the graph).
+ */
+ public static class BoundingBox {
+
+ private final Point topLeft, bottomRight;
+
+ /**
+ * Create a new BoundingBox represented by the given top-left and bottom-right
+ * points.
+ *
+ * @param topLeft Top left corner of the bounding box.
+ * @param bottomRight Bottom right corner of the bounding box.
+ */
+ public BoundingBox(Point topLeft, Point bottomRight) {
+ this.topLeft = topLeft;
+ this.bottomRight = bottomRight;
+ }
+
+ /**
+ * @return Bottom-right point of this bounding box.
+ */
+ public Point getBottomRightPoint() {
+ return bottomRight;
+ }
+
+ /**
+ * @return Top-left point of this bounding box.
+ */
+ public Point getTopLeftPoint() {
+ return topLeft;
+ }
+
+ /**
+ * Create a new bounding box by extending the current one according to the given
+ * value for each side.
+ *
+ * @param left Extra size to add to the left of the box.
+ * @param top Extra size to add to the top of the box.
+ * @param right Extra size to add to the right of the box.
+ * @param bottom Extra size to add to the bottom of the box.
+ * @return New bounding box corresponding to an extension of the current one.
+ */
+ public BoundingBox extend(float left, float top, float right, float bottom) {
+ return new BoundingBox(
+ new Point(this.topLeft.getLongitude() - left,
+ this.topLeft.getLatitude() + top),
+ new Point(this.bottomRight.getLongitude() + right,
+ this.bottomRight.getLatitude() - bottom));
+ }
+
+ /**
+ * Create a new bounding box by extending the current one according by the given
+ * value on each side.
+ *
+ * @param size Extra size to add to each side of this box.
+ * @return New bounding box corresponding to an extension of the current one.
+ */
+ public BoundingBox extend(float size) {
+ return this.extend(size, size, size, size);
+ }
+
+ /**
+ * @param point Point to check
+ * @return true if this box contains the given point.
+ */
+ public boolean contains(Point point) {
+ return this.bottomRight.getLatitude() <= point.getLatitude()
+ && this.topLeft.getLatitude() >= point.getLatitude()
+ && this.topLeft.getLongitude() <= point.getLongitude()
+ && this.bottomRight.getLongitude() >= point.getLongitude();
+ }
+
+ /**
+ * @param other Box to intersect.
+ * @return true if this box contains the given box.
+ */
+ public boolean contains(BoundingBox other) {
+ return this.contains(other.bottomRight) && this.contains(other.topLeft);
+ }
+
+ @Override
+ public String toString() {
+ return "BoundingBox(topLeft=" + this.topLeft + ", bottomRight="
+ + this.bottomRight + ")";
+ }
+
+ }
+
+ // Bounding box for this graph.
+ private final BoundingBox boundingBox;
+
+ // Number of roads
+ private final int nbRoadOneWay, nbRoadTwoWays;
+
+ // Maximum speed on this graph (in kmph).
+ private final int maximumSpeed;
+
+ // Maximum length of any arc on this graph.
+ private final float maximumLength;
+
+ /**
+ * Create a new GraphStatistics instance with the given value.
+ *
+ * @param boundingBox Bounding-box for the graph.
+ * @param nbRoadOneWay Number of one-way roads in the graph.
+ * @param nbRoadTwoWays Number of two-ways roads in the graph.
+ * @param maximumSpeed Maximum speed of any road of the graph. You can use
+ * {@link #NO_MAXIMUM_SPEED} to indicate that the graph has no maximum speed
+ * limit.
+ * @param maximumLength Maximum length of any arc of the graph.
+ */
+ public GraphStatistics(BoundingBox boundingBox, int nbRoadOneWay, int nbRoadTwoWays,
+ int maximumSpeed, float maximumLength) {
+ this.boundingBox = boundingBox;
+ this.nbRoadOneWay = nbRoadOneWay;
+ this.nbRoadTwoWays = nbRoadTwoWays;
+ this.maximumLength = maximumLength;
+ this.maximumSpeed = maximumSpeed;
+ }
+
+ /**
+ * @return The bounding box for this graph.
+ */
+ public BoundingBox getBoundingBox() {
+ return this.boundingBox;
+ }
+
+ /**
+ * @return Amount of one-way roads in this graph.
+ */
+ public int getOneWayRoadCount() {
+ return this.nbRoadOneWay;
+ }
+
+ /**
+ * @return Amount of two-ways roads in this graph.
+ */
+ public int getTwoWaysRoadCount() {
+ return this.nbRoadTwoWays;
+ }
+
+ /**
+ * @return Number of arcs in this graph.
+ * @see #getOneWayRoadCount()
+ * @see #getTwoWaysRoadCount()
+ */
+ public int getArcCount() {
+ return getOneWayRoadCount() + 2 * getTwoWaysRoadCount();
+ }
+
+ /**
+ * @return true if this graph has a maximum speed limit, false otherwise.
+ */
+ public boolean hasMaximumSpeed() {
+ return this.maximumLength != NO_MAXIMUM_SPEED;
+ }
+
+ /**
+ * @return Maximum speed of any arc in the graph, or {@link #NO_MAXIMUM_SPEED} if
+ * some roads have no speed limitation.
+ */
+ public int getMaximumSpeed() {
+ return this.maximumSpeed;
+ }
+
+ /**
+ * @return Maximum length of any arc in the graph.
+ */
+ public float getMaximumLength() {
+ return this.maximumLength;
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/Node.java b/be-graphes-model/src/main/java/org/insa/graphs/model/Node.java
new file mode 100644
index 0000000..1655eab
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/Node.java
@@ -0,0 +1,142 @@
+package org.insa.graphs.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ * Class representing a Node in a {@link Graph}.
+ *
+ *
+ * This class holds information regarding nodes in the graph together with the
+ * successors associated to the nodes.
+ *
+ *
+ * Nodes are comparable based on their ID.
+ *
+ */
+public final class Node {
+ /**
+ *
+ * Link the two given nodes with one or two arcs (depending on roadInformation),
+ * with the given attributes.
+ *
+ *
+ * If {@code roadInformation.isOneWay()} is {@code true}, only a forward arc is
+ * created (origin to destination) and added to origin. Otherwise, a corresponding
+ * backward arc is created and add to destination.
+ *
+ *
+ * @param origin Origin of the arc.
+ * @param destination Destination of the arc.
+ * @param length Length of the arc.
+ * @param roadInformation Information corresponding to the arc.
+ * @param points Points for the arc.
+ * @return The newly created forward arc (origin to destination).
+ */
+ public static Arc linkNodes(Node origin, Node destination, float length,
+ RoadInformation roadInformation, ArrayList points) {
+ Arc arc = null;
+ if (roadInformation.isOneWay()) {
+ arc = new ArcForward(origin, destination, length, roadInformation, points);
+ origin.addSuccessor(arc);
+ }
+ else {
+ Arc d2o;
+ if (origin.getId() < destination.getId()) {
+ arc = new ArcForward(origin, destination, length, roadInformation,
+ points);
+ d2o = new ArcBackward(arc);
+ }
+ else {
+ Collections.reverse(points);
+ d2o = new ArcForward(destination, origin, length, roadInformation,
+ points);
+ arc = new ArcBackward(d2o);
+ }
+ origin.addSuccessor(arc);
+ destination.addSuccessor(d2o);
+ }
+ return arc;
+ }
+
+ // ID of the node.
+ private final int id;
+
+ // Point of this graph.
+ private final Point point;
+
+ // Successors.
+ private final ArrayList successors;
+
+ /**
+ * Create a new Node with the given ID corresponding to the given Point with an
+ * empty list of successors.
+ *
+ * @param id ID of the node.
+ * @param point Position of the node.
+ */
+ public Node(int id, Point point) {
+ this.id = id;
+ this.point = point;
+ this.successors = new ArrayList();
+ }
+
+ /**
+ * Add a successor to this node.
+ *
+ * @param arc Arc to the successor.
+ */
+ protected void addSuccessor(Arc arc) {
+ successors.add(arc);
+ }
+
+ /**
+ * @return ID of this node.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * @return Number of successors of this node.
+ */
+ public int getNumberOfSuccessors() {
+ return this.successors.size();
+ }
+
+ /**
+ * @return true if this node has at least one successor.
+ */
+ public boolean hasSuccessors() {
+ return !this.successors.isEmpty();
+ }
+
+ /**
+ * @return List of successors of this node (unmodifiable list).
+ * @see Collections#unmodifiableList(List)
+ */
+ public List getSuccessors() {
+ return Collections.unmodifiableList(this.successors);
+ }
+
+ /**
+ * @return Location of this node.
+ */
+ public Point getPoint() {
+ return point;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (other instanceof Node) {
+ return getId() == ((Node) other).getId();
+ }
+ return false;
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/Path.java b/be-graphes-model/src/main/java/org/insa/graphs/model/Path.java
new file mode 100644
index 0000000..7580981
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/Path.java
@@ -0,0 +1,230 @@
+package org.insa.graphs.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ * Class representing a path between nodes in a graph.
+ *
+ *
+ * A path is represented as a list of {@link Arc} with an origin and not a list of
+ * {@link Node} due to the multi-graph nature (multiple arcs between two nodes) of the
+ * considered graphs.
+ *
+ */
+public class Path {
+
+ /**
+ * Create a new path that goes through the given list of nodes (in order), choosing
+ * the fastest route if multiple are available.
+ *
+ * @param graph Graph containing the nodes in the list.
+ * @param nodes List of nodes to build the path.
+ * @return A path that goes through the given list of nodes.
+ * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
+ * consecutive nodes in the list are not connected in the graph.
+ * @deprecated Need to be implemented.
+ */
+ public static Path createFastestPathFromNodes(Graph graph, List nodes)
+ throws IllegalArgumentException {
+ List arcs = new ArrayList();
+ // TODO:
+ return new Path(graph, arcs);
+ }
+
+ /**
+ * Create a new path that goes through the given list of nodes (in order), choosing
+ * the shortest route if multiple are available.
+ *
+ * @param graph Graph containing the nodes in the list.
+ * @param nodes List of nodes to build the path.
+ * @return A path that goes through the given list of nodes.
+ * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
+ * consecutive nodes in the list are not connected in the graph.
+ * @deprecated Need to be implemented.
+ */
+ public static Path createShortestPathFromNodes(Graph graph, List nodes)
+ throws IllegalArgumentException {
+ List arcs = new ArrayList();
+ // TODO:
+ return new Path(graph, arcs);
+ }
+
+ /**
+ * Concatenate the given paths.
+ *
+ * @param paths Array of paths to concatenate.
+ * @return Concatenated path.
+ * @throws IllegalArgumentException if the paths cannot be concatenated (IDs of map
+ * do not match, or the end of a path is not the beginning of the next).
+ */
+ public static Path concatenate(Path... paths) throws IllegalArgumentException {
+ if (paths.length == 0) {
+ throw new IllegalArgumentException(
+ "Cannot concatenate an empty list of paths.");
+ }
+ final String mapId = paths[0].getGraph().getMapId();
+ for (int i = 1; i < paths.length; ++i) {
+ if (!paths[i].getGraph().getMapId().equals(mapId)) {
+ throw new IllegalArgumentException(
+ "Cannot concatenate paths from different graphs.");
+ }
+ }
+ ArrayList arcs = new ArrayList<>();
+ for (Path path : paths) {
+ arcs.addAll(path.getArcs());
+ }
+ Path path = new Path(paths[0].getGraph(), arcs);
+ if (!path.isValid()) {
+ throw new IllegalArgumentException(
+ "Cannot concatenate paths that do not form a single path.");
+ }
+ return path;
+ }
+
+ // Graph containing this path.
+ private final Graph graph;
+
+ // Origin of the path
+ private final Node origin;
+
+ // List of arcs in this path.
+ private final List arcs;
+
+ /**
+ * Create an empty path corresponding to the given graph.
+ *
+ * @param graph Graph containing the path.
+ */
+ public Path(Graph graph) {
+ this.graph = graph;
+ this.origin = null;
+ this.arcs = new ArrayList<>();
+ }
+
+ /**
+ * Create a new path containing a single node.
+ *
+ * @param graph Graph containing the path.
+ * @param node Single node of the path.
+ */
+ public Path(Graph graph, Node node) {
+ this.graph = graph;
+ this.origin = node;
+ this.arcs = new ArrayList<>();
+ }
+
+ /**
+ * Create a new path with the given list of arcs.
+ *
+ * @param graph Graph containing the path.
+ * @param arcs Arcs to construct the path.
+ */
+ public Path(Graph graph, List arcs) {
+ this.graph = graph;
+ this.arcs = arcs;
+ this.origin = arcs.size() > 0 ? arcs.get(0).getOrigin() : null;
+ }
+
+ /**
+ * @return Graph containing the path.
+ */
+ public Graph getGraph() {
+ return graph;
+ }
+
+ /**
+ * @return First node of the path.
+ */
+ public Node getOrigin() {
+ return origin;
+ }
+
+ /**
+ * @return Last node of the path.
+ */
+ public Node getDestination() {
+ return arcs.get(arcs.size() - 1).getDestination();
+ }
+
+ /**
+ * @return List of arcs in the path.
+ */
+ public List getArcs() {
+ return Collections.unmodifiableList(arcs);
+ }
+
+ /**
+ * Check if this path is empty (it does not contain any node).
+ *
+ * @return true if this path is empty, false otherwise.
+ */
+ public boolean isEmpty() {
+ return this.origin == null;
+ }
+
+ /**
+ * Get the number of nodes in this path.
+ *
+ * @return Number of nodes in this path.
+ */
+ public int size() {
+ return isEmpty() ? 0 : 1 + this.arcs.size();
+ }
+
+ /**
+ * Check if this path is valid. A path is valid if any of the following is true:
+ *
+ * - it is empty;
+ * - it contains a single node (without arcs);
+ * - the first arc has for origin the origin of the path and, for two consecutive
+ * arcs, the destination of the first one is the origin of the second one.
+ *
+ *
+ * @return true if the path is valid, false otherwise.
+ * @deprecated Need to be implemented.
+ */
+ public boolean isValid() {
+ // TODO:
+ return false;
+ }
+
+ /**
+ * Compute the length of this path (in meters).
+ *
+ * @return Total length of the path (in meters).
+ * @deprecated Need to be implemented.
+ */
+ public float getLength() {
+ // TODO:
+ return 0;
+ }
+
+ /**
+ * Compute the time required to travel this path if moving at the given speed.
+ *
+ * @param speed Speed to compute the travel time.
+ * @return Time (in seconds) required to travel this path at the given speed (in
+ * kilometers-per-hour).
+ * @deprecated Need to be implemented.
+ */
+ public double getTravelTime(double speed) {
+ // TODO:
+ return 0;
+ }
+
+ /**
+ * Compute the time to travel this path if moving at the maximum allowed speed on
+ * every arc.
+ *
+ * @return Minimum travel time to travel this path (in seconds).
+ * @deprecated Need to be implemented.
+ */
+ public double getMinimumTravelTime() {
+ // TODO:
+ return 0;
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/Point.java b/be-graphes-model/src/main/java/org/insa/graphs/model/Point.java
new file mode 100644
index 0000000..fb438df
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/Point.java
@@ -0,0 +1,82 @@
+package org.insa.graphs.model;
+
+/**
+ * Class representing a point (position) on Earth.
+ */
+public final class Point {
+
+ /**
+ * Approximated Earth radius (in meters).
+ */
+ public static final double EARTH_RADIUS = 6378137.0;
+
+ /**
+ * Compute the distance in meters between the two given points.
+ *
+ * @param p1 First point.
+ * @param p2 second point.
+ * @return Distance between the two given points (in meters).
+ */
+ public static double distance(Point p1, Point p2) {
+ double sinLat = Math.sin(Math.toRadians(p1.getLatitude()))
+ * Math.sin(Math.toRadians(p2.getLatitude()));
+ double cosLat = Math.cos(Math.toRadians(p1.getLatitude()))
+ * Math.cos(Math.toRadians(p2.getLatitude()));
+ double cosLong =
+ Math.cos(Math.toRadians(p2.getLongitude() - p1.getLongitude()));
+
+ double koef = sinLat + cosLat * cosLong;
+
+ if (koef >= 1.0) {
+ koef = 1.0;
+ }
+ if (koef <= -1.0) {
+ koef = -1.0;
+ }
+
+ return (EARTH_RADIUS * Math.acos(koef));
+ }
+
+ // Longitude and latitude of the point.
+ private final float longitude, latitude;
+
+ /**
+ * Create a new point corresponding to the given (longitude, latitude) position.
+ *
+ * @param longitude Longitude of the point (in degrees).
+ * @param latitude Latitude of the point (in degrees).
+ */
+ public Point(float longitude, float latitude) {
+ this.longitude = longitude;
+ this.latitude = latitude;
+ }
+
+ /**
+ * @return Longitude of this point (in degrees).
+ */
+ public float getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * @return Latitude of this point (in degrees).
+ */
+ public float getLatitude() {
+ return latitude;
+ }
+
+ /**
+ * Compute the distance from this point to the given point
+ *
+ * @param target Target point to compute distance to.
+ * @return Distance between this point and the target point, in meters.
+ */
+ public double distanceTo(Point target) {
+ return distance(this, target);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Point(%f, %f)", getLongitude(), getLatitude());
+ }
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/RoadInformation.java b/be-graphes-model/src/main/java/org/insa/graphs/model/RoadInformation.java
new file mode 100644
index 0000000..e0472eb
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/RoadInformation.java
@@ -0,0 +1,106 @@
+package org.insa.graphs.model;
+
+/**
+ *
+ * Class containing information for road that may be shared by multiple arcs.
+ *
+ *
+ * Sharing information between arcs reduces memory footprints of the program (a long
+ * road is often split into multiple arcs at each intersection).
+ *
+ */
+public class RoadInformation {
+
+ /**
+ * Enumeration for road types.
+ *
+ * @see OpenStreetMap
+ * reference for road types.
+ */
+ public enum RoadType {
+ MOTORWAY, TRUNK, PRIMARY, SECONDARY, MOTORWAY_LINK, TRUNK_LINK, PRIMARY_LINK, SECONDARY_LINK, TERTIARY, TRACK, RESIDENTIAL, UNCLASSIFIED, LIVING_STREET, SERVICE, ROUNDABOUT, PEDESTRIAN, CYCLEWAY, COASTLINE
+ }
+
+ // Type of the road (see above).
+ private final RoadType type;
+
+ // Access information
+ private final AccessRestrictions access;
+
+ // One way road?
+ private final boolean oneway;
+
+ // Max speed in kilometers per hour.
+ private final int maxSpeed;
+
+ // Name of the road.
+ private final String name;
+
+ /**
+ * Create a new RoadInformation instance containing the given parameters.
+ *
+ * @param roadType Type of the road (see {@link RoadType}).
+ * @param access Access restrictions for the road (see {@link AccessRestrictions}).
+ * @param isOneWay true if this road is a one way road, false otherwise.
+ * @param maxSpeed Maximum speed for the road (in kilometers-per-hour).
+ * @param name Name of the road.
+ */
+ public RoadInformation(RoadType roadType, AccessRestrictions access,
+ boolean isOneWay, int maxSpeed, String name) {
+ this.type = roadType;
+ this.access = access;
+ this.oneway = isOneWay;
+ this.maxSpeed = maxSpeed;
+ this.name = name;
+ }
+
+ /**
+ * @return Access restrictions for this road.
+ */
+ public AccessRestrictions getAccessRestrictions() {
+ return this.access;
+ }
+
+ /**
+ * @return Type of the road.
+ */
+ public RoadType getType() {
+ return type;
+ }
+
+ /**
+ * @return true if the road is a one-way road.
+ */
+ public boolean isOneWay() {
+ return oneway;
+ }
+
+ /**
+ * @return Maximum speed for this road (in kilometers-per-hour).
+ */
+ public int getMaximumSpeed() {
+ return maxSpeed;
+ }
+
+ /**
+ * @return Name of the road.
+ */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ String typeAsString = "road";
+ if (getType() == RoadType.COASTLINE) {
+ typeAsString = "coast";
+ }
+ if (getType() == RoadType.MOTORWAY) {
+ typeAsString = "highway";
+ }
+ return typeAsString + " : " + getName() + " " + (isOneWay() ? " (oneway) " : "")
+ + maxSpeed + " km/h (max.)";
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadFormatException.java b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadFormatException.java
new file mode 100644
index 0000000..23e5077
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadFormatException.java
@@ -0,0 +1,32 @@
+package org.insa.graphs.model.io;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when a format-error is detected when reading a graph (e.g.,
+ * non-matching check bytes).
+ */
+public class BadFormatException extends IOException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1270945933549613579L;
+
+ /**
+ *
+ */
+ public BadFormatException() {
+ super();
+ }
+
+ /**
+ * Create a new format exception with the given message.
+ *
+ * @param message Message for the exception.
+ */
+ public BadFormatException(String message) {
+ super(message);
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadMagicNumberException.java b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadMagicNumberException.java
new file mode 100644
index 0000000..5bb4799
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadMagicNumberException.java
@@ -0,0 +1,44 @@
+package org.insa.graphs.model.io;
+
+/**
+ * Exception thrown when there is a mismatch between expected and actual magic number.
+ */
+public class BadMagicNumberException extends BadFormatException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -2176603967548838864L;
+
+ // Actual and expected magic numbers.
+ private int actualNumber, expectedNumber;
+
+ /**
+ * Create a new BadMagicNumberException with the given expected and actual magic
+ * number.
+ *
+ * @param actualNumber Actual magic number (read from a file).
+ * @param expectedNumber Expected magic number.
+ */
+ public BadMagicNumberException(int actualNumber, int expectedNumber) {
+ super(String.format("Magic number mismatch, expected %#X, got %#X.",
+ expectedNumber, actualNumber));
+ this.actualNumber = actualNumber;
+ this.expectedNumber = expectedNumber;
+ }
+
+ /**
+ * @return The actual magic number.
+ */
+ public int getActualMagicNumber() {
+ return actualNumber;
+ }
+
+ /**
+ * @return The expected magic number.
+ */
+ public int getExpectedMagicNumber() {
+ return expectedNumber;
+ }
+
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadVersionException.java b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadVersionException.java
new file mode 100644
index 0000000..808d6ab
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BadVersionException.java
@@ -0,0 +1,39 @@
+package org.insa.graphs.model.io;
+
+/**
+ * Exception thrown when the version of the file is not at least the expected one.
+ */
+public class BadVersionException extends BadFormatException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7776317018302386042L;
+
+ // Actual and expected version..
+ private int actualVersion, expectedVersion;
+
+ /**
+ * @param actualVersion Actual version of the file.
+ * @param expectedVersion Expected version of the file.
+ */
+ public BadVersionException(int actualVersion, int expectedVersion) {
+ super();
+ this.actualVersion = actualVersion;
+ this.expectedVersion = expectedVersion;
+ }
+
+ /**
+ * @return Actual version of the file.
+ */
+ public int getActualVersion() {
+ return actualVersion;
+ }
+
+ /**
+ * @return Expected (minimal) version of the file.
+ */
+ public int getExpectedVersion() {
+ return expectedVersion;
+ }
+}
diff --git a/be-graphes-model/src/main/java/org/insa/graphs/model/io/BinaryGraphReader.java b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BinaryGraphReader.java
new file mode 100644
index 0000000..9e52574
--- /dev/null
+++ b/be-graphes-model/src/main/java/org/insa/graphs/model/io/BinaryGraphReader.java
@@ -0,0 +1,325 @@
+package org.insa.graphs.model.io;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+
+import org.insa.graphs.model.AccessRestrictions;
+import org.insa.graphs.model.AccessRestrictions.AccessMode;
+import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
+import org.insa.graphs.model.Arc;
+import org.insa.graphs.model.Graph;
+import org.insa.graphs.model.GraphStatistics;
+import org.insa.graphs.model.GraphStatistics.BoundingBox;
+import org.insa.graphs.model.Node;
+import org.insa.graphs.model.Point;
+import org.insa.graphs.model.RoadInformation;
+import org.insa.graphs.model.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 final 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...
+ final 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.
+ final String mapId;
+ final String mapName;
+
+ if (getCurrentVersion() < 6) {
+ mapId = "0x" + Integer.toHexString(dis.readInt());
+ mapName = ""; // No map name for older versions.
+ }
+ else {
+ mapId = readFixedLengthString(MAP_ID_FIELD_LENGTH, "UTF-8");
+ mapName = dis.readUTF();
+ }
+
+ observers.forEach((observer) -> observer.notifyStartReading(mapId));
+
+ // Number of descriptors and nodes.
+ final int nbDesc = dis.readInt();
+ final int nbNodes = dis.readInt();
+
+ // Number of successors for each nodes.
+ final int[] nbSuccessors = new int[nbNodes];
+ int nbTotalSuccessors = 0;
+
+ // Construct an array list with initial capacity of nbNodes.
+ final ArrayList nodes = new ArrayList