Switch to Maven project.

This commit is contained in:
Mikaël Capelle
2020-02-23 16:30:06 +01:00
parent fd503d148e
commit 5bb454a3b2
139 changed files with 2487 additions and 2198 deletions

14
be-graphes-model/pom.xml Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.insa.graphs</groupId>
<artifactId>be-graphes-all</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>be-graphes-model</artifactId>
<name>be-graphes-model</name>
</project>

View File

@@ -0,0 +1,236 @@
package org.insa.graphs.model;
import java.util.EnumMap;
import java.util.EnumSet;
/**
* <p>
* Class containing access restrictions for roads/arcs.
* </p>
*
* <p>
* This class maps transport modes to their restriction and provide interface
* based on EnumSet to query restrictions.
* </p>
*
* <p>
* To each transport is associated at most one restriction per road (no
* restriction corresponds to {@link AccessRestriction#UNKNOWN} but a road can
* have different restrictions for different modes.
* </p>
*
*/
public class AccessRestrictions {
/**
* Enumeration representing the available transport modes.
*
* @see <a href=
* "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
* reference for access modes.</a>
*/
public enum AccessMode {
/**
* Access mode corresponding to pedestrians.
*/
FOOT,
/**
* Access mode corresponding to bicycles (non-motorized).
*/
BICYCLE,
/**
* Access mode corresponding to small motorcycles (limited speed).
*/
SMALL_MOTORCYCLE,
/**
* Access mode corresponding to agricultural vehicles.
*/
AGRICULTURAL,
/**
* Access mode corresponding to motorcycles.
*/
MOTORCYCLE,
/**
* Access mode corresponding to motorcars.
*/
MOTORCAR,
/**
* Access mode corresponding to heavy transportation vehicles.
*/
HEAVY_GOODS,
/**
* Access mode corresponding to public transport vehicles.
*/
PUBLIC_TRANSPORT;
/**
* {@code EnumSet} containing all possible transport modes.
*
*
*/
public static final EnumSet<AccessMode> ALL = EnumSet.allOf(AccessMode.class);
/**
* {@code EnumSet} containing all vehicle transport modes.
*
*/
public static final EnumSet<AccessMode> VEHICLE = EnumSet.range(AccessMode.BICYCLE,
AccessMode.PUBLIC_TRANSPORT);
/**
* {@code EnumSet} containing all motorized vehicle transport modes.
*
*/
public static final EnumSet<AccessMode> MOTOR_VEHICLE = EnumSet
.range(AccessMode.SMALL_MOTORCYCLE, AccessMode.PUBLIC_TRANSPORT);
}
/**
* Possible restrictions for the roads/arcs.
*
* @see <a href=
* "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
* reference for access restrictions.</a>
*/
public enum AccessRestriction {
/**
*
*/
ALLOWED,
/**
*
*/
FORBIDDEN,
/**
*
*/
PRIVATE,
/**
*
*/
DESTINATION,
/**
*
*/
DELIVERY,
/**
*
*/
CUSTOMERS,
/**
*
*/
FORESTRY,
/**
*
*/
UNKNOWN;
/**
* {@code EnumSet} corresponding to restrictions that are not totally private.
*
*/
public static final EnumSet<AccessRestriction> ALLOWED_FOR_SOMETHING = EnumSet.of(
AccessRestriction.ALLOWED, AccessRestriction.DESTINATION,
AccessRestriction.DESTINATION, AccessRestriction.DELIVERY,
AccessRestriction.CUSTOMERS, AccessRestriction.FORESTRY);
}
// Map mode -> restriction
private final EnumMap<AccessMode, AccessRestriction> restrictions;
/**
* Create new AccessRestrictions instances with unknown restrictions.
*/
public AccessRestrictions() {
this.restrictions = new EnumMap<>(AccessMode.class);
for (AccessMode mode: AccessMode.values()) {
this.restrictions.put(mode, AccessRestriction.UNKNOWN);
}
}
/**
* Create a new AccessRestrictions instances with the given restrictions.
*
* @param restrictions Map of restrictions for this instance of
* AccessRestrictions.
*/
public AccessRestrictions(EnumMap<AccessMode, AccessRestriction> restrictions) {
this.restrictions = restrictions;
}
/**
* Retrieve the restriction corresponding to the given mode.
*
* @param mode Mode for which the restriction should be retrieved.
*
* @return Restriction for the given mode.
*/
public AccessRestriction getRestrictionFor(AccessMode mode) {
return restrictions.getOrDefault(mode, AccessRestriction.UNKNOWN);
}
/**
* Check if the restriction associated with the given mode is one of the given
* restrictions.
*
* @param mode Mode for which to check the restrictions.
* @param restrictions List of queried restrictions for the mode.
*
* @return {@code true} if the restriction of the given mode is one of the given
* restrictions.
*/
public boolean isAllowedForAny(AccessMode mode, EnumSet<AccessRestriction> restrictions) {
return restrictions.contains(getRestrictionFor(mode));
}
/**
* Check if the restriction for the given mode corresponds to the given
* restriction.
*
* @param mode Mode for which the restriction should be checked.
* @param restriction Restriction to check against.
*
* @return {@code true} if the restriction of the given mode corresponds to the
* given restriction.
*/
public boolean isAllowedFor(AccessMode mode, AccessRestriction restriction) {
return getRestrictionFor(mode).equals(restriction);
}
/**
* Check if the restriction associated to each given mode is one of the
* restrictions. The restriction may not be the same for all modes.
*
* @param modes Modes for which restrictions should be checked.
* @param restrictions Set of wanted restrictions for the modes.
*
* @return {@code true} if all the given modes are allowed for any of the given
* restrictions.
*/
public boolean areAllAllowedForAny(EnumSet<AccessMode> modes,
EnumSet<AccessRestriction> restrictions) {
boolean allowed = true;
for (AccessMode mode: modes) {
allowed = allowed && isAllowedForAny(mode, restrictions);
}
return allowed;
}
}

View File

@@ -0,0 +1,70 @@
package org.insa.graphs.model;
import java.util.List;
/**
* <p>
* Interface representing an arc in the graph. {@code Arc} is an interface and
* not a class to allow us to represent two-ways roads in a memory efficient
* manner (without having to duplicate attributes).
* </p>
*
* <p>
* Arc should never be created manually but always using the
* {@link Node#linkNodes(Node, Node, float, RoadInformation, java.util.ArrayList)}
* method to ensure proper instantiation of the {@link ArcForward} and
* {@link ArcBackward} classes.
* </p>
*
*/
public abstract class Arc {
/**
* @return Origin node of this arc.
*/
public abstract Node getOrigin();
/**
* @return Destination node of this arc.
*/
public abstract Node getDestination();
/**
* @return Length of this arc, in meters.
*/
public abstract float getLength();
/**
* Compute the time required to travel this arc if moving at the given speed.
*
* @param speed Speed to compute the travel time.
*
* @return Time (in seconds) required to travel this arc at the given speed (in
* kilometers-per-hour).
*/
public double getTravelTime(double speed) {
return getLength() * 3600.0 / (speed * 1000.0);
}
/**
* Compute and return the minimum time required to travel this arc, or the time
* required to travel this arc at the maximum speed allowed.
*
* @return Minimum time required to travel this arc, in seconds.
*
* @see Arc#getTravelTime(double)
*/
public double getMinimumTravelTime() {
return getTravelTime(getRoadInformation().getMaximumSpeed());
}
/**
* @return Road information for this arc.
*/
public abstract RoadInformation getRoadInformation();
/**
* @return Points representing segments of this arc.
*/
public abstract List<Point> getPoints();
}

View File

@@ -0,0 +1,55 @@
package org.insa.graphs.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Implementation of Arc that represents a "backward" arc in a graph, i.e., an
* arc that is the reverse of another one. This arc only holds a reference to
* the original arc.
*
*/
class ArcBackward extends Arc {
// Original arc
private final Arc originalArc;
/**
* Create a new backward arc which corresponds to the reverse arc of the given
* arc.
*
* @param originalArc Original forwarc arc corresponding to this backward arc.
*/
protected ArcBackward(Arc originalArc) {
this.originalArc = originalArc;
}
@Override
public Node getOrigin() {
return this.originalArc.getDestination();
}
@Override
public Node getDestination() {
return this.originalArc.getOrigin();
}
@Override
public float getLength() {
return this.originalArc.getLength();
}
@Override
public RoadInformation getRoadInformation() {
return this.originalArc.getRoadInformation();
}
@Override
public List<Point> getPoints() {
List<Point> pts = new ArrayList<>(this.originalArc.getPoints());
Collections.reverse(pts);
return pts;
}
}

View File

@@ -0,0 +1,68 @@
package org.insa.graphs.model;
import java.util.Collections;
import java.util.List;
/**
* Implementation of Arc that represents a "forward" arc in a graph, this is the
* arc implementation that stores data relative to the arc.
*
*/
class ArcForward extends Arc {
// Destination node.
private final Node origin, destination;
// Length of the road (in meters).
private final float length;
// Road information.
private final RoadInformation info;
// Segments.
private final List<Point> points;
/**
* Create a new ArcForward with the given attributes.
*
* @param origin Origin of this arc.
* @param dest Destination of this arc.
* @param length Length of this arc (in meters).
* @param roadInformation Road information for this arc.
* @param points Points representing this arc.
*/
protected ArcForward(Node origin, Node dest, float length, RoadInformation roadInformation,
List<Point> points) {
this.origin = origin;
this.destination = dest;
this.length = length;
this.info = roadInformation;
this.points = points;
}
@Override
public Node getOrigin() {
return origin;
}
@Override
public Node getDestination() {
return destination;
}
@Override
public float getLength() {
return length;
}
@Override
public RoadInformation getRoadInformation() {
return info;
}
@Override
public List<Point> getPoints() {
return Collections.unmodifiableList(points);
}
}

View File

@@ -0,0 +1,131 @@
package org.insa.graphs.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>
* Main graph class.
* </p>
*
* <p>
* This class acts as a object-oriented <b>adjacency list</b> for a graph, i.e.,
* it holds a list of nodes and each node holds a list of its successors.
* </p>
*
*/
public final class Graph {
// Map identifier.
private final String mapId;
// Map name
private final String mapName;
// Nodes of the graph.
private final List<Node> nodes;
// Graph information of this graph.
private final GraphStatistics graphStatistics;
/**
* Create a new graph with the given ID, name, nodes and information.
*
* @param mapId ID of the map corresponding to this graph.
* @param mapName Name of the map corresponding to this graph.
* @param nodes List of nodes for this graph.
* @param graphStatistics Information for this graph.
*/
public Graph(String mapId, String mapName, List<Node> nodes, GraphStatistics graphStatistics) {
this.mapId = mapId;
this.mapName = mapName;
this.nodes = Collections.unmodifiableList(nodes);
this.graphStatistics = graphStatistics;
}
/**
* @return The GraphStatistics instance associated with this graph.
*/
public GraphStatistics getGraphInformation() {
return this.graphStatistics;
}
/**
* Fetch the node with the given ID.
*
* Complexity: O(1).
*
* @param id ID of the node to fetch.
*
* @return Node with the given ID.
*/
public Node get(int id) {
return this.nodes.get(id);
}
/**
* @return Number of nodes in this graph.
*/
public int size() {
return this.nodes.size();
}
/**
* @return List of nodes in this graph (unmodifiable).
*
* @see Collections#unmodifiableList(List)
*/
public List<Node> getNodes() {
return this.nodes;
}
/**
* @return ID of the map associated with this graph.
*/
public String getMapId() {
return mapId;
}
/**
* @return Name of the map associated with this graph.
*/
public String getMapName() {
return mapName;
}
/**
* @return Transpose graph of this graph.
*/
public Graph transpose() {
ArrayList<Node> trNodes = new ArrayList<>(nodes.size());
for (Node node: nodes) {
trNodes.add(new Node(node.getId(), node.getPoint()));
}
for (Node node: nodes) {
Node orig = trNodes.get(node.getId());
for (Arc arc: node.getSuccessors()) {
if (arc.getRoadInformation().isOneWay()) {
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) {
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());
}
}

View File

@@ -0,0 +1,204 @@
package org.insa.graphs.model;
/**
* <p>
* Utility class that stores some statistics of graphs that are not easy to
* access.
* </p>
*
* <p>
* 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.
* </p>
*
*/
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;
}
}

View File

@@ -0,0 +1,159 @@
package org.insa.graphs.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>
* Class representing a Node in a {@link Graph}.
* </p>
*
* <p>
* This class holds information regarding nodes in the graph together with the
* successors associated to the nodes.
* </p>
*
* <p>
* Nodes are comparable based on their ID.
* </p>
*
*/
public final class Node implements Comparable<Node> {
/**
* <p>
* Link the two given nodes with one or two arcs (depending on roadInformation),
* with the given attributes.
* </p>
*
* <p>
* 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.
* </p>
*
* @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<Point> 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<Arc> 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<Arc>();
}
/**
* 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<Arc> 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;
}
/**
* Compare the ID of this node with the ID of the given node.
*
* @param other Node to compare this node with.
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Node other) {
return Integer.compare(getId(), other.getId());
}
}

View File

@@ -0,0 +1,248 @@
package org.insa.graphs.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>
* Class representing a path between nodes in a graph.
* </p>
*
* <p>
* 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.
* </p>
*
*/
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<Node> nodes)
throws IllegalArgumentException {
List<Arc> arcs = new ArrayList<Arc>();
// 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<Node> nodes)
throws IllegalArgumentException {
List<Arc> arcs = new ArrayList<Arc>();
// 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<Arc> 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<Arc> 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<Arc> 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<Arc> 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 <b>nodes</b> 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:
* <ul>
* <li>it is empty;</li>
* <li>it contains a single node (without arcs);</li>
* <li>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.</li>
* </ul>
*
* @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;
}
}

View File

@@ -0,0 +1,74 @@
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()));
return EARTH_RADIUS * Math.acos(sinLat + cosLat * cosLong);
}
// 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());
}
}

View File

@@ -0,0 +1,126 @@
package org.insa.graphs.model;
/**
* <p>
* Class containing information for road that may be shared by multiple arcs.
* </p>
*
* <p>
* Sharing information between arcs reduces memory footprints of the program (a
* long road is often split into multiple arcs at each intersection).
* </p>
*
*/
public class RoadInformation {
/**
* Enumeration for road types.
*
* @see <a href=
* "https://wiki.openstreetmap.org/wiki/Key:highway#Values">OpenStreetMap
* reference for road types.</a>
*/
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.)";
}
}

View File

@@ -0,0 +1,33 @@
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 = 1L;
/**
*
*/
public BadFormatException() {
super();
}
/**
* Create a new format exception with the given message.
*
* @param message Message for the exception.
*/
public BadFormatException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,46 @@
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;
}
}

View File

@@ -0,0 +1,42 @@
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;
}
}

View File

@@ -0,0 +1,323 @@
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.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.GraphStatistics;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Point;
import org.insa.graphs.model.RoadInformation;
import org.insa.graphs.model.AccessRestrictions.AccessMode;
import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
import org.insa.graphs.model.GraphStatistics.BoundingBox;
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 List<GraphReaderObserver> 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<AccessMode, AccessRestriction> 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<Node> nodes = new ArrayList<Node>(nbNodes);
// Read nodes.
float minLongitude = Float.POSITIVE_INFINITY, minLatitude = Float.POSITIVE_INFINITY,
maxLongitude = Float.NEGATIVE_INFINITY, maxLatitude = Float.NEGATIVE_INFINITY;
observers.forEach((observer) -> observer.notifyStartReadingNodes(nbNodes));
for (int node = 0; node < nbNodes; ++node) {
// Read longitude / latitude.
float longitude = ((float) dis.readInt()) / 1E6f;
float latitude = ((float) dis.readInt()) / 1E6f;
// Update minimum / maximum.
minLongitude = Math.min(longitude, minLongitude);
minLatitude = Math.min(latitude, minLatitude);
maxLongitude = Math.max(longitude, maxLongitude);
maxLatitude = Math.max(latitude, maxLatitude);
// Update information.
nbSuccessors[node] = dis.readUnsignedByte();
nbTotalSuccessors += nbSuccessors[node];
// Create 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...
int nbOneWayRoad = 0;
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);
length = Math.max(length, (float) Point.distance(nodes.get(node).getPoint(),
nodes.get(destNode).getPoint()));
// Number of segments.
int nbSegments = dis.readUnsignedShort();
// Chain of points corresponding to the segments.
ArrayList<Point> points = new ArrayList<Point>(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);
if (info.isOneWay()) {
nbOneWayRoad++;
}
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(
new BoundingBox(new Point(minLongitude, maxLatitude),
new Point(maxLongitude, minLatitude)),
nbOneWayRoad, nbTotalSuccessors - nbOneWayRoad, 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());
}
}

View File

@@ -0,0 +1,76 @@
package org.insa.graphs.model.io;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
/**
* Implementation of {@link PathReader} to read paths in binary format.
*
*/
public class BinaryPathReader extends BinaryReader implements PathReader {
// Map version and magic number targeted for this reader.
protected static final int VERSION = 1;
protected static final int MAGIC_NUMBER = 0xdecafe;
/**
* Create a new BinaryPathReader that read from the given input stream.
*
* @param dis Input stream to read from.
*/
public BinaryPathReader(DataInputStream dis) {
super(MAGIC_NUMBER, VERSION, dis);
}
@Override
public Path readPath(Graph graph) throws IOException {
// Read and check magic number and version.
checkMagicNumberOrThrow(dis.readInt());
checkVersionOrThrow(dis.readInt());
// Read map ID and check against graph.
String mapId = readFixedLengthString(BinaryGraphReader.MAP_ID_FIELD_LENGTH, "UTF-8");
if (!mapId.equals(graph.getMapId())) {
throw new MapMismatchException(mapId, graph.getMapId());
}
// Number of nodes in the path (without first and last).
int nbNodes = dis.readInt();
// Skip (duplicate) first and last node
readNode(graph);
readNode(graph);
// Read intermediate nodes:
ArrayList<Node> nodes = new ArrayList<Node>();
for (int i = 0; i < nbNodes; ++i) {
nodes.add(readNode(graph));
}
this.dis.close();
return Path.createFastestPathFromNodes(graph, nodes);
}
/**
* Read a node from the input stream and returns it.
*
* @param graph Graph containing the nodes.
*
* @return The next node in the input stream.
*
* @throws IOException if something occurs while reading the note.
* @throws IndexOutOfBoundsException if the node is not in the graph.
*/
protected Node readNode(Graph graph) throws IOException {
return graph.get(dis.readInt());
}
}

View File

@@ -0,0 +1,54 @@
package org.insa.graphs.model.io;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Path;
/**
* Implementation of {@link PathWriter} to write paths in binary format.
*
*/
public class BinaryPathWriter extends BinaryWriter implements PathWriter {
/**
* Create a new BinaryPathWriter that writes to the given output stream.
*
* @param dos Output stream to write to.
*/
public BinaryPathWriter(DataOutputStream dos) {
super(dos);
}
@Override
public void writePath(Path path) throws IOException {
// Write magic number and version.
dos.writeInt(BinaryPathReader.MAGIC_NUMBER);
dos.writeInt(BinaryPathReader.VERSION);
// Write map id.
byte[] bytes = Arrays.copyOf(path.getGraph().getMapId().getBytes("UTF-8"),
BinaryGraphReader.MAP_ID_FIELD_LENGTH);
dos.write(bytes);
// Write number of arcs
dos.writeInt(path.getArcs().size() + 1);
// Write origin / destination.
dos.writeInt(path.getOrigin().getId());
dos.writeInt(path.getDestination().getId());
// Write nodes.
dos.writeInt(path.getOrigin().getId());
for (Arc arc: path.getArcs()) {
dos.writeInt(arc.getDestination().getId());
}
dos.flush();
dos.close();
}
}

View File

@@ -0,0 +1,121 @@
package org.insa.graphs.model.io;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.IOException;
/**
* Base class for writing binary file.
*
*/
public abstract class BinaryReader implements AutoCloseable, Closeable {
// Map version and magic number targeted for this reader.
private final int minVersion;
private int curVersion;
private final int magicNumber;
// InputStream
protected final DataInputStream dis;
/**
* Create a new BinaryReader that reads from the given stream and that expected
* the given magic number and at least the given minimum version.
*
* @param magicNumber Magic number of files to be read.
* @param minVersion Minimum version of files to be read.
* @param dis Input stream from which to read.
*/
protected BinaryReader(int magicNumber, int minVersion, DataInputStream dis) {
this.magicNumber = magicNumber;
this.minVersion = minVersion;
this.dis = dis;
}
@Override
public void close() throws IOException {
this.dis.close();
}
/**
* Check if the given version is greater than the minimum version, and update
* the current version if it is.
*
* @param version Version to check.
*
* @throws BadVersionException if the given version is not greater than the
* minimum version.
*/
protected void checkVersionOrThrow(int version) throws BadVersionException {
if (version < this.minVersion) {
throw new BadVersionException(version, this.minVersion);
}
this.curVersion = version;
}
/**
* @return The current version.
*/
protected int getCurrentVersion() {
return this.curVersion;
}
/**
* Check if the given number matches the expected magic number.
*
* @param magicNumber The magic number to check.
*
* @throws BadMagicNumberException If the two magic numbers are not equal.
*/
protected void checkMagicNumberOrThrow(int magicNumber) throws BadMagicNumberException {
if (this.magicNumber != magicNumber) {
throw new BadMagicNumberException(magicNumber, this.magicNumber);
}
}
/**
* Check if the next byte in the input stream correspond to the given byte.
*
* This function consumes the next byte in the input stream.
*
* @param b Byte to check.
*
* @throws IOException if an error occurs while reading the byte.
* @throws BadFormatException if the byte read is not the expected one.
*/
protected void checkByteOrThrow(int b) throws IOException {
if (dis.readUnsignedByte() != b) {
throw new BadFormatException();
}
}
/**
* Read a byte array of fixed length from the input and convert it to a string
* using the given charset, removing any trailing '\0'.
*
* @param length Number of bytes to read.
* @param charset Charset to use to convert the bytes into a string.
*
* @return The convert string.
*
* @throws IOException if an error occurs while reading or converting.
*/
protected String readFixedLengthString(int length, String charset) throws IOException {
byte[] bytes = new byte[length];
this.dis.read(bytes);
return new String(bytes, "UTF-8").trim();
}
/**
* Read 24 bits in BigEndian order from the stream and return the corresponding
* integer value.
*
* @return Integer value read from the next 24 bits of the stream.
*
* @throws IOException if an error occurs while reading from the stream.
*/
protected int read24bits() throws IOException {
int x = dis.readUnsignedShort();
return (x << 8) | dis.readUnsignedByte();
}
}

View File

@@ -0,0 +1,42 @@
package org.insa.graphs.model.io;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Base class for writing binary file.
*
*/
public abstract class BinaryWriter implements AutoCloseable, Closeable {
// Output stream.
protected final DataOutputStream dos;
/**
* Create a new BinaryWriter that writes to the given output stream.
*
* @param dos Stream to write to.
*/
protected BinaryWriter(DataOutputStream dos) {
this.dos = dos;
}
@Override
public void close() throws IOException {
this.dos.close();
}
/**
* Write a 24-bits integer in BigEndian order to the output stream.
*
* @param value Value to write.
*
* @throws IOException if an error occurs while writing to the stream.
*/
protected void write24bits(int value) throws IOException {
dos.writeShort(value >> 8);
dos.writeByte(value & 0xff);
}
}

View File

@@ -0,0 +1,39 @@
package org.insa.graphs.model.io;
import java.io.Closeable;
import java.io.IOException;
import org.insa.graphs.model.Graph;
/**
* Base interface for classes that can read graph.
*
*/
public interface GraphReader extends AutoCloseable, Closeable {
/**
* Add a new observer to this graph reader.
*
* @param observer Observer to add.
*/
public void addObserver(GraphReaderObserver observer);
/**
* Read a graph an returns it.
*
* @return The graph read.
*
* @throws IOException if an exception occurs while reading the graph.
*
*/
public Graph read() throws IOException;
/**
* Close this graph reader.
*
* @throws IOException if an exception occurs while closing the reader.
*
*/
public void close() throws IOException;
}

View File

@@ -0,0 +1,69 @@
package org.insa.graphs.model.io;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.RoadInformation;
/**
* Base interface that should be implemented by classes that want to observe the
* reading of a graph by a {@link GraphReader}.
*
*/
public interface GraphReaderObserver {
/**
* Notify observer about information on the graph, this method is always the
* first called
*
* @param mapId ID of the graph.
*/
public void notifyStartReading(String mapId);
/**
* Notify that the graph has been fully read.
*/
public void notifyEndReading();
/**
* Notify that the reader is starting to read node.
*
* @param nNodes Number of nodes to read.
*/
public void notifyStartReadingNodes(int nNodes);
/**
* Notify that a new nodes has been read.
*
* @param node read.
*/
public void notifyNewNodeRead(Node node);
/**
* Notify that the reader is starting to read descriptor/road informations.
*
* @param nDesc Number of descriptors to read.
*/
public void notifyStartReadingDescriptors(int nDesc);
/**
* Notify that a new descriptor has been read.
*
* @param desc Descriptor read.
*/
public void notifyNewDescriptorRead(RoadInformation desc);
/**
* Notify that the reader is starting to read arcs.
*
* @param nArcs Number of arcs to read (!= number of arcs in the graph).
*/
public void notifyStartReadingArcs(int nArcs);
/**
* Notify that a new arc has been read.
*
* @param arc Arc read.
*/
public void notifyNewArcRead(Arc arc);
}

View File

@@ -0,0 +1,45 @@
package org.insa.graphs.model.io;
import java.io.IOException;
/**
* Exception thrown when there is mismatch between the expected map ID and the
* actual map ID when reading a graph.
*
*/
public class MapMismatchException extends IOException {
/**
*
*/
private static final long serialVersionUID = 1L;
// Actual and expected map ID.
private String actualMapId, expectedMapId;
/**
* Create a new MapMismatchException with the given IDs.
*
* @param actualMapId Actual map ID found when reading the path.
* @param expectedMapId Expected map ID from the graph.
*/
public MapMismatchException(String actualMapId, String expectedMapId) {
super();
this.actualMapId = actualMapId;
this.expectedMapId = expectedMapId;
}
/**
* @return Actual ID of the map (read from the path).
*/
public String getActualMapId() {
return actualMapId;
}
/**
* @return Expected ID of the map.
*/
public String getExpectedMapId() {
return expectedMapId;
}
}

View File

@@ -0,0 +1,34 @@
package org.insa.graphs.model.io;
import java.io.Closeable;
import java.io.IOException;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
/**
* Base interface that should be implemented by class used to read paths.
*
*/
public interface PathReader extends AutoCloseable, Closeable {
/**
* Read a path of the given graph and returns it.
*
* @param graph Graph of the path.
*
* @return Path read.
*
* @throws IOException When an error occurs while reading the path.
*/
public Path readPath(Graph graph) throws IOException;
/**
* Close this graph reader.
*
* @throws IOException if an exception occurs while closing the reader.
*
*/
public void close() throws IOException;
}

View File

@@ -0,0 +1,31 @@
package org.insa.graphs.model.io;
import java.io.Closeable;
import java.io.IOException;
import org.insa.graphs.model.Path;
/**
* Base interface that should be implemented by class used to write paths.
*
*/
public interface PathWriter extends AutoCloseable, Closeable {
/**
* Write the given path.
*
* @param path Path to write.
*
* @throws IOException When an error occurs while writing the path.
*/
public void writePath(Path path) throws IOException;
/**
* Close this graph reader.
*
* @throws IOException if an exception occurs while closing the reader.
*
*/
public void close() throws IOException;
}

View File

@@ -0,0 +1,112 @@
package org.insa.graphes.model;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.RoadInformation;
import org.insa.graphs.model.RoadInformation.RoadType;
import org.junit.BeforeClass;
import org.junit.Test;
public class GraphTest {
// Small graph use for tests
private static Graph graph;
// List of nodes
private static Node[] nodes;
@BeforeClass
public static void initAll() throws IOException {
// Create nodes
nodes = new Node[5];
for (int i = 0; i < nodes.length; ++i) {
nodes[i] = new Node(i, null);
}
Node.linkNodes(nodes[0], nodes[1], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[0], nodes[2], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[0], nodes[4], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[1], nodes[2], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[3], nodes[0], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[3], nodes[4], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[4], nodes[0], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
graph = new Graph("ID", "", Arrays.asList(nodes), null);
}
/**
* @return List of arcs between a and b.
*/
private List<Arc> getArcsBetween(Node a, Node b) {
List<Arc> arcs = new ArrayList<>();
for (Arc arc: a.getSuccessors()) {
if (arc.getDestination().equals(b)) {
arcs.add(arc);
}
}
return arcs;
}
@Test
public void testTranspose() {
Graph transpose = graph.transpose();
// Basic asserts...
assertEquals("R/" + graph.getMapId(), transpose.getMapId());
assertEquals(graph.size(), transpose.size());
final int expNbSucc[] = { 4, 2, 2, 4, 2 };
for (int i = 0; i < expNbSucc.length; ++i) {
assertEquals(expNbSucc[i], transpose.get(i).getNumberOfSuccessors());
}
assertEquals(1, getArcsBetween(transpose.get(0), transpose.get(1)).size());
assertEquals(1, getArcsBetween(transpose.get(0), transpose.get(2)).size());
assertEquals(1, getArcsBetween(transpose.get(0), transpose.get(3)).size());
assertEquals(1, getArcsBetween(transpose.get(0), transpose.get(4)).size());
assertEquals(1, getArcsBetween(transpose.get(1), transpose.get(0)).size());
assertEquals(1, getArcsBetween(transpose.get(1), transpose.get(2)).size());
assertEquals(0, getArcsBetween(transpose.get(1), transpose.get(3)).size());
assertEquals(0, getArcsBetween(transpose.get(1), transpose.get(4)).size());
assertEquals(1, getArcsBetween(transpose.get(2), transpose.get(0)).size());
assertEquals(1, getArcsBetween(transpose.get(2), transpose.get(1)).size());
assertEquals(0, getArcsBetween(transpose.get(2), transpose.get(3)).size());
assertEquals(0, getArcsBetween(transpose.get(2), transpose.get(4)).size());
assertEquals(1, getArcsBetween(transpose.get(3), transpose.get(0)).size());
assertEquals(0, getArcsBetween(transpose.get(3), transpose.get(1)).size());
assertEquals(3, getArcsBetween(transpose.get(3), transpose.get(2)).size());
assertEquals(0, getArcsBetween(transpose.get(3), transpose.get(4)).size());
assertEquals(1, getArcsBetween(transpose.get(4), transpose.get(0)).size());
assertEquals(0, getArcsBetween(transpose.get(4), transpose.get(1)).size());
assertEquals(0, getArcsBetween(transpose.get(4), transpose.get(2)).size());
assertEquals(1, getArcsBetween(transpose.get(4), transpose.get(3)).size());
}
}

View File

@@ -0,0 +1,92 @@
package org.insa.graphes.model;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.RoadInformation;
import org.insa.graphs.model.RoadInformation.RoadType;
import org.junit.Before;
import org.junit.Test;
public class NodeTest {
// List of nodes
private Node[] nodes;
@Before
public void initAll() throws IOException {
// Create nodes
nodes = new Node[6];
for (int i = 0; i < nodes.length; ++i) {
nodes[i] = new Node(i, null);
}
Node.linkNodes(nodes[0], nodes[1], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[0], nodes[2], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[0], nodes[4], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[1], nodes[2], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[2], nodes[3], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[3], nodes[0], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, false, 1, null),
new ArrayList<>());
Node.linkNodes(nodes[3], nodes[4], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
Node.linkNodes(nodes[4], nodes[0], 0,
new RoadInformation(RoadType.UNCLASSIFIED, null, true, 1, null), new ArrayList<>());
}
/**
* @return The first arc between from a to b, or null.
*/
private Arc getFirstArcBetween(Node a, Node b) {
for (Arc arc: a.getSuccessors()) {
if (arc.getDestination().equals(b)) {
return arc;
}
}
return null;
}
@Test
public void testGetNumberOfSuccessors() {
final int[] expNbSucc = { 4, 2, 5, 2, 1, 0 };
assertEquals(expNbSucc.length, nodes.length);
for (int i = 0; i < expNbSucc.length; ++i) {
assertEquals(expNbSucc[i], nodes[i].getNumberOfSuccessors());
}
}
@Test
public void testHasSuccessors() {
final int[] expNbSucc = { 4, 2, 5, 2, 1, 0 };
assertEquals(expNbSucc.length, nodes.length);
for (int i = 0; i < expNbSucc.length; ++i) {
assertEquals(expNbSucc[i] != 0, nodes[i].hasSuccessors());
}
}
@Test
public void testLinkNodes() {
assertEquals(getFirstArcBetween(nodes[0], nodes[1]).getRoadInformation(),
getFirstArcBetween(nodes[1], nodes[0]).getRoadInformation());
}
}

View File

@@ -0,0 +1,243 @@
package org.insa.graphes.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.RoadInformation;
import org.insa.graphs.model.RoadInformation.RoadType;
import org.junit.BeforeClass;
import org.junit.Test;
public class PathTest {
// Small graph use for tests
private static Graph graph;
// List of nodes
private static Node[] nodes;
// List of arcs in the graph, a2b is the arc from node A (0) to B (1).
@SuppressWarnings("unused")
private static Arc a2b, a2c, a2e, b2c, c2d_1, c2d_2, c2d_3, c2a, d2a, d2e, e2d;
// Some paths...
private static Path emptyPath, singleNodePath, shortPath, longPath, loopPath, longLoopPath,
invalidPath;
@BeforeClass
public static void initAll() throws IOException {
// 10 and 20 meters per seconds
RoadInformation speed10 = new RoadInformation(RoadType.MOTORWAY, null, true, 36, ""),
speed20 = new RoadInformation(RoadType.MOTORWAY, null, true, 72, "");
// Create nodes
nodes = new Node[5];
for (int i = 0; i < nodes.length; ++i) {
nodes[i] = new Node(i, null);
}
// Add arcs...
a2b = Node.linkNodes(nodes[0], nodes[1], 10, speed10, null);
a2c = Node.linkNodes(nodes[0], nodes[2], 15, speed10, null);
a2e = Node.linkNodes(nodes[0], nodes[4], 15, speed20, null);
b2c = Node.linkNodes(nodes[1], nodes[2], 10, speed10, null);
c2d_1 = Node.linkNodes(nodes[2], nodes[3], 20, speed10, null);
c2d_2 = Node.linkNodes(nodes[2], nodes[3], 10, speed10, null);
c2d_3 = Node.linkNodes(nodes[2], nodes[3], 15, speed20, null);
d2a = Node.linkNodes(nodes[3], nodes[0], 15, speed10, null);
d2e = Node.linkNodes(nodes[3], nodes[4], 22.8f, speed20, null);
e2d = Node.linkNodes(nodes[4], nodes[0], 10, speed10, null);
graph = new Graph("ID", "", Arrays.asList(nodes), null);
emptyPath = new Path(graph, new ArrayList<Arc>());
singleNodePath = new Path(graph, nodes[1]);
shortPath = new Path(graph, Arrays.asList(new Arc[] { a2b, b2c, c2d_1 }));
longPath = new Path(graph, Arrays.asList(new Arc[] { a2b, b2c, c2d_1, d2e }));
loopPath = new Path(graph, Arrays.asList(new Arc[] { a2b, b2c, c2d_1, d2a }));
longLoopPath = new Path(graph,
Arrays.asList(new Arc[] { a2b, b2c, c2d_1, d2a, a2c, c2d_3, d2a, a2b, b2c }));
invalidPath = new Path(graph, Arrays.asList(new Arc[] { a2b, c2d_1, d2e }));
}
@Test
public void testConstructor() {
assertEquals(graph, emptyPath.getGraph());
assertEquals(graph, singleNodePath.getGraph());
assertEquals(graph, shortPath.getGraph());
assertEquals(graph, longPath.getGraph());
assertEquals(graph, loopPath.getGraph());
assertEquals(graph, longLoopPath.getGraph());
assertEquals(graph, invalidPath.getGraph());
}
@Test(expected = UnsupportedOperationException.class)
public void testImmutability() {
emptyPath.getArcs().add(a2b);
}
@Test
public void testIsEmpty() {
assertTrue(emptyPath.isEmpty());
assertFalse(singleNodePath.isEmpty());
assertFalse(shortPath.isEmpty());
assertFalse(longPath.isEmpty());
assertFalse(loopPath.isEmpty());
assertFalse(longLoopPath.isEmpty());
assertFalse(invalidPath.isEmpty());
}
@Test
public void testSize() {
assertEquals(0, emptyPath.size());
assertEquals(1, singleNodePath.size());
assertEquals(4, shortPath.size());
assertEquals(5, longPath.size());
assertEquals(5, loopPath.size());
assertEquals(10, longLoopPath.size());
}
@Test
public void testIsValid() {
assertTrue(emptyPath.isValid());
assertTrue(singleNodePath.isValid());
assertTrue(shortPath.isValid());
assertTrue(longPath.isValid());
assertTrue(loopPath.isValid());
assertTrue(longLoopPath.isValid());
assertFalse(invalidPath.isValid());
}
@Test
public void testGetLength() {
assertEquals(0, emptyPath.getLength(), 1e-6);
assertEquals(0, singleNodePath.getLength(), 1e-6);
assertEquals(40, shortPath.getLength(), 1e-6);
assertEquals(62.8, longPath.getLength(), 1e-6);
assertEquals(55, loopPath.getLength(), 1e-6);
assertEquals(120, longLoopPath.getLength(), 1e-6);
}
@Test
public void testGetTravelTime() {
// Note: 18 km/h = 5m/s
assertEquals(0, emptyPath.getTravelTime(18), 1e-6);
assertEquals(0, singleNodePath.getTravelTime(18), 1e-6);
assertEquals(8, shortPath.getTravelTime(18), 1e-6);
assertEquals(12.56, longPath.getTravelTime(18), 1e-6);
assertEquals(11, loopPath.getTravelTime(18), 1e-6);
assertEquals(24, longLoopPath.getTravelTime(18), 1e-6);
// Note: 28.8 km/h = 8m/s
assertEquals(0, emptyPath.getTravelTime(28.8), 1e-6);
assertEquals(0, singleNodePath.getTravelTime(28.8), 1e-6);
assertEquals(5, shortPath.getTravelTime(28.8), 1e-6);
assertEquals(7.85, longPath.getTravelTime(28.8), 1e-6);
assertEquals(6.875, loopPath.getTravelTime(28.8), 1e-6);
assertEquals(15, longLoopPath.getTravelTime(28.8), 1e-6);
}
@Test
public void testGetMinimumTravelTime() {
assertEquals(0, emptyPath.getMinimumTravelTime(), 1e-4);
assertEquals(0, singleNodePath.getLength(), 1e-4);
assertEquals(4, shortPath.getMinimumTravelTime(), 1e-4);
assertEquals(5.14, longPath.getMinimumTravelTime(), 1e-4);
assertEquals(5.5, loopPath.getMinimumTravelTime(), 1e-4);
assertEquals(11.25, longLoopPath.getMinimumTravelTime(), 1e-4);
}
@Test
public void testCreateFastestPathFromNodes() {
Path path;
Arc[] expected;
// Simple construction
path = Path.createFastestPathFromNodes(graph,
Arrays.asList(new Node[] { nodes[0], nodes[1], nodes[2] }));
expected = new Arc[] { a2b, b2c };
assertEquals(expected.length, path.getArcs().size());
for (int i = 0; i < expected.length; ++i) {
assertEquals(expected[i], path.getArcs().get(i));
}
// Not so simple construction
path = Path.createFastestPathFromNodes(graph,
Arrays.asList(new Node[] { nodes[0], nodes[1], nodes[2], nodes[3] }));
expected = new Arc[] { a2b, b2c, c2d_3 };
assertEquals(expected.length, path.getArcs().size());
for (int i = 0; i < expected.length; ++i) {
assertEquals(expected[i], path.getArcs().get(i));
}
// Trap construction!
path = Path.createFastestPathFromNodes(graph, Arrays.asList(new Node[] { nodes[1] }));
assertEquals(nodes[1], path.getOrigin());
assertEquals(0, path.getArcs().size());
// Trap construction - The return!
path = Path.createFastestPathFromNodes(graph, Arrays.asList(new Node[0]));
assertEquals(null, path.getOrigin());
assertEquals(0, path.getArcs().size());
assertTrue(path.isEmpty());
}
@Test
public void testCreateShortestPathFromNodes() {
Path path;
Arc[] expected;
// Simple construction
path = Path.createShortestPathFromNodes(graph,
Arrays.asList(new Node[] { nodes[0], nodes[1], nodes[2] }));
expected = new Arc[] { a2b, b2c };
assertEquals(expected.length, path.getArcs().size());
for (int i = 0; i < expected.length; ++i) {
assertEquals(expected[i], path.getArcs().get(i));
}
// Not so simple construction
path = Path.createShortestPathFromNodes(graph,
Arrays.asList(new Node[] { nodes[0], nodes[1], nodes[2], nodes[3] }));
expected = new Arc[] { a2b, b2c, c2d_2 };
assertEquals(expected.length, path.getArcs().size());
for (int i = 0; i < expected.length; ++i) {
assertEquals(expected[i], path.getArcs().get(i));
}
// Trap construction!
path = Path.createShortestPathFromNodes(graph, Arrays.asList(new Node[] { nodes[1] }));
assertEquals(nodes[1], path.getOrigin());
assertEquals(0, path.getArcs().size());
// Trap construction - The return!
path = Path.createShortestPathFromNodes(graph, Arrays.asList(new Node[0]));
assertEquals(null, path.getOrigin());
assertEquals(0, path.getArcs().size());
assertTrue(path.isEmpty());
}
@Test(expected = IllegalArgumentException.class)
public void testCreateFastestPathFromNodesException() {
Path.createFastestPathFromNodes(graph, Arrays.asList(new Node[] { nodes[1], nodes[0] }));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateShortestPathFromNodesException() {
Path.createShortestPathFromNodes(graph, Arrays.asList(new Node[] { nodes[1], nodes[0] }));
}
}