Importation du code depuis GitHub

This commit is contained in:
gasc 2021-08-22 13:22:44 +02:00
commit 59329c4dbc
119 changed files with 11539 additions and 0 deletions

28
README.md Normal file
View file

@ -0,0 +1,28 @@
# I3MIIL11 - Bureau d'étude graphes
Répertoire de travail pour le bureau d'étude graphes de 3 MIC à l'INSA de Toulouse.
## Remarques
Ce BE sera à priori fait sous VSCode (sous Ubuntu 20.04.02) au lieu d'Eclispe pour des raisons de praticité et d'habitudes (et aussi parce que je n'ai pas énormément de place sur mon disque dur). Jusqu'à présent ce choix ne m'a posé aucun problème hormis pour générer la docs.
## Algorithme de Dijkstra (PCC)
Voici quelques résultats visuels de mon algorithme de Dijkstra en mode plus court chemin. D'abord sans observateurs, en vert sans contrainte de chemin, en rouge les routes autorisées aux voitures uniquement :
![2 chemins](rsc/img1.png)
Exemple de résultat avec observateurs :
![avec observateurs](rsc/img2.png)
## Test
### Test avec oracle
Pour effectuer ces tests, j'ai utiliser le GUI pour créer un oracle avec Bellman Ford (un trajet entre l'aéroport LFBO et l'INSA de Toulouse sur la carte Haute Garonne). Notez que les tests peuvent être effectués avec n'importe quel autre oracle (il suffit de changer la carte et l'oracle).
On vérifie alors que la solution retournée est equivalente à l'oracle. *Note: On vérifie pas l'égalité strict des chemins car (théoriquement) deux chemins optimaux peuvent co-exister.*
### Test d'optimalité (tests sans oracle)
Plusieurs tests sans oracle ont été implémentés :
- Test des sous-chemins : tous les sous-chemins doivent également suivre les mêmes nodes
- Test du vol d'oiseau : la distance en vol d'oiseau doit être plus courte que la distance obtenue
- Test de l'inégalité triangulaire : le même trajet passant par un troisième point doit être plus long ou égal en distance au chemin trouvé

25
be-graphes-algos/pom.xml Normal file
View file

@ -0,0 +1,25 @@
<?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-algos</artifactId>
<name>be-graphes-algos</name>
<dependencies>
<dependency>
<groupId>org.insa.graphs</groupId>
<artifactId>be-graphes-model</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,87 @@
package org.insa.graphs.algorithm;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
/**
* Base class for algorithm classes.
*
* @param <Observer> Observer type for the algorithm.
*/
public abstract class AbstractAlgorithm<Observer> {
// Input data for the algorithm
protected final AbstractInputData data;
// List of observers for the algorithm
protected final ArrayList<Observer> observers;
/**
* Create a new algorithm with an empty list of observers.
*
* @param data Input data for the algorithm.
*/
protected AbstractAlgorithm(AbstractInputData data) {
this.data = data;
this.observers = new ArrayList<Observer>();
}
/**
* Create a new algorithm with the given list of observers.
*
* @param data Input data for the algorithm.
* @param observers Initial list of observers for the algorithm.
*/
protected AbstractAlgorithm(AbstractInputData data, ArrayList<Observer> observers) {
this.data = data;
this.observers = observers;
}
/**
* Add an observer to this algorithm.
*
* @param observer Observer to add to this algorithm.
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* @return The list of observers for this algorithm.
*/
public ArrayList<Observer> getObservers() {
return observers;
}
/**
* @return Input for this algorithm.
*/
public AbstractInputData getInputData() {
return data;
}
/**
* Run the algorithm and return the solution.
*
* This methods internally time the call to doRun() and update the result of the
* call with the computed solving time.
*
* @return The solution found by the algorithm (may not be a feasible solution).
*/
public AbstractSolution run() {
Instant start = Instant.now();
AbstractSolution solution = this.doRun();
solution.setSolvingTime(Duration.between(start, Instant.now()));
return solution;
}
/**
* Abstract method that should be implemented by child class.
*
* @return The solution found, must not be null (use an infeasible or unknown
* status if necessary).
*/
protected abstract AbstractSolution doRun();
}

View file

@ -0,0 +1,97 @@
package org.insa.graphs.algorithm;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.GraphStatistics;
/**
* Base class for algorithm input data classes. This class contains the basic
* data that are required by most graph algorithms, i.e. a graph, a mode (time /
* length) and a filter for the arc.
*
*/
public abstract class AbstractInputData {
/**
* Enum specifying the top mode of the algorithms.
*
* @see ArcInspector
*/
public enum Mode {
TIME, LENGTH
}
// Graph
private final Graph graph;
// Arc filter.
protected final ArcInspector arcInspector;
/**
* Create a new AbstractInputData instance for the given graph, mode and filter.
*
* @param graph Graph for this input data.
* @param arcInspector Arc inspector for this input data.
*/
protected AbstractInputData(Graph graph, ArcInspector arcInspector) {
this.graph = graph;
this.arcInspector = arcInspector;
}
/**
* @return Graph associated with this input.
*/
public Graph getGraph() {
return graph;
}
/**
* Retrieve the cost associated with the given arc according to the underlying
* arc inspector.
*
* @param arc Arc for which cost should be retrieved.
*
* @return Cost for the given arc.
*
* @see ArcInspector
*/
public double getCost(Arc arc) {
return this.arcInspector.getCost(arc);
}
/**
* @return Mode associated with this input data.
*
* @see Mode
*/
public Mode getMode() {
return this.arcInspector.getMode();
}
/**
* Retrieve the maximum speed associated with this input data, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is associated. The maximum
* speed associated with input data is different from the maximum speed
* associated with graph (accessible via {@link Graph#getGraphInformation()}).
*
* @return The maximum speed for this inspector, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
*/
public int getMaximumSpeed() {
return this.arcInspector.getMaximumSpeed();
}
/**
* Check if the given arc is allowed for the filter corresponding to this input.
*
* @param arc Arc to check.
*
* @return true if the given arc is allowed.
*
* @see ArcInspector
*/
public boolean isAllowed(Arc arc) {
return this.arcInspector.isAllowed(arc);
}
}

View file

@ -0,0 +1,88 @@
package org.insa.graphs.algorithm;
import java.time.Duration;
/**
* Base class for solution classes returned by the algorithm. This class
* contains the basic information that any solution should have: status of the
* solution (unknown, infeasible, etc.), solving time and the original input
* data.
*/
public abstract class AbstractSolution {
/**
* Possible status for a solution.
*
*/
public enum Status {
UNKNOWN, INFEASIBLE, FEASIBLE, OPTIMAL,
};
// Status of the solution.
private final Status status;
// Solving time for the solution.
private Duration solvingTime;
// Original input of the solution.
private final AbstractInputData data;
/**
* Create a new abstract solution with unknown status.
*
* @param data
*/
protected AbstractSolution(AbstractInputData data) {
this.data = data;
this.solvingTime = Duration.ZERO;
this.status = Status.UNKNOWN;
}
/**
*
* @param data
* @param status
*/
protected AbstractSolution(AbstractInputData data, Status status) {
this.data = data;
this.status = status;
}
/**
* @return Original input for this solution.
*/
public AbstractInputData getInputData() {
return data;
}
/**
* @return Status of this solution.
*/
public Status getStatus() {
return status;
}
/**
* @return Solving time of this solution.
*/
public Duration getSolvingTime() {
return solvingTime;
}
/**
* Set the solving time of this solution.
*
* @param solvingTime Solving time for the solution.
*/
protected void setSolvingTime(Duration solvingTime) {
this.solvingTime = solvingTime;
}
/**
* @return true if the solution is feasible or optimal.
*/
public boolean isFeasible() {
return status == Status.FEASIBLE || status == Status.OPTIMAL;
}
}

View file

@ -0,0 +1,128 @@
package org.insa.graphs.algorithm;
import java.lang.reflect.Constructor;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
/**
* Factory class used to register and retrieve algorithms based on their common
* ancestor and name.
*
*/
public class AlgorithmFactory {
// Map between algorithm names and class.
private final static Map<Class<? extends AbstractAlgorithm<?>>, Map<String, Class<? extends AbstractAlgorithm<?>>>> ALGORITHMS = new IdentityHashMap<>();
static {
// Register weakly-connected components algorithm:
registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic",
WeaklyConnectedComponentsAlgorithm.class);
// Register shortest path algorithm:
registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class);
registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class);
registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class);
// Register your algorithms here:
// registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm",
// MyCarPoolingAlgorithm.class);
}
/**
* Register the given algorithm class with the given name as a child class of
* the given base algorithm.
*
* @param baseAlgorithm Base algorithm class that corresponds to the newly
* registered algorithm class (e.g., generic algorithm
* class for the problem).
* @param name Name for the registered algorithm class.
* @param algoClass Algorithm class to register.
*/
public static void registerAlgorithm(Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
String name, Class<? extends AbstractAlgorithm<?>> algoClass) {
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>());
}
ALGORITHMS.get(baseAlgorithm).put(name, algoClass);
}
/**
* Create an instance of the given algorithm class using the given input data.
* Assuming algorithm correspond to a class "Algorithm", this function returns
* an object equivalent to `new Algorithm(data)`.
*
* @param algorithm Class of the algorithm to create.
* @param data Input data for the algorithm.
*
* @return A new instance of the given algorithm class using the given data.
*
* @throws Exception if something wrong happens when constructing the object,
* i.e. the given input data does not correspond to the given
* algorithm and/or no constructor that takes a single
* parameter of type (data.getClass()) exists.
*/
public static AbstractAlgorithm<?> createAlgorithm(
Class<? extends AbstractAlgorithm<?>> algorithm, AbstractInputData data)
throws Exception {
// Retrieve the set of constructors for the given algorithm class.
Constructor<?>[] constructors = algorithm.getDeclaredConstructors();
// Within this set, find the constructor that can be called with "data" (only).
AbstractAlgorithm<?> constructed = null;
for (Constructor<?> c: constructors) {
Class<?>[] params = c.getParameterTypes();
if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) {
c.setAccessible(true);
constructed = (AbstractAlgorithm<?>) c.newInstance(new Object[] { data });
break;
}
}
return constructed;
}
/**
* Return the algorithm class corresponding to the given base algorithm class
* and name. The algorithm must have been previously registered using
* registerAlgorithm.
*
* @param baseAlgorithm Base algorithm class for the algorithm to retrieve.
* @param name Name of the algorithm to retrieve.
*
* @return Class corresponding to the given name.
*
* @see #registerAlgorithm
*/
public static Class<? extends AbstractAlgorithm<?>> getAlgorithmClass(
Class<? extends AbstractAlgorithm<?>> baseAlgorithm, String name) {
return ALGORITHMS.get(baseAlgorithm).get(name);
}
/**
* Return the list of names corresponding to the registered algorithm classes
* for the given base algorithm class.
*
* @param baseAlgorithm Base algorithm class for the algorithm class names to
* retrieve.
*
* @return Names of the currently registered algorithms.
*
* @see #registerAlgorithm
*/
public static Set<String> getAlgorithmNames(
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
return new TreeSet<>();
}
return ALGORITHMS.get(baseAlgorithm).keySet();
}
}

View file

@ -0,0 +1,43 @@
package org.insa.graphs.algorithm;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.GraphStatistics;
/**
* This class can be used to indicate to an algorithm which arcs can be used and
* the costs of the usable arcs..
*
*/
public interface ArcInspector {
/**
* Check if the given arc can be used (is allowed).
*
* @param arc Arc to check.
*
* @return true if the given arc is allowed.
*/
public boolean isAllowed(Arc arc);
/**
* Find the cost of the given arc.
*
* @param arc Arc for which the cost should be returned.
*
* @return Cost of the arc.
*/
public double getCost(Arc arc);
/**
* @return The maximum speed for this inspector, or
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
*/
public int getMaximumSpeed();
/**
* @return Mode for this arc inspector.
*/
public Mode getMode();
}

View file

@ -0,0 +1,177 @@
package org.insa.graphs.algorithm;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.GraphStatistics;
import org.insa.graphs.model.AccessRestrictions.AccessMode;
import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
public class ArcInspectorFactory {
/**
* @return List of all arc filters in this factory.
*/
public static List<ArcInspector> getAllFilters() {
List<ArcInspector> filters = new ArrayList<>();
// Common filters:
// No filter (all arcs allowed):
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return true;
}
@Override
public double getCost(Arc arc) {
return arc.getLength();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.LENGTH;
}
@Override
public String toString() {
return "Shortest path, all roads allowed";
}
});
// Only road allowed for cars and length:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getLength();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.LENGTH;
}
@Override
public String toString() {
return "Shortest path, only roads open for cars";
}
});
// Only road allowed for cars and time:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return true;
}
@Override
public double getCost(Arc arc) {
return arc.getMinimumTravelTime();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
@Override
public String toString() {
return "Fastest path, all roads allowed";
}
});
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getMinimumTravelTime();
}
@Override
public int getMaximumSpeed() {
return GraphStatistics.NO_MAXIMUM_SPEED;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
@Override
public String toString() {
return "Fastest path, only roads open for cars";
}
});
// Non-private roads for pedestrian and bicycle:
filters.add(new ArcInspector() {
@Override
public boolean isAllowed(Arc arc) {
return arc.getRoadInformation().getAccessRestrictions()
.isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
}
@Override
public double getCost(Arc arc) {
return arc.getTravelTime(
Math.min(getMaximumSpeed(), arc.getRoadInformation().getMaximumSpeed()));
}
@Override
public String toString() {
return "Fastest path for pedestrian";
}
@Override
public int getMaximumSpeed() {
return 5;
}
@Override
public Mode getMode() {
return Mode.TIME;
}
});
// Add your own filters here (do not forget to implement toString()
// to get an understandable output!):
return filters;
}
}

View file

@ -0,0 +1,24 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractAlgorithm;
public abstract class CarPoolingAlgorithm extends AbstractAlgorithm<CarPoolingObserver> {
protected CarPoolingAlgorithm(CarPoolingData data) {
super(data);
}
@Override
public CarPoolingSolution run() {
return (CarPoolingSolution) super.run();
}
@Override
protected abstract CarPoolingSolution doRun();
@Override
public CarPoolingData getInputData() {
return (CarPoolingData) super.getInputData();
}
}

View file

@ -0,0 +1,13 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
public class CarPoolingData extends AbstractInputData {
protected CarPoolingData(Graph graph, ArcInspector arcFilter) {
super(graph, arcFilter);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingGraphicObserver implements CarPoolingObserver {
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public interface CarPoolingObserver {
}

View file

@ -0,0 +1,11 @@
package org.insa.graphs.algorithm.carpooling;
import org.insa.graphs.algorithm.AbstractSolution;
public class CarPoolingSolution extends AbstractSolution {
protected CarPoolingSolution(CarPoolingData data, Status status) {
super(data, status);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingTextObserver implements CarPoolingObserver {
}

View file

@ -0,0 +1,29 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractAlgorithm;
public abstract class PackageSwitchAlgorithm extends AbstractAlgorithm<PackageSwitchObserver> {
/**
* Create a new PackageSwitchAlgorithm with the given data.
*
* @param data
*/
protected PackageSwitchAlgorithm(PackageSwitchData data) {
super(data);
}
@Override
public PackageSwitchSolution run() {
return (PackageSwitchSolution) super.run();
}
@Override
protected abstract PackageSwitchSolution doRun();
@Override
public PackageSwitchData getInputData() {
return (PackageSwitchData) super.getInputData();
}
}

View file

@ -0,0 +1,13 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
public class PackageSwitchData extends AbstractInputData {
protected PackageSwitchData(Graph graph, ArcInspector arcFilter) {
super(graph, arcFilter);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public interface PackageSwitchObserver {
}

View file

@ -0,0 +1,11 @@
package org.insa.graphs.algorithm.packageswitch;
import org.insa.graphs.algorithm.AbstractSolution;
public class PackageSwitchSolution extends AbstractSolution {
protected PackageSwitchSolution(PackageSwitchData data, Status status) {
super(data, status);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchTextObserver implements PackageSwitchObserver {
}

View file

@ -0,0 +1,58 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Point;
public class AStarAlgorithm extends DijkstraAlgorithm {
Node arrivee;
Mode mode;
double max_speed;
class LabelStar extends Label {
Node arrivee;
double heuristiqueArrive;
public LabelStar(Node sommet_courant, Node pere, double cout, Node arrivee) {
super(sommet_courant, pere, cout);
this.arrivee = arrivee;
switch (mode) {
case TIME:
this.heuristiqueArrive = this.sommet_courant.getPoint().distanceTo(arrivee.getPoint()) / max_speed;
break;
case LENGTH:
this.heuristiqueArrive = Point.distance(super.sommet_courant.getPoint(), arrivee.getPoint());
break;
}
}
@Override
public int compareTo(Label other) throws IllegalArgumentException {
return Double.compare(
this.cout + heuristiqueArrive,
other.cout + ((LabelStar)other).heuristiqueArrive);
}
}
public AStarAlgorithm(ShortestPathData data) {
super(data);
this.arrivee = data.getDestination();
this.mode = data.getMode();
this.max_speed = (double) data.getMaximumSpeed()/3.6;
if (this.max_speed < 0){
this.max_speed = (double) data.getGraph().getGraphInformation().getMaximumSpeed() / 3.6;
}
}
@Override
public Label createLabel(Node n) {
return new LabelStar(n, null, Float.MAX_VALUE, arrivee);
}
public Label createLabel(Node n, double cout) {
return new LabelStar(n, null, cout, arrivee);
}
}

View file

@ -0,0 +1,100 @@
package org.insa.graphs.algorithm.shortestpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.insa.graphs.algorithm.AbstractSolution.Status;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
public class BellmanFordAlgorithm extends ShortestPathAlgorithm {
public BellmanFordAlgorithm(ShortestPathData data) {
super(data);
}
@Override
protected ShortestPathSolution doRun() {
// Retrieve the graph.
ShortestPathData data = getInputData();
Graph graph = data.getGraph();
final int nbNodes = graph.size();
// Initialize array of distances.
double[] distances = new double[nbNodes];
Arrays.fill(distances, Double.POSITIVE_INFINITY);
distances[data.getOrigin().getId()] = 0;
// Notify observers about the first event (origin processed).
notifyOriginProcessed(data.getOrigin());
// Initialize array of predecessors.
Arc[] predecessorArcs = new Arc[nbNodes];
// Actual algorithm, we will assume the graph does not contain negative
// cycle...
boolean found = false;
for (int i = 0; !found && i < nbNodes; ++i) {
found = true;
for (Node node: graph.getNodes()) {
for (Arc arc: node.getSuccessors()) {
// Small test to check allowed roads...
if (!data.isAllowed(arc)) {
continue;
}
// Retrieve weight of the arc.
double w = data.getCost(arc);
double oldDistance = distances[arc.getDestination().getId()];
double newDistance = distances[node.getId()] + w;
if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) {
notifyNodeReached(arc.getDestination());
}
// Check if new distances would be better, if so update...
if (newDistance < oldDistance) {
found = false;
distances[arc.getDestination().getId()] = distances[node.getId()] + w;
predecessorArcs[arc.getDestination().getId()] = arc;
}
}
}
}
ShortestPathSolution solution = null;
// Destination has no predecessor, the solution is infeasible...
if (predecessorArcs[data.getDestination().getId()] == null) {
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
}
else {
// The destination has been found, notify the observers.
notifyDestinationReached(data.getDestination());
// Create the path from the array of predecessors...
ArrayList<Arc> arcs = new ArrayList<>();
Arc arc = predecessorArcs[data.getDestination().getId()];
while (arc != null) {
arcs.add(arc);
arc = predecessorArcs[arc.getOrigin().getId()];
}
// Reverse the path...
Collections.reverse(arcs);
// Create the final solution.
solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs));
}
return solution;
}
}

View file

@ -0,0 +1,154 @@
package org.insa.graphs.algorithm.shortestpath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import org.insa.graphs.algorithm.AbstractSolution.Status;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
import org.insa.graphs.algorithm.utils.*;
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
class Label implements Comparable<Label> {
Node sommet_courant, pere;
Boolean marque;
double cout;
public Label(Node sommet_courant, Node pere, double cout) {
this.sommet_courant = sommet_courant;
this.pere = pere;
this.marque = false;
this.cout = cout;
}
public double getCost() {
return cout;
}
public void setMark() {
this.marque = true;
}
public int compareTo(Label other) {
return Double.compare(cout, other.cout);
}
}
public DijkstraAlgorithm(ShortestPathData data) {
super(data);
}
public Label createLabel(Node n){
return new Label(n, null, Float.MAX_VALUE);
}
public Label createLabel(Node n, double cout) {
return new Label(n, null, cout);
}
@Override
protected ShortestPathSolution doRun() {
// Récupération des données du problème
final ShortestPathData data = getInputData();
// Cas triviaux
if (data.getOrigin() == data.getDestination())
return new ShortestPathSolution(data, Status.OPTIMAL, new Path(data.getGraph(), data.getOrigin()));
// Tas des nodes visités, initialisée avec le node d'origine
BinaryHeap<Label> visite = new BinaryHeap<Label>();
visite.insert(new Label(data.getOrigin(), null, 0));
notifyOriginProcessed(data.getOrigin());
// Map de correspondance Node - Label initialisée avec des labels par défaut.
HashMap<Node, Label> map = new HashMap<Node, Label>();
for (Node n : data.getGraph().getNodes())
map.put(n, createLabel(n));
/// Varibles de fonctionnement
// Varible contenant le node le plus intéressant
Label smallest;
// Variable contenant le node destination
Node dest = data.getDestination();
while (true) {
// Est-ce qu'il reste des nodes à explorer ?
// Si oui, on récupère le plus intéressant
// Sinon, il n'y a pas de solution --> on quitte
try {
smallest = visite.deleteMin();
} catch (EmptyPriorityQueueException e) {
return new ShortestPathSolution(data, Status.INFEASIBLE);
}
// On marque ce node
smallest.setMark();
notifyNodeMarked(smallest.sommet_courant);
// Si on est arrivé, on arrête
if (smallest.sommet_courant == dest) break;
// Pour chacun des sommets fils, on regarde s'ils proposent des chemins plus intéressants
for (Arc arc : smallest.sommet_courant.getSuccessors()) {
// On test si le chemin n'est pas permis, on skip
if (!data.isAllowed(arc)) continue;
Node node = arc.getDestination();
Label l = map.get(node);
// S'il est déjà marqué, on skip
if (l.marque) continue;
double n_cout = smallest.getCost() + data.getCost(arc);
//if (l.compareTo(createLabel(node, n_cout)) == 1) {
if (l.cout > n_cout) {
visite.tryremove(l);
l.cout = n_cout;
l.pere = smallest.sommet_courant;
l.sommet_courant = node;
notifyNodeReached(node);
visite.insert(l);
}
}
}
notifyDestinationReached(smallest.sommet_courant);
// Liste des nodes formant la route entre notre origine et destination
ArrayList<Node> nodes = new ArrayList<Node>();
Label last = smallest;
// On remplit la liste en se référant au père à chaque fois
nodes.add(last.sommet_courant);
while (true) {
smallest = map.get(smallest.pere);
nodes.add(smallest.sommet_courant);
if (smallest == map.get(data.getOrigin())) break;
}
// On inverve le sens des nodes
Collections.reverse(nodes);
// On créé le chemin
Path p = Path.createShortestPathFromNodes(data.getGraph(), nodes);
/** Vérification des résultats --> Toutes les tests sont ok */
// Vérification des coûts croisants (l'adjectif, pas la patisserie)
//for (Node n: nodes) System.out.println(map.get(n).cout);
// Vérification du chemin
//if (p.isValid()) System.out.println("Chemin valide !");
//System.out.println("CD: " + last.cout + "; CPCC: " + p.getLength());
// On créé on chemin à partir de la liste que l'on donne en solution
return new ShortestPathSolution(data, Status.OPTIMAL, p);
}
}

View file

@ -0,0 +1,69 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractAlgorithm;
import org.insa.graphs.model.Node;
public abstract class ShortestPathAlgorithm extends AbstractAlgorithm<ShortestPathObserver> {
protected ShortestPathAlgorithm(ShortestPathData data) {
super(data);
}
@Override
public ShortestPathSolution run() {
return (ShortestPathSolution) super.run();
}
@Override
protected abstract ShortestPathSolution doRun();
@Override
public ShortestPathData getInputData() {
return (ShortestPathData) super.getInputData();
}
/**
* Notify all observers that the origin has been processed.
*
* @param node Origin.
*/
public void notifyOriginProcessed(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyOriginProcessed(node);
}
}
/**
* Notify all observers that a node has been reached for the first time.
*
* @param node Node that has been reached.
*/
public void notifyNodeReached(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyNodeReached(node);
}
}
/**
* Notify all observers that a node has been marked, i.e. its final value has
* been set.
*
* @param node Node that has been marked.
*/
public void notifyNodeMarked(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyNodeMarked(node);
}
}
/**
* Notify all observers that the destination has been reached.
*
* @param node Destination.
*/
public void notifyDestinationReached(Node node) {
for (ShortestPathObserver obs: getObservers()) {
obs.notifyDestinationReached(node);
}
}
}

View file

@ -0,0 +1,47 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
public class ShortestPathData extends AbstractInputData {
// Origin and destination nodes.
private final Node origin, destination;
/**
* Construct a new instance of ShortestPathInputData with the given parameters.
*
* @param graph Graph in which the path should be looked for.
* @param origin Origin node of the path.
* @param destination Destination node of the path.
* @param arcInspector Filter for arcs (used to allow only a specific set of
* arcs in the graph to be used).
*/
public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) {
super(graph, arcInspector);
this.origin = origin;
this.destination = destination;
}
/**
* @return Origin node for the path.
*/
public Node getOrigin() {
return origin;
}
/**
* @return Destination node for the path.
*/
public Node getDestination() {
return destination;
}
@Override
public String toString() {
return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " ["
+ this.arcInspector.toString().toLowerCase() + "]";
}
}

View file

@ -0,0 +1,37 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.model.Node;
public interface ShortestPathObserver {
/**
* Notify the observer that the origin has been processed.
*
* @param node Origin.
*/
public void notifyOriginProcessed(Node node);
/**
* Notify the observer that a node has been reached for the first
* time.
*
* @param node Node that has been reached.
*/
public void notifyNodeReached(Node node);
/**
* Notify the observer that a node has been marked, i.e. its final
* value has been set.
*
* @param node Node that has been marked.
*/
public void notifyNodeMarked(Node node);
/**
* Notify the observer that the destination has been reached.
*
* @param node Destination.
*/
public void notifyDestinationReached(Node node);
}

View file

@ -0,0 +1,74 @@
package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Path;
import org.insa.graphs.algorithm.AbstractSolution;
public class ShortestPathSolution extends AbstractSolution {
// Optimal solution.
private final Path path;
/**
* Create a new infeasible shortest-path solution for the given input and
* status.
*
* @param data Original input data for this solution.
* @param status Status of the solution (UNKNOWN / INFEASIBLE).
*/
public ShortestPathSolution(ShortestPathData data, Status status) {
super(data, status);
this.path = null;
}
/**
* Create a new shortest-path solution.
*
* @param data Original input data for this solution.
* @param status Status of the solution (FEASIBLE / OPTIMAL).
* @param path Path corresponding to the solution.
*/
public ShortestPathSolution(ShortestPathData data, Status status, Path path) {
super(data, status);
this.path = path;
}
@Override
public ShortestPathData getInputData() {
return (ShortestPathData) super.getInputData();
}
/**
* @return The path of this solution, if any.
*/
public Path getPath() {
return path;
}
@Override
public String toString() {
String info = null;
if (!isFeasible()) {
info = String.format("No path found from node #%d to node #%d",
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
}
else {
double cost = 0;
for (Arc arc: getPath().getArcs()) {
cost += getInputData().getCost(arc);
}
info = String.format("Found a path from node #%d to node #%d",
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
if (getInputData().getMode() == Mode.LENGTH) {
info = String.format("%s, %.4f kilometers", info, cost / 1000.0);
}
else {
info = String.format("%s, %.4f minutes", info, cost / 60.0);
}
}
info += " in " + getSolvingTime().getSeconds() + " seconds.";
return info;
}
}

View file

@ -0,0 +1,37 @@
package org.insa.graphs.algorithm.shortestpath;
import java.io.PrintStream;
import org.insa.graphs.model.Node;
public class ShortestPathTextObserver implements ShortestPathObserver {
private final PrintStream stream;
public ShortestPathTextObserver(PrintStream stream) {
this.stream = stream;
}
@Override
public void notifyOriginProcessed(Node node) {
// TODO Auto-generated method stub
}
@Override
public void notifyNodeReached(Node node) {
stream.println("Node " + node.getId() + " reached.");
}
@Override
public void notifyNodeMarked(Node node) {
stream.println("Node " + node.getId() + " marked.");
}
@Override
public void notifyDestinationReached(Node node) {
// TODO Auto-generated method stub
}
}

View file

@ -0,0 +1,234 @@
package org.insa.graphs.algorithm.utils;
import java.util.ArrayList;
/**
* Implements a binary heap containing elements of type E.
*
* Note that all comparisons are based on the compareTo method, hence E must
* implement Comparable
*
* @author Mark Allen Weiss
* @author DLB
*/
public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
// Number of elements in heap.
private int currentSize;
// The heap array.
protected final ArrayList<E> array;
/**
* Construct a new empty binary heap.
*/
public BinaryHeap() {
this.currentSize = 0;
this.array = new ArrayList<E>();
}
/**
* Construct a copy of the given heap.
*
* @param heap Binary heap to copy.
*/
public BinaryHeap(BinaryHeap<E> heap) {
this.currentSize = heap.currentSize;
this.array = new ArrayList<E>(heap.array);
}
/**
* Set an element at the given index.
*
* @param index Index at which the element should be set.
* @param value Element to set.
*/
private void arraySet(int index, E value) {
if (index == this.array.size()) {
this.array.add(value);
}
else {
this.array.set(index, value);
}
}
/**
* @return Index of the parent of the given index.
*/
protected int indexParent(int index) {
return (index - 1) / 2;
}
/**
* @return Index of the left child of the given index.
*/
protected int indexLeft(int index) {
return index * 2 + 1;
}
/**
* Internal method to percolate up in the heap.
*
* @param index Index at which the percolate begins.
*/
private void percolateUp(int index) {
E x = this.array.get(index);
for (; index > 0
&& x.compareTo(this.array.get(indexParent(index))) < 0; index = indexParent(
index)) {
E moving_val = this.array.get(indexParent(index));
this.arraySet(index, moving_val);
}
this.arraySet(index, x);
}
/**
* Internal method to percolate down in the heap.
*
* @param index Index at which the percolate begins.
*/
private void percolateDown(int index) {
int ileft = indexLeft(index);
int iright = ileft + 1;
if (ileft < this.currentSize) {
E current = this.array.get(index);
E left = this.array.get(ileft);
boolean hasRight = iright < this.currentSize;
E right = (hasRight) ? this.array.get(iright) : null;
if (!hasRight || left.compareTo(right) < 0) {
// Left is smaller
if (left.compareTo(current) < 0) {
this.arraySet(index, left);
this.arraySet(ileft, current);
this.percolateDown(ileft);
}
}
else {
// Right is smaller
if (right.compareTo(current) < 0) {
this.arraySet(index, right);
this.arraySet(iright, current);
this.percolateDown(iright);
}
}
}
}
@Override
public boolean isEmpty() {
return this.currentSize == 0;
}
@Override
public int size() {
return this.currentSize;
}
@Override
public void insert(E x) {
int index = this.currentSize++;
this.arraySet(index, x);
this.percolateUp(index);
}
@Override
public void remove(E x) throws ElementNotFoundException {
// Difficile de faire mieux que du O(n) ici
// Surtout qu'un tas binaire n'est pas fait pour
// chercher un élément...
int ie = this.array.indexOf(x);
//int ie = 0;
//while (ie<this.currentSize && !this.array.get(ie).equals(x)) ie++;
if (this.isEmpty() || ie < 0 || ie >= this.currentSize)
throw new ElementNotFoundException(x);
E last = this.array.get(--this.currentSize);
this.arraySet(ie, last);
this.percolateUp(ie);
this.percolateDown(ie);
}
/** Try to remove an element without raising any exception */
public void tryremove(E x) {
try {
remove(x);
}
catch (ElementNotFoundException e) {
;
}
}
public boolean isIn(E x) {
return this.array.contains(x);
}
@Override
public E findMin() throws EmptyPriorityQueueException {
if (isEmpty())
throw new EmptyPriorityQueueException();
return this.array.get(0);
}
@Override
public E deleteMin() throws EmptyPriorityQueueException {
E minItem = findMin();
E lastItem = this.array.get(--this.currentSize);
this.arraySet(0, lastItem);
this.percolateDown(0);
return minItem;
}
/**
* Creates a multi-lines string representing a sorted view of this binary heap.
*
* @return a string containing a sorted view this binary heap.
*/
public String toStringSorted() {
return BinaryHeapFormatter.toStringSorted(this, -1);
}
/**
* Creates a multi-lines string representing a sorted view of this binary heap.
*
* @param maxElement Maximum number of elements to display. or {@code -1} to
* display all the elements.
*
* @return a string containing a sorted view this binary heap.
*/
public String toStringSorted(int maxElement) {
return BinaryHeapFormatter.toStringSorted(this, maxElement);
}
/**
* Creates a multi-lines string representing a tree view of this binary heap.
*
* @return a string containing a tree view of this binary heap.
*/
public String toStringTree() {
return BinaryHeapFormatter.toStringTree(this, Integer.MAX_VALUE);
}
/**
* Creates a multi-lines string representing a tree view of this binary heap.
*
* @param maxDepth Maximum depth of the tree to display.
*
* @return a string containing a tree view of this binary heap.
*/
public String toStringTree(int maxDepth) {
return BinaryHeapFormatter.toStringTree(this, maxDepth);
}
@Override
public String toString() {
return BinaryHeapFormatter.toStringTree(this, 8);
}
}

View file

@ -0,0 +1,198 @@
package org.insa.graphs.algorithm.utils;
import java.util.ArrayList;
public class BinaryHeapFormatter {
/**
* This class is used by {@link #toStringTree}, and simply contains three string
* accumulating. This is an immutable class.
*
*/
private static class Context {
// Output text:
public final String acu;
// Margin to get back exactly under the current position:
public final String margin;
// Last margin used for the last child of a node. The markers are different:
public final String lastmargin;
/**
* Creaet a new {@code Context}.
*
* @param acu The accumulated string.
* @param margin The current margin.
* @param lastMargin The last margin used.
*/
public Context(String acu, String margin, String lastMargin) {
this.acu = acu;
this.margin = margin;
this.lastmargin = lastMargin;
}
/**
* Creates a new context by appending newlines to this context.
*
* @param n Number of newlines to append.
*
* @return a new context with {@code n} newlines appended.
*/
public Context appendNewlines(int n) {
if (n <= 0) {
return this;
}
else {
return (new Context(this.acu + "\n" + this.margin, this.margin, this.lastmargin)
.appendNewlines(n - 1));
}
}
/**
* Creates a new context by appending the given string to this context.
*
* @param count Number of spaces to add to the margin, or {@code null} to use
* the length of the string.
* @param text String to append.
*
* @return a new context with {@code text} appended.
*/
public Context appendText(Integer count, String text) {
int cnt = (count == null) ? text.length() : count;
final String spaces = new String(new char[cnt]).replace('\0', ' ');
return new Context(this.acu + text, this.margin + spaces, this.lastmargin + spaces);
}
/**
* Creates a new context by appending a branch to this context.
*
* @param n Number of spaces to add to the margin, or {@code null} to use
* the length of the string.
* @param label Name of the branch.
*
* @return a new context with the branch appended.
*/
public Context appendBranch(Integer count, String label) {
final Context ctxt = this.appendText(count, label);
if (count == null) {
return new Context(ctxt.acu + "_", ctxt.margin + "|", ctxt.margin + " ");
}
else {
return new Context(ctxt.acu, ctxt.margin + "|", ctxt.margin + " ")
.appendNewlines(1);
}
}
}
/*
* Input : ready to write the current node at the current context position.
* Output : the last character of acu is the last character of the current node.
*/
protected static <E extends Comparable<E>> Context toStringLoop(BinaryHeap<E> heap,
Context ctxt, int node, int max_depth) {
if (max_depth < 0) {
return ctxt.appendText(null, "...");
}
else {
E nodeval = heap.array.get(node);
String nodevals = nodeval.toString();
ArrayList<Integer> childs = new ArrayList<Integer>();
// Add childs
int index_left = heap.indexLeft(node);
int index_right = index_left + 1;
if (index_left < heap.size()) {
childs.add(index_left);
}
if (index_right < heap.size()) {
childs.add(index_right);
}
Context ctxt2 = childs.isEmpty() ? ctxt.appendText(null, nodevals)
: ctxt.appendBranch(1, nodevals);
for (int ch = 0; ch < childs.size(); ch++) {
boolean is_last = (ch == childs.size() - 1);
int child = childs.get(ch);
if (is_last) {
Context ctxt3 = new Context(ctxt2.acu, ctxt2.lastmargin, ctxt2.lastmargin);
ctxt2 = new Context(toStringLoop(heap, ctxt3.appendText(null, "___"), child,
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin);
}
else {
ctxt2 = new Context(toStringLoop(heap, ctxt2.appendText(null, "___"), child,
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin).appendNewlines(2);
}
}
return ctxt2;
}
}
/**
* Creates a multi-lines string representing a tree view of the given binary
* heap.
*
* @param heap The binary heap to display.
* @param maxDepth Maximum depth of the tree to display.
*
* @return a string containing a tree view of the given binary heap.
*/
public static <E extends Comparable<E>> String toStringTree(BinaryHeap<E> heap, int maxDepth) {
final Context init_context = new Context(" ", " ", " ");
final Context result = toStringLoop(heap, init_context, 0, maxDepth);
return result.acu;
}
/**
* Creates a multi-lines string representing a sorted view of the given binary
* heap.
*
* @param heap The binary heap to display.
* @param maxElement Maximum number of elements to display. or {@code -1} to
* display all the elements.
*
* @return a string containing a sorted view the given binary heap.
*/
public static <E extends Comparable<E>> String toStringSorted(BinaryHeap<E> heap,
int max_elements) {
String result = "";
final BinaryHeap<E> copy = new BinaryHeap<E>(heap);
final String truncate;
if (max_elements < 0 || max_elements >= heap.size()) {
truncate = "";
}
else {
truncate = ", only " + max_elements + " elements are shown";
}
result += "======== Sorted HEAP (size = " + heap.size() + truncate + ") ========\n\n";
while (!copy.isEmpty() && max_elements-- != 0) {
result += copy.deleteMin() + "\n";
}
result += "\n-------- End of heap --------";
return result;
}
public static void main(String[] args) {
final BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
for (int i = 0; i < 12; i++) {
heap.insert(i);
}
System.out.println(heap.toStringSorted(-1));
System.out.println(heap.toStringTree(6));
}
}

View file

@ -0,0 +1,64 @@
package org.insa.graphs.algorithm.utils;
import java.util.SortedSet;
import java.util.TreeSet;
public class BinarySearchTree<E extends Comparable<E>> implements PriorityQueue<E> {
// Underlying implementation
private final SortedSet<E> sortedSet;
/**
* Create a new empty binary search tree.
*/
public BinarySearchTree() {
this.sortedSet = new TreeSet<>();
}
/**
* Create a copy of the given binary search tree.
*
* @param bst Binary search tree to copy.
*/
public BinarySearchTree(BinarySearchTree<E> bst) {
this.sortedSet = new TreeSet<>(bst.sortedSet);
}
@Override
public boolean isEmpty() {
return sortedSet.isEmpty();
}
@Override
public int size() {
return sortedSet.size();
}
@Override
public void insert(E x) {
sortedSet.add(x);
}
@Override
public void remove(E x) throws ElementNotFoundException {
if (!sortedSet.remove(x)) {
throw new ElementNotFoundException(x);
}
}
@Override
public E findMin() throws EmptyPriorityQueueException {
if (isEmpty()) {
throw new EmptyPriorityQueueException();
}
return sortedSet.first();
}
@Override
public E deleteMin() throws EmptyPriorityQueueException {
E min = findMin();
remove(min);
return min;
}
}

View file

@ -0,0 +1,32 @@
package org.insa.graphs.algorithm.utils;
public class ElementNotFoundException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
// Element not found
private final Object element;
/**
* @param element Element that was not found.
*/
public ElementNotFoundException(Object element) {
this.element = element;
}
/**
* @return The element that was not found.
*/
public Object getElement() {
return this.element;
}
@Override
public String toString() {
return "element not found: " + element;
}
}

View file

@ -0,0 +1,16 @@
package org.insa.graphs.algorithm.utils;
public class EmptyPriorityQueueException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
public EmptyPriorityQueueException() {
}
}

View file

@ -0,0 +1,81 @@
package org.insa.graphs.algorithm.utils;
/**
* Interface representing a basic priority queue.
*
* Implementation should enforce the required complexity of each method.
*
*/
public interface PriorityQueue<E extends Comparable<E>> {
/**
* Check if the priority queue is empty.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return true if the queue is empty, false otherwise.
*/
public boolean isEmpty();
/**
* Get the number of elements in this queue.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return Current size (number of elements) of this queue.
*/
public int size();
/**
* Insert the given element into the queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @param x Item to insert.
*/
public void insert(E x);
/**
* Remove the given element from the priority queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @param x Item to remove.
*/
public void remove(E x) throws ElementNotFoundException;
/**
* Retrieve (but not remove) the smallest item in the queue.
*
* <p>
* <b>Complexity:</b> <i>O(1)</i>
* </p>
*
* @return The smallest item in the queue.
*
* @throws EmptyPriorityQueueException if this queue is empty.
*/
public E findMin() throws EmptyPriorityQueueException;
/**
* Remove and return the smallest item from the priority queue.
*
* <p>
* <b>Complexity:</b> <i>O(log n)</i>
* </p>
*
* @return The smallest item in the queue.
*
* @throws EmptyPriorityQueueException if this queue is empty.
*/
public E deleteMin() throws EmptyPriorityQueueException;
}

View file

@ -0,0 +1,30 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import org.insa.graphs.model.Node;
public interface WeaklyConnectedComponentObserver {
/**
* Notify that the algorithm is entering a new component.
*
* @param curNode Starting node for the component.
*/
public void notifyStartComponent(Node curNode);
/**
* Notify that a new node has been found for the current component.
*
* @param node New node found for the current component.
*/
public void notifyNewNodeInComponent(Node node);
/**
* Notify that the algorithm has computed a new component.
*
* @param nodes List of nodes in the component.
*/
public void notifyEndComponent(ArrayList<Node> nodes);
}

View file

@ -0,0 +1,36 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.io.PrintStream;
import java.util.ArrayList;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentTextObserver implements WeaklyConnectedComponentObserver {
// Number of the current component.
private int numComponent = 1;
// Output stream
PrintStream stream;
public WeaklyConnectedComponentTextObserver(PrintStream stream) {
this.stream = stream;
}
@Override
public void notifyStartComponent(Node curNode) {
stream.print("Entering component #" + numComponent + " from node #" + curNode.getId() + "... ");
}
@Override
public void notifyNewNodeInComponent(Node node) {
}
@Override
public void notifyEndComponent(ArrayList<Node> nodes) {
stream.println(nodes.size() + " nodes found.");
stream.flush();
numComponent += 1;
}
}

View file

@ -0,0 +1,159 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import org.insa.graphs.algorithm.AbstractAlgorithm;
import org.insa.graphs.algorithm.AbstractSolution.Status;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentsAlgorithm
extends AbstractAlgorithm<WeaklyConnectedComponentObserver> {
/**
* @param data Input data for this algorithm.
*/
public WeaklyConnectedComponentsAlgorithm(WeaklyConnectedComponentsData data) {
super(data);
}
@Override
public WeaklyConnectedComponentsSolution run() {
return (WeaklyConnectedComponentsSolution) super.run();
}
@Override
public WeaklyConnectedComponentsData getInputData() {
return (WeaklyConnectedComponentsData) super.getInputData();
}
/**
* Notify all observers that the algorithm is entering a new component.
*
* @param curNode Starting node for the component.
*/
protected void notifyStartComponent(Node curNode) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyStartComponent(curNode);
}
}
/**
* Notify all observers that a new node has been found for the current
* component.
*
* @param node New node found for the current component.
*/
protected void notifyNewNodeInComponent(Node node) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyNewNodeInComponent(node);
}
}
/**
* Notify all observers that the algorithm has computed a new component.
*
* @param nodes List of nodes in the component.
*/
protected void notifyEndComponent(ArrayList<Node> nodes) {
for (WeaklyConnectedComponentObserver obs: getObservers()) {
obs.notifyEndComponent(nodes);
}
}
/**
* @return An adjacency list for the undirected graph equivalent to the stored
* graph.
*/
protected ArrayList<HashSet<Integer>> createUndirectedGraph() {
int nNodes = getInputData().getGraph().size();
ArrayList<HashSet<Integer>> res = new ArrayList<HashSet<Integer>>(nNodes);
for (int i = 0; i < nNodes; ++i) {
res.add(new HashSet<Integer>());
}
for (Node node: getInputData().getGraph().getNodes()) {
for (Arc arc: node.getSuccessors()) {
res.get(node.getId()).add(arc.getDestination().getId());
if (arc.getRoadInformation().isOneWay()) {
res.get(arc.getDestination().getId()).add(node.getId());
}
}
}
return res;
}
/**
* Apply a breadth first search algorithm on the given undirected graph
* (adjacency list), starting at node cur, and marking nodes in marked.
*
* @param marked
* @param cur
*
* @return
*/
protected ArrayList<Node> bfs(ArrayList<HashSet<Integer>> ugraph, boolean[] marked, int cur) {
Graph graph = getInputData().getGraph();
ArrayList<Node> component = new ArrayList<Node>();
// Using a queue because we are doing a BFS
Queue<Integer> queue = new LinkedList<Integer>();
// Notify observers about the current component.
notifyStartComponent(graph.get(cur));
// Add original node and loop until the queue is empty.
queue.add(cur);
marked[cur] = true;
while (!queue.isEmpty()) {
Node node = graph.get(queue.remove());
component.add(node);
// Notify observers
notifyNewNodeInComponent(node);
for (Integer destId: ugraph.get(node.getId())) {
Node dest = graph.get(destId);
if (!marked[dest.getId()]) {
queue.add(destId);
marked[destId] = true;
}
}
}
notifyEndComponent(component);
return component;
}
@Override
protected WeaklyConnectedComponentsSolution doRun() {
Graph graph = getInputData().getGraph();
ArrayList<HashSet<Integer>> ugraph = createUndirectedGraph();
boolean[] marked = new boolean[graph.size()];
Arrays.fill(marked, false);
ArrayList<ArrayList<Node>> components = new ArrayList<ArrayList<Node>>();
// perform algorithm
int cur = 0;
while (cur < marked.length) {
// Apply BFS
components.add(this.bfs(ugraph, marked, cur));
// Find next non-marked
for (; cur < marked.length && marked[cur]; ++cur)
;
}
return new WeaklyConnectedComponentsSolution(getInputData(), Status.OPTIMAL, components);
}
}

View file

@ -0,0 +1,20 @@
package org.insa.graphs.algorithm.weakconnectivity;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.model.Graph;
public class WeaklyConnectedComponentsData extends AbstractInputData {
/**
* @param graph Graph for which components should be retrieved.
*/
public WeaklyConnectedComponentsData(Graph graph) {
super(graph, null);
}
@Override
public String toString() {
return "Weakly-connected components from #0.";
}
}

View file

@ -0,0 +1,57 @@
package org.insa.graphs.algorithm.weakconnectivity;
import java.util.ArrayList;
import org.insa.graphs.algorithm.AbstractSolution;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentsSolution extends AbstractSolution {
// Components
private ArrayList<ArrayList<Node>> components;
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data) {
super(data);
}
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data, Status status,
ArrayList<ArrayList<Node>> components) {
super(data, status);
this.components = components;
}
@Override
public WeaklyConnectedComponentsData getInputData() {
return (WeaklyConnectedComponentsData) super.getInputData();
}
/**
* @return Components of the solution, if any.
*/
public ArrayList<ArrayList<Node>> getComponents() {
return components;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
int nIsolated = 0;
int nGt10 = 0;
for (ArrayList<Node> component: components) {
if (component.size() == 1) {
nIsolated += 1;
}
else if (component.size() > 10) {
nGt10 += 1;
}
}
return "Found " + components.size() + " components (" + nGt10 + " with more than 10 nodes, "
+ nIsolated + " isolated nodes) in " + getSolvingTime().getSeconds() + " seconds.";
}
}

View file

@ -0,0 +1,15 @@
package org.insa.graphs.algorithm.utils;
import java.io.IOException;
import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
public class AStarTest extends PccAlgorithmTest {
public AStarTest() throws IOException {}
@Override
protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
return new AStarAlgorithm(data);
}
}

View file

@ -0,0 +1,17 @@
package org.insa.graphs.algorithm.utils;
import java.io.IOException;
import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
// Note : Faire l'ensemble des tests avec Bellman-Ford est long, l'algorithme étant peu efficace.
public class BellmanFordTest extends PccAlgorithmTest {
public BellmanFordTest() throws IOException {}
@Override
protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
return new BellmanFordAlgorithm(data);
}
}

View file

@ -0,0 +1,15 @@
package org.insa.graphs.algorithm.utils;
public class BinaryHeapTest extends PriorityQueueTest {
@Override
public PriorityQueue<MutableInteger> createQueue() {
return new BinaryHeap<>();
}
@Override
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
return new BinaryHeap<>((BinaryHeap<MutableInteger>) queue);
}
}

View file

@ -0,0 +1,15 @@
package org.insa.graphs.algorithm.utils;
public class BinarySearchTreeTest extends PriorityQueueTest {
@Override
public PriorityQueue<MutableInteger> createQueue() {
return new BinarySearchTree<>();
}
@Override
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
return new BinarySearchTree<>((BinarySearchTree<MutableInteger>) queue);
}
}

View file

@ -0,0 +1,16 @@
package org.insa.graphs.algorithm.utils;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
import java.io.IOException;
import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
public class DijkstraTest extends PccAlgorithmTest {
public DijkstraTest() throws IOException {}
@Override
protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
return new DijkstraAlgorithm(data);
}
}

View file

@ -0,0 +1,196 @@
package org.insa.graphs.algorithm.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.algorithm.ArcInspectorFactory;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.algorithm.AbstractSolution.*;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.io.BinaryGraphReader;
import org.insa.graphs.model.io.BinaryPathReader;
import org.insa.graphs.model.io.GraphReader;
import org.insa.graphs.model.io.PathReader;
public abstract class PccAlgorithmTest {
// Oracle (généré à partir du Bellman Ford)
String hauteGaronneFileName = "./Maps/haute-garonne.mapgr";
String lfboInsaPathName = "./Paths/LFBO_INSA_correct.path";
Graph hauteGaronne;
Path LfboInsa;
// Outils
PathReader pathReader;
ArcInspector inspector;
// Solution trouvée par l'algorithme donné pour le chemin LFBO - INSA
ShortestPathSolution solutionTrouveeLfboInsa;
class Scenario {
Graph graph;
Mode nc;
Node origine;
Node destination;
public Scenario(Graph graph, Mode nc, Node origine, Node destination) {
this.graph = graph;
this.origine = origine;
this.destination = destination;
this.nc = nc;
}
}
protected abstract ShortestPathAlgorithm createAlgorithm(ShortestPathData data);
/**
* Fonction utilitaire, test un scenario donné
* @param scenario
* @return
*/
public ShortestPathSolution testScenario(Scenario scenario) {
// Préparation des données
ShortestPathData data = new ShortestPathData(scenario.graph, scenario.origine, scenario.destination, inspector);
// Préparation de l'algorithme
ShortestPathAlgorithm algo = createAlgorithm(data);
// Lancement de l'algorithme
return algo.run();
}
/**
* Fonction utilitaire, fait calculer le chemin LFBO - INSA à l'algorithme donné
* @return
*/
public ShortestPathSolution doLFBO_INSA() {
// Récupération du chemin
Path correctPath = LfboInsa;
// Création du scénario
Scenario ScnLfboInsa = new Scenario(hauteGaronne, Mode.LENGTH, correctPath.getOrigin(), correctPath.getDestination());
return testScenario(ScnLfboInsa);
}
/**
* Constructeur, lit les fichiers liés à l'oracle et calcul LFBO - INSA
* @throws IOException
*/
public PccAlgorithmTest() throws IOException {
inspector = ArcInspectorFactory.getAllFilters().get(0);
GraphReader reader = new BinaryGraphReader(new DataInputStream(new BufferedInputStream(new FileInputStream(hauteGaronneFileName))));
hauteGaronne = reader.read();
pathReader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(new FileInputStream(lfboInsaPathName))));
LfboInsa = pathReader.readPath(hauteGaronne);
solutionTrouveeLfboInsa = doLFBO_INSA();
}
@Test
public void testLFBO_INSA() {
Path pathTrouve = solutionTrouveeLfboInsa.getPath();
// Test de la solution trouvée
assertTrue(solutionTrouveeLfboInsa.isFeasible());
assertEquals(solutionTrouveeLfboInsa.getStatus(), Status.OPTIMAL);
assertEquals(pathTrouve.getOrigin(), LfboInsa.getOrigin());
assertEquals(pathTrouve.getDestination(), LfboInsa.getDestination());
assertEquals(pathTrouve.getLength(), LfboInsa.getLength(), 0.01);
}
@Test
public void testOneNodePath() {
Node onlyOne = LfboInsa.getOrigin();
// Création du scénario
Scenario ScnOneNode = new Scenario(hauteGaronne, Mode.LENGTH, onlyOne, onlyOne);
// Test du scénario
ShortestPathSolution solutionTrouvee = testScenario(ScnOneNode);
// Test de la solution trouvée
assertTrue(solutionTrouvee.isFeasible());
assertEquals(solutionTrouvee.getStatus(), Status.OPTIMAL);
assertEquals(solutionTrouvee.getPath().getLength(), 0.0, 0.01);
}
/**
* Il n'y a pas beaucoup de solution pour vérifier l'optimalité d'un chemin sans oracle.
* On peut cependant vérifier que les sous-chemin sont également optimaux.
* Note : La meilleure solution reste de démontrer l'optimalité du A* et de prendre
* une heurestique admissible.
*/
@Test
public void testSousChemin() {
Path pathTrouve = solutionTrouveeLfboInsa.getPath();
Node n = pathTrouve.getOrigin();
Node m = pathTrouve.getArcs().get(30).getDestination();
Scenario ScnSousChemin = new Scenario(pathTrouve.getGraph(), Mode.LENGTH, n, m);
ShortestPathSolution sol = testScenario(ScnSousChemin);
List<Arc> arcsCheminOriginal = pathTrouve.getArcs();
List<Arc> arcsSousChemin = sol.getPath().getArcs();
for (int i = 0; i < 30; ++i) {
assertTrue(arcsCheminOriginal.get(i) == arcsSousChemin.get(i));
}
}
/**
* Ce test permet de vérifier que la distance à vol d'oiseau est toujours plus faible
* que la longueur du chemin retourné par l'algorithme
*/
@Test
public void testVolDOiseau() {
Path pathTrouve = solutionTrouveeLfboInsa.getPath();
double dx = LfboInsa.getOrigin().getPoint().getLongitude() - LfboInsa.getDestination().getPoint().getLongitude();
double dy = LfboInsa.getOrigin().getPoint().getLatitude() - LfboInsa.getDestination().getPoint().getLatitude();
assertTrue(pathTrouve.getLength() >= Math.sqrt(dx * dx + dy * dy));
}
@Test
public void testInegaliteTriangulaire() {
// Récupération d'un point légèrement -axé
Node pointDePassage = LfboInsa.getOrigin().getSuccessors().get(0).getDestination().getSuccessors().get(0).getDestination().getSuccessors().get(0).getDestination();
// Récupération de la solution directe
Path pathTrouve = solutionTrouveeLfboInsa.getPath();
Scenario Scn1 = new Scenario(LfboInsa.getGraph(), Mode.LENGTH, pathTrouve.getOrigin(), pointDePassage);
Scenario Scn2 = new Scenario(LfboInsa.getGraph(), Mode.LENGTH, pointDePassage, pathTrouve.getDestination());
ShortestPathSolution sol1 = testScenario(Scn1);
ShortestPathSolution sol2 = testScenario(Scn2);
// Vérification de l'inégalité triangulaire
assertTrue(sol1.getPath().getLength() + sol2.getPath().getLength() >= pathTrouve.getLength());
}
}

View file

@ -0,0 +1,320 @@
package org.insa.graphs.algorithm.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public abstract class PriorityQueueTest {
/**
* Needs to be implemented by child class to actually provide priority queue
* implementation.
*
* @return A new instance of a PriorityQueue implementation.
*/
public abstract PriorityQueue<MutableInteger> createQueue();
/**
* Needs to be implemented by child class to actually provide priority queue
* implementation.
*
* @param queue Queue to copy.
*
* @return Copy of the given queue.
*/
public abstract PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue);
protected static class MutableInteger implements Comparable<MutableInteger> {
// Actual value
private int value;
public MutableInteger(int value) {
this.value = value;
}
/**
* @return The integer value stored inside this MutableInteger.
*/
public int get() {
return this.value;
}
/**
* Update the integer value stored inside this MutableInteger.
*
* @param value New value to set.
*/
public void set(int value) {
this.value = value;
}
@Override
public int compareTo(MutableInteger other) {
return Integer.compare(this.value, other.value);
}
@Override
public String toString() {
return Integer.toString(get());
}
};
protected static class TestParameters<E extends Comparable<E>> {
// Data to insert
public final E[] data;
public final int[] deleteOrder;
public TestParameters(E[] data, int[] deleteOrder) {
this.data = data;
this.deleteOrder = deleteOrder;
}
};
/**
* Set of parameters.
*
*/
@Parameters
public static Collection<Object> data() {
Collection<Object> objects = new ArrayList<>();
// Empty queue
objects.add(new TestParameters<>(new MutableInteger[0], new int[0]));
// Queue with 50 elements from 0 to 49, inserted in order and deleted in order.
objects.add(new TestParameters<>(
IntStream.range(0, 50).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
IntStream.range(0, 50).toArray()));
// Queue with 20 elements from 0 to 19, inserted in order, deleted in the given
// order.
objects.add(new TestParameters<>(
IntStream.range(0, 20).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, 6, 1,
11 }));
// Queue with 7 elements.
objects.add(
new TestParameters<>(
Arrays.stream(new int[] { 8, 1, 6, 3, 4, 5, 9 })
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
new int[] { 6, 5, 0, 1, 4, 2, 3 }));
// Queue with 7 elements.
objects.add(
new TestParameters<>(
Arrays.stream(new int[] { 1, 7, 4, 8, 9, 6, 5 })
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
new int[] { 2, 0, 1, 3, 4, 5, 6 }));
// Queue with 13 elements.
objects.add(new TestParameters<>(
Arrays.stream(new int[] { 1, 7, 2, 8, 9, 3, 4, 10, 11, 12, 13, 5, 6 })
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
new int[] { 3, 4, 0, 2, 5, 6, 1, 7, 8, 9, 10, 11, 12 }));
return objects;
}
@Parameter
public TestParameters<MutableInteger> parameters;
// Actual queue.
private PriorityQueue<MutableInteger> queue;
@Before
public void init() {
// Create the range queue
this.queue = createQueue();
for (MutableInteger v: parameters.data) {
this.queue.insert(v);
}
}
@Test
public void testIsEmpty() {
assertEquals(parameters.data.length == 0, this.queue.isEmpty());
}
@Test
public void testSize() {
assertEquals(parameters.data.length, this.queue.size());
}
@Test
public void testInsert() {
PriorityQueue<MutableInteger> queue = createQueue();
int size = 0;
for (MutableInteger x: parameters.data) {
queue.insert(x);
assertEquals(++size, queue.size());
}
assertEquals(parameters.data.length, queue.size());
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
Arrays.sort(range);
for (MutableInteger mi: range) {
assertEquals(mi.get(), queue.deleteMin().value);
assertEquals(--size, queue.size());
}
}
@Test(expected = EmptyPriorityQueueException.class)
public void testEmptyFindMin() {
Assume.assumeTrue(queue.isEmpty());
queue.findMin();
}
@Test
public void testFindMin() {
Assume.assumeFalse(queue.isEmpty());
assertEquals(Collections.min(Arrays.asList(parameters.data)).get(), queue.findMin().get());
}
@Test(expected = EmptyPriorityQueueException.class)
public void testEmptyDeleteMin() {
Assume.assumeTrue(queue.isEmpty());
queue.deleteMin();
}
@Test
public void testDeleteMin() {
int size = parameters.data.length;
assertEquals(size, queue.size());
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
Arrays.sort(range);
for (MutableInteger x: range) {
assertEquals(x, queue.deleteMin());
size -= 1;
assertEquals(size, queue.size());
}
assertEquals(0, queue.size());
assertTrue(queue.isEmpty());
}
@Test(expected = ElementNotFoundException.class)
public void testRemoveEmpty() {
Assume.assumeTrue(queue.isEmpty());
queue.remove(new MutableInteger(0));
}
@Test
public void testRemoveNotFound() {
Assume.assumeFalse(queue.isEmpty());
List<MutableInteger> data = Arrays.asList(parameters.data);
MutableInteger min = new MutableInteger(Collections.min(data).get() - 1),
max = new MutableInteger(Collections.max(data).get() + 1);
try {
queue.remove(min);
fail("Expected exception " + ElementNotFoundException.class.getName());
}
catch (ElementNotFoundException e) {
assertEquals(min, e.getElement());
}
try {
queue.remove(max);
fail("Expected exception " + ElementNotFoundException.class.getName());
}
catch (ElementNotFoundException e) {
assertEquals(max, e.getElement());
}
}
@Test
public void testDeleteThenRemove() {
Assume.assumeFalse(queue.isEmpty());
while (!queue.isEmpty()) {
MutableInteger min = queue.deleteMin();
try {
queue.remove(min);
fail("Expected exception " + ElementNotFoundException.class.getName());
}
catch (ElementNotFoundException e) {
assertEquals(min, e.getElement());
}
}
}
@Test
public void testRemoveTwice() {
Assume.assumeFalse(queue.isEmpty());
for (MutableInteger data: parameters.data) {
PriorityQueue<MutableInteger> copyQueue = this.createQueue(this.queue);
copyQueue.remove(data);
try {
copyQueue.remove(data);
fail("Expected exception " + ElementNotFoundException.class.getName());
}
catch (ElementNotFoundException e) {
assertEquals(data, e.getElement());
}
}
}
@Test
public void testRemove() {
int size1 = queue.size();
for (int i = 0; i < parameters.deleteOrder.length; ++i) {
// Remove from structure
queue.remove(parameters.data[parameters.deleteOrder[i]]);
// Copy the remaining elements
PriorityQueue<MutableInteger> copyTree = createQueue(queue);
// Retrieve all remaining elements in both structures
ArrayList<MutableInteger> remains_in = new ArrayList<>(),
remains_cp = new ArrayList<>();
for (int j = i + 1; j < parameters.deleteOrder.length; ++j) {
remains_in.add(parameters.data[parameters.deleteOrder[j]]);
remains_cp.add(copyTree.deleteMin());
}
Collections.sort(remains_in);
// Check that the copy is now empty, and that both list contains all
// elements.
assertTrue(copyTree.isEmpty());
assertEquals(remains_in, remains_cp);
// Check that the size of the original tree is correct.
assertEquals(--size1, queue.size());
}
assertTrue(queue.isEmpty());
}
@Test
public void testRemoveThenAdd() {
Assume.assumeFalse(queue.isEmpty());
int min = Collections.min(Arrays.asList(parameters.data)).get();
for (MutableInteger mi: parameters.data) {
queue.remove(mi);
assertEquals(parameters.data.length - 1, queue.size());
mi.set(--min);
queue.insert(mi);
assertEquals(parameters.data.length, queue.size());
assertEquals(min, queue.findMin().get());
}
}
}

87
be-graphes-gui/pom.xml Normal file
View file

@ -0,0 +1,87 @@
<?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>
<properties>
<mapsforge.version>0.13.0</mapsforge.version>
</properties>
<artifactId>be-graphes-gui</artifactId>
<name>be-graphes-gui</name>
<!-- Repository for Salamander (dependency of Mapsforge). -->
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.insa.graphs</groupId>
<artifactId>be-graphes-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.insa.graphs</groupId>
<artifactId>be-graphes-algos</artifactId>
<version>${project.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-core -->
<dependency>
<groupId>net.sf.kxml</groupId>
<artifactId>kxml2</artifactId>
<version>2.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
<dependency>
<groupId>org.mapsforge</groupId>
<artifactId>mapsforge-themes</artifactId>
<version>${mapsforge.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map -->
<dependency>
<groupId>org.mapsforge</groupId>
<artifactId>mapsforge-map</artifactId>
<version>${mapsforge.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-awt -->
<dependency>
<groupId>org.mapsforge</groupId>
<artifactId>mapsforge-map-awt</artifactId>
<version>${mapsforge.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
<dependency>
<groupId>org.mapsforge</groupId>
<artifactId>mapsforge-themes</artifactId>
<version>${mapsforge.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
<dependency>
<groupId>org.mapsforge</groupId>
<artifactId>mapsforge-map-reader</artifactId>
<version>${mapsforge.version}</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,381 @@
package org.insa.graphs.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import org.insa.graphs.algorithm.AbstractAlgorithm;
import org.insa.graphs.algorithm.AlgorithmFactory;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.algorithm.ArcInspectorFactory;
import org.insa.graphs.gui.NodesInputPanel.InputChangedEvent;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.utils.ColorUtils;
import org.insa.graphs.model.Node;
public class AlgorithmPanel extends JPanel implements DrawingChangeListener {
/**
*
*/
private static final long serialVersionUID = 1L;
public class StartActionEvent extends ActionEvent {
/**
*
*/
private static final long serialVersionUID = 4090710269781229078L;
protected static final String START_EVENT_COMMAND = "allInputFilled";
protected static final int START_EVENT_ID = 0x1;
private final List<Node> nodes;
private final Class<? extends AbstractAlgorithm<?>> algoClass;
private final ArcInspector arcFilter;
private final boolean graphicVisualization;
private final boolean textualVisualization;
public StartActionEvent(Class<? extends AbstractAlgorithm<?>> algoClass, List<Node> nodes,
ArcInspector arcFilter, boolean graphicVisualization,
boolean textualVisualization) {
super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND);
this.nodes = nodes;
this.algoClass = algoClass;
this.graphicVisualization = graphicVisualization;
this.textualVisualization = textualVisualization;
this.arcFilter = arcFilter;
}
/**
* @return Nodes associated with this event.
*/
public List<Node> getNodes() {
return this.nodes;
}
/**
* @return Arc filter associated with this event.
*/
public ArcInspector getArcFilter() {
return this.arcFilter;
}
/**
* @return Algorithm class associated with this event.
*/
public Class<? extends AbstractAlgorithm<?>> getAlgorithmClass() {
return this.algoClass;
}
/**
* @return true if graphic visualization is enabled.
*/
public boolean isGraphicVisualizationEnabled() {
return this.graphicVisualization;
}
/**
* @return true if textual visualization is enabled.
*/
public boolean isTextualVisualizationEnabled() {
return this.textualVisualization;
}
};
// Input panels for node.
protected NodesInputPanel nodesInputPanel;
// Solution
protected SolutionPanel solutionPanel;
// Component that can be enabled/disabled.
private ArrayList<JComponent> components = new ArrayList<>();
// Graphic / Text checkbox observer
private final JCheckBox graphicObserverCheckbox, textualObserverCheckbox;
private JButton startAlgoButton;
// Start listeners
List<ActionListener> startActionListeners = new ArrayList<>();
/**
* Create a new AlgorithmPanel with the given parameters.
*
* @param parent Parent component for this panel. Only use for centering
* dialogs.
* @param baseAlgorithm Base algorithm for this algorithm panel.
* @param title Title of the panel.
* @param nodeNames Names of the input nodes.
* @param enableArcFilterSelection <code>true</code> to enable
* {@link ArcInspector} selection.
*
* @see ArcInspectorFactory
*/
public AlgorithmPanel(Component parent, Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
String title, String[] nodeNames, boolean enableArcFilterSelection) {
super();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBorder(new EmptyBorder(15, 15, 15, 15));
// Set title.
add(createTitleLabel(title));
add(Box.createVerticalStrut(8));
// Add algorithm selection
JComboBox<String> algoSelect = createAlgoritmSelectComboBox(baseAlgorithm);
if (algoSelect.getItemCount() > 1) {
add(algoSelect);
components.add(algoSelect);
}
// Add inputs for node.
this.nodesInputPanel = createNodesInputPanel(nodeNames);
add(this.nodesInputPanel);
components.add(this.nodesInputPanel);
JComboBox<ArcInspector> arcFilterSelect = new JComboBox<>(
ArcInspectorFactory.getAllFilters().toArray(new ArcInspector[0]));
arcFilterSelect.setBackground(Color.WHITE);
// Add mode selection
JPanel modeAndObserverPanel = new JPanel();
modeAndObserverPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
modeAndObserverPanel.setLayout(new GridBagLayout());
graphicObserverCheckbox = new JCheckBox("Graphic");
graphicObserverCheckbox.setSelected(true);
textualObserverCheckbox = new JCheckBox("Textual");
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridy = 2;
c.gridx = 0;
c.weightx = 0;
modeAndObserverPanel.add(new JLabel("Visualization: "), c);
c.gridx = 1;
c.weightx = 1;
modeAndObserverPanel.add(graphicObserverCheckbox, c);
c.gridx = 2;
c.weightx = 1;
modeAndObserverPanel.add(textualObserverCheckbox, c);
if (enableArcFilterSelection) {
c.gridy = 1;
c.gridx = 0;
c.weightx = 0;
modeAndObserverPanel.add(new JLabel("Mode: "), c);
c.gridx = 1;
c.gridwidth = 2;
c.weightx = 1;
modeAndObserverPanel.add(arcFilterSelect, c);
}
components.add(arcFilterSelect);
components.add(textualObserverCheckbox);
add(modeAndObserverPanel);
solutionPanel = new SolutionPanel(parent);
solutionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
solutionPanel.setVisible(false);
add(Box.createVerticalStrut(10));
add(solutionPanel);
// Bottom panel
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
startAlgoButton = new JButton("Start");
startAlgoButton.setEnabled(false);
startAlgoButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (ActionListener lis: startActionListeners) {
lis.actionPerformed(new StartActionEvent(
AlgorithmFactory.getAlgorithmClass(baseAlgorithm,
(String) algoSelect.getSelectedItem()),
nodesInputPanel.getNodeForInputs(),
(ArcInspector) arcFilterSelect.getSelectedItem(),
graphicObserverCheckbox.isSelected(),
textualObserverCheckbox.isSelected()));
}
}
});
JButton hideButton = new JButton("Hide");
hideButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
nodesInputPanel.setEnabled(false);
setVisible(false);
}
});
bottomPanel.add(startAlgoButton);
bottomPanel.add(Box.createHorizontalGlue());
bottomPanel.add(hideButton);
components.add(hideButton);
bottomPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
add(Box.createVerticalStrut(8));
add(bottomPanel);
nodesInputPanel.addInputChangedListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
InputChangedEvent evt = (InputChangedEvent) e;
startAlgoButton.setEnabled(allNotNull(evt.getNodes()));
}
});
addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
setEnabled(true);
nodesInputPanel.setVisible(true);
}
@Override
public void componentHidden(ComponentEvent e) {
setEnabled(false);
nodesInputPanel.setVisible(false);
}
});
setEnabled(false);
}
/**
* Create the title JLabel for this panel.
*
* @param title Title for the label.
*
* @return A new JLabel containing the given title with proper font.
*/
protected JLabel createTitleLabel(String title) {
JLabel titleLabel = new JLabel(title);
titleLabel.setBackground(Color.RED);
titleLabel.setHorizontalAlignment(JLabel.LEFT);
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
Font font = titleLabel.getFont();
font = font.deriveFont(Font.BOLD, 18);
titleLabel.setFont(font);
return titleLabel;
}
/**
* Create the combo box for the algorithm selection.
*
* @param baseAlgorithm Base algorithm for which the select box should be
* created.
*
* @return A new JComboBox containing algorithms for the given base algorithm.
*
* @see AlgorithmFactory
*/
protected JComboBox<String> createAlgoritmSelectComboBox(
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
JComboBox<String> algoSelect = new JComboBox<>(
AlgorithmFactory.getAlgorithmNames(baseAlgorithm).toArray(new String[0]));
algoSelect.setBackground(Color.WHITE);
algoSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
return algoSelect;
}
/**
* Create a node input panel with the given node input names.
*
* @param nodeNames Field names for the inputs to create.
*
* @return A new NodesInputPanel containing inputs for the given names.
*/
protected NodesInputPanel createNodesInputPanel(String[] nodeNames) {
NodesInputPanel panel = new NodesInputPanel();
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
for (int i = 0; i < nodeNames.length; ++i) {
panel.addTextField(nodeNames[i] + ": ", ColorUtils.getColor(i));
}
panel.setEnabled(false);
return panel;
}
/**
* Check if the given list of nodes does not contain any <code>null</code>
* value.
*
* @param nodes List of {@link Node} to check.
*
* @return <code>true</code> if the list does not contain any <code>null</code>
* value, <code>false</code> otherwise.
*/
protected boolean allNotNull(List<Node> nodes) {
boolean allNotNull = true;
for (Node node: nodes) {
allNotNull = allNotNull && node != null;
}
return allNotNull;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
nodesInputPanel.setEnabled(enabled);
solutionPanel.setEnabled(enabled);
for (JComponent component: components) {
component.setEnabled(enabled);
}
graphicObserverCheckbox.setEnabled(enabled);
enabled = enabled && allNotNull(this.nodesInputPanel.getNodeForInputs());
startAlgoButton.setEnabled(enabled);
}
/**
* Add a new start action listener to this class.
*
* @param listener Listener to add.
*/
public void addStartActionListener(ActionListener listener) {
this.startActionListeners.add(listener);
}
@Override
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
}
@Override
public void onRedrawRequest() {
}
}

View file

@ -0,0 +1,56 @@
package org.insa.graphs.gui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JOptionPane;
public class BlockingActionFactory {
// List of running actions.
private ArrayList<RunningAction> actions = new ArrayList<>();
// Parent component.
private Component parentComponent;
public BlockingActionFactory(Component parentComponent) {
this.parentComponent = parentComponent;
}
public void addAction(RunningAction action) {
actions.add(action);
}
public ActionListener createBlockingAction(ActionListener listener) {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean accepted = true;
// Check if actions...
for (int i = 0; i < actions.size() && accepted; ++i) {
RunningAction action = actions.get(i);
// If action is running, ask user...
if (action.isRunning()) {
if (JOptionPane.showConfirmDialog(parentComponent, "Action {"
+ action.getInformation()
+ "} is running, do you want to stop it?") == JOptionPane.OK_OPTION) {
action.interrupt();
}
else {
accepted = false;
}
}
}
// If action is accepted, run it...
if (accepted) {
listener.actionPerformed(e);
}
}
};
}
}

View file

@ -0,0 +1,24 @@
package org.insa.graphs.gui;
import org.insa.graphs.gui.drawing.Drawing;
public interface DrawingChangeListener {
/**
* Event fired when a new drawing is loaded.
*
* @param oldDrawing Old drawing, may be null if no drawing exits prior to this
* one.
* @param newDrawing New drawing.
*/
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing);
/**
* Event fired when a redraw request is emitted - This is typically emitted
* after a onDrawingLoaded event, but not always, and request that elements are
* drawn again on the new drawing.
*
*/
public void onRedrawRequest();
}

View file

@ -0,0 +1,14 @@
package org.insa.graphs.gui;
import org.insa.graphs.model.Graph;
public interface GraphChangeListener {
/**
* Event fire when a new graph has been loaded.
*
* @param graph The new graph.
*/
public void newGraphLoaded(Graph graph);
}

View file

@ -0,0 +1,123 @@
package org.insa.graphs.gui;
import java.awt.Component;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.border.EmptyBorder;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.RoadInformation;
import org.insa.graphs.model.io.GraphReaderObserver;
/**
* One-time use GraphReaderObserver that display progress in three different
* JProgressBar.
*
* @author Mikael
*
*/
public class GraphReaderProgressBar extends JDialog implements GraphReaderObserver {
/**
*
*/
private static final long serialVersionUID = -1;
// Index...
private static final int NODE = 0, DESC = 1, ARC = 2;
// Progress bar
private final JProgressBar[] progressBars = new JProgressBar[3];
// Current element read, and modulo.
private int[] counters = new int[]{ 0, 0, 0 };
private int[] modulos = new int[3];
public GraphReaderProgressBar(JFrame owner) {
super(owner);
this.setVisible(false);
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final String[] infos = { "nodes", "road informations", "arcs" };
JPanel pane = new JPanel();
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
pane.setBorder(new EmptyBorder(15, 15, 15, 15));
pane.add(Box.createVerticalGlue());
for (int i = 0; i < 3; ++i) {
JLabel label = new JLabel("Reading " + infos[i] + "... ");
label.setAlignmentX(Component.LEFT_ALIGNMENT);
progressBars[i] = new JProgressBar();
progressBars[i].setAlignmentX(Component.LEFT_ALIGNMENT);
pane.add(label);
pane.add(progressBars[i]);
}
pane.add(Box.createVerticalGlue());
setContentPane(pane);
pack();
}
@Override
public void notifyStartReading(String mapId) {
setTitle("Reading graph " + mapId + "... ");
setVisible(true);
}
@Override
public void notifyEndReading() {
setVisible(false);
dispose();
}
protected void initProgressBar(int index, int max) {
progressBars[index].setMaximum(max);
modulos[index] = Math.max(max / 100, 1);
}
protected void incCounter(int index) {
counters[index] += 1;
if (counters[index] % modulos[index] == 0) {
progressBars[index].setValue(counters[index]);
}
}
@Override
public void notifyStartReadingNodes(int nNodes) {
initProgressBar(NODE, nNodes);
}
@Override
public void notifyNewNodeRead(Node node) {
incCounter(NODE);
}
@Override
public void notifyStartReadingDescriptors(int nDesc) {
initProgressBar(DESC, nDesc);
}
@Override
public void notifyNewDescriptorRead(RoadInformation desc) {
incCounter(DESC);
}
@Override
public void notifyStartReadingArcs(int nArcs) {
initProgressBar(ARC, nArcs);
}
@Override
public void notifyNewArcRead(Arc arc) {
incCounter(ARC);
}
}

View file

@ -0,0 +1,862 @@
package org.insa.graphs.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import org.insa.graphs.algorithm.AbstractSolution;
import org.insa.graphs.algorithm.AlgorithmFactory;
import org.insa.graphs.algorithm.carpooling.CarPoolingAlgorithm;
import org.insa.graphs.algorithm.packageswitch.PackageSwitchAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
import org.insa.graphs.algorithm.shortestpath.ShortestPathTextObserver;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentTextObserver;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsData;
import org.insa.graphs.gui.AlgorithmPanel.StartActionEvent;
import org.insa.graphs.gui.drawing.BasicGraphPalette;
import org.insa.graphs.gui.drawing.BlackAndWhiteGraphPalette;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.GraphPalette;
import org.insa.graphs.gui.drawing.components.BasicDrawing;
import org.insa.graphs.gui.drawing.components.MapViewDrawing;
import org.insa.graphs.gui.observers.ShortestPathGraphicObserver;
import org.insa.graphs.gui.observers.WeaklyConnectedComponentGraphicObserver;
import org.insa.graphs.gui.utils.FileUtils;
import org.insa.graphs.gui.utils.FileUtils.FolderType;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.io.BinaryGraphReader;
import org.insa.graphs.model.io.BinaryPathReader;
import org.insa.graphs.model.io.GraphReader;
import org.insa.graphs.model.io.MapMismatchException;
public class MainWindow extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
private static final String WINDOW_TITLE = "BE Graphes INSA";
/**
*
*/
private static final int THREAD_TIMER_DELAY = 1000; // in milliseconds
// Current graph.
protected Graph graph;
// Path to the last opened graph file.
private String graphFilePath;
// Drawing and click adapter.
protected Drawing drawing;
private final MapViewDrawing mapViewDrawing;
private final BasicDrawing basicDrawing;
private final GraphPalette basicPalette, blackAndWhitePalette;
private GraphPalette currentPalette;
// Main panel.
private final JSplitPane mainPanel;
// Algorithm panels
private final List<AlgorithmPanel> algoPanels = new ArrayList<>();
private final AlgorithmPanel wccPanel, spPanel, cpPanel, psPanel;
// Path panel
private final PathsPanel pathPanel;
// List of items that cannot be used without a graph
private final ArrayList<JMenuItem> graphLockItems = new ArrayList<JMenuItem>();
// Label containing the map ID of the current graph.
private JLabel graphInfoPanel;
// Thread information
private Timer threadTimer;
private JPanel threadPanel;
// Log stream and print stream
private StreamCapturer logStream;
private PrintStream printStream;
// Current running thread
private ThreadWrapper currentThread;
// Factory
private BlockingActionFactory baf;
// Observers
private List<DrawingChangeListener> drawingChangeListeners = new ArrayList<>();
private List<GraphChangeListener> graphChangeListeneres = new ArrayList<>();
public MainWindow() {
super(WINDOW_TITLE);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setLayout(new BorderLayout());
setMinimumSize(new Dimension(800, 600));
// Create drawing and action listeners...
this.basicDrawing = new BasicDrawing();
this.mapViewDrawing = new MapViewDrawing();
this.drawing = basicDrawing;
// Createa palettes
this.basicPalette = new BasicGraphPalette();
this.blackAndWhitePalette = new BlackAndWhiteGraphPalette();
this.currentPalette = this.basicPalette;
wccPanel = new AlgorithmPanel(this, WeaklyConnectedComponentsAlgorithm.class,
"Weakly-Connected Components", new String[] {}, false);
wccPanel.addStartActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
StartActionEvent evt = (StartActionEvent) e;
WeaklyConnectedComponentsData data = new WeaklyConnectedComponentsData(graph);
WeaklyConnectedComponentsAlgorithm wccAlgorithm = null;
try {
wccAlgorithm = (WeaklyConnectedComponentsAlgorithm) AlgorithmFactory
.createAlgorithm(evt.getAlgorithmClass(), data);
}
catch (Exception e1) {
JOptionPane.showMessageDialog(MainWindow.this,
"An error occurred while creating the specified algorithm.",
"Internal error: Algorithm instantiation failure",
JOptionPane.ERROR_MESSAGE);
e1.printStackTrace();
return;
}
wccPanel.setEnabled(false);
if (evt.isGraphicVisualizationEnabled()) {
wccAlgorithm.addObserver(new WeaklyConnectedComponentGraphicObserver(drawing));
}
if (evt.isTextualVisualizationEnabled()) {
wccAlgorithm.addObserver(new WeaklyConnectedComponentTextObserver(printStream));
}
// We love Java...
final WeaklyConnectedComponentsAlgorithm copyAlgorithm = wccAlgorithm;
launchThread(new Runnable() {
@Override
public void run() {
AbstractSolution solution = copyAlgorithm.run();
wccPanel.solutionPanel.addSolution(solution, false);
wccPanel.solutionPanel.setVisible(true);
wccPanel.setEnabled(true);
}
});
}
});
spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class, "Shortest-Path",
new String[] { "Origin", "Destination" }, true);
spPanel.addStartActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
StartActionEvent evt = (StartActionEvent) e;
ShortestPathData data = new ShortestPathData(graph, evt.getNodes().get(0),
evt.getNodes().get(1), evt.getArcFilter());
ShortestPathAlgorithm spAlgorithm = null;
try {
spAlgorithm = (ShortestPathAlgorithm) AlgorithmFactory
.createAlgorithm(evt.getAlgorithmClass(), data);
}
catch (Exception e1) {
JOptionPane.showMessageDialog(MainWindow.this,
"An error occurred while creating the specified algorithm.",
"Internal error: Algorithm instantiation failure",
JOptionPane.ERROR_MESSAGE);
e1.printStackTrace();
return;
}
spPanel.setEnabled(false);
if (evt.isGraphicVisualizationEnabled()) {
spAlgorithm.addObserver(new ShortestPathGraphicObserver(drawing));
}
if (evt.isTextualVisualizationEnabled()) {
spAlgorithm.addObserver(new ShortestPathTextObserver(printStream));
}
final ShortestPathAlgorithm copyAlgorithm = spAlgorithm;
launchThread(new Runnable() {
@Override
public void run() {
// Run the algorithm.
ShortestPathSolution solution = copyAlgorithm.run();
// Add the solution to the solution panel (but do not display
// overlay).
spPanel.solutionPanel.addSolution(solution, false);
// If the solution is feasible, add the path to the path panel.
if (solution.isFeasible()) {
pathPanel.addPath(solution.getPath());
}
// Show the solution panel and enable the shortest-path panel.
spPanel.solutionPanel.setVisible(true);
spPanel.setEnabled(true);
}
});
}
});
cpPanel = new AlgorithmPanel(this, CarPoolingAlgorithm.class, "Car-Pooling", new String[] {
"Origin Car", "Origin Pedestrian", "Destination Car", "Destination Pedestrian" },
true);
psPanel = new AlgorithmPanel(this, PackageSwitchAlgorithm.class, "Car-Pooling",
new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true);
// add algorithm panels
algoPanels.add(wccPanel);
algoPanels.add(spPanel);
algoPanels.add(cpPanel);
algoPanels.add(psPanel);
this.pathPanel = new PathsPanel(this);
// Add click listeners to both drawing.
for (AlgorithmPanel panel: algoPanels) {
this.basicDrawing.addDrawingClickListener(panel.nodesInputPanel);
this.mapViewDrawing.addDrawingClickListener(panel.nodesInputPanel);
this.graphChangeListeneres.add(panel.nodesInputPanel);
this.graphChangeListeneres.add(panel.solutionPanel);
this.drawingChangeListeners.add(panel.nodesInputPanel);
this.drawingChangeListeners.add(panel.solutionPanel);
this.drawingChangeListeners.add(panel);
}
this.graphChangeListeneres.add(pathPanel);
this.drawingChangeListeners.add(pathPanel);
// Create action factory.
this.currentThread = new ThreadWrapper(this);
this.baf = new BlockingActionFactory(this);
this.baf.addAction(currentThread);
// Click adapter
ActionListener openMapActionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = FileUtils.createFileChooser(FolderType.Map);
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
graphFilePath = chooser.getSelectedFile().getAbsolutePath();
// Note: Don't use a try-resources block since loadGraph is asynchronous.
final DataInputStream stream;
try {
stream = new DataInputStream(new BufferedInputStream(
new FileInputStream(chooser.getSelectedFile())));
}
catch (IOException e1) {
JOptionPane.showMessageDialog(MainWindow.this,
"Cannot open the selected file.");
return;
}
loadGraph(new BinaryGraphReader(stream));
}
}
};
setJMenuBar(createMenuBar(openMapActionListener));
// Initial panel to show "Open Map... "
JPanel openPanel = new JPanel();
openPanel.setLayout(new BoxLayout(openPanel, BoxLayout.PAGE_AXIS));
JButton openButton = new JButton("Open Map... ");
openButton.setAlignmentX(Component.CENTER_ALIGNMENT);
openButton.addActionListener(openMapActionListener);
openButton.setFocusPainted(false);
openPanel.add(Box.createVerticalGlue());
openPanel.add(openButton);
openPanel.add(Box.createVerticalGlue());
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
"Are you sure you want to close the application?", "Exit Confirmation",
JOptionPane.YES_NO_OPTION);
if (confirmed == JOptionPane.YES_OPTION) {
dispose();
System.exit(0);
}
}
});
// Create graph area
mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
JTextArea infoPanel = new JTextArea();
infoPanel.setMinimumSize(new Dimension(200, 50));
infoPanel.setBackground(Color.WHITE);
infoPanel.setLineWrap(true);
infoPanel.setEditable(false);
this.logStream = new StreamCapturer(infoPanel);
this.printStream = new PrintStream(this.logStream);
JPanel rightComponent = new JPanel();
rightComponent.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.fill = GridBagConstraints.HORIZONTAL;
rightComponent.add(pathPanel, c);
c.gridy = 1;
for (AlgorithmPanel panel: algoPanels) {
panel.setVisible(false);
rightComponent.add(panel, c);
}
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 2;
c.weightx = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
c.gridheight = GridBagConstraints.REMAINDER;
rightComponent.add(new JScrollPane(infoPanel), c);
mainPanel.setResizeWeight(0.8);
mainPanel.setDividerSize(5);
mainPanel.setBackground(Color.WHITE);
mainPanel.setLeftComponent(openPanel);
mainPanel.setRightComponent(rightComponent);
this.add(mainPanel, BorderLayout.CENTER);
// Top Panel
this.add(createStatusBar(), BorderLayout.SOUTH);
// Notify everythin
notifyDrawingLoaded(null, drawing);
}
/**
* @param runnable
* @param canInterrupt
*/
private void launchThread(Runnable runnable, boolean canInterrupt) {
if (canInterrupt) {
currentThread.setThread(new Thread(new Runnable() {
@Override
public void run() {
threadTimer.restart();
threadPanel.setVisible(true);
runnable.run();
clearCurrentThread();
}
}));
}
else {
currentThread.setThread(new Thread(runnable));
}
currentThread.startThread();
}
private void launchThread(Runnable runnable) {
launchThread(runnable, true);
}
protected void clearCurrentThread() {
threadTimer.stop();
threadPanel.setVisible(false);
currentThread.setThread(null);
if (spPanel.isVisible()) {
spPanel.setEnabled(true);
}
}
/**
* Notify all listeners that a new graph has been loaded.
*/
private void notifyNewGraphLoaded() {
for (GraphChangeListener listener: graphChangeListeneres) {
listener.newGraphLoaded(graph);
}
}
/**
* Notify all listeners that a new drawing has been set up.
*
* @param oldDrawing
* @param newDrawing
*/
private void notifyDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
for (DrawingChangeListener listener: drawingChangeListeners) {
listener.onDrawingLoaded(oldDrawing, newDrawing);
}
}
/**
* Notify all listeners that a redraw request is emitted.
*/
private void notifyRedrawRequest() {
for (DrawingChangeListener listener: drawingChangeListeners) {
listener.onRedrawRequest();
}
}
/**
* Draw the stored graph on the drawing.
*/
private void drawGraph(Class<? extends Drawing> newClass, GraphPalette palette) {
// Save old divider location
int oldLocation = mainPanel.getDividerLocation();
// Set drawing if not set
if (!(mainPanel.getLeftComponent() instanceof Drawing)) {
mainPanel.setLeftComponent((Component) this.drawing);
mainPanel.setDividerLocation(oldLocation);
// Need to re-validate or the drawing will not have the
// correct size prior to drawing, which can cause issue.
this.revalidate();
}
boolean isNewGraph = newClass == null;
boolean isMapView = (isNewGraph && drawing == mapViewDrawing)
|| (!isNewGraph && newClass.equals(MapViewDrawing.class));
// We need to draw MapView, we have to check if the file exists.
File mfile = null;
if (isMapView) {
String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map"))
+ ".mapfg";
mfile = new File(mfpath);
if (!mfile.exists()) {
if (JOptionPane.showConfirmDialog(this,
"The associated mapsforge (.mapfg) file has not been found, do you want to specify it manually?",
"File not found",
JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) {
JFileChooser chooser = new JFileChooser(mfile.getParentFile());
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
mfile = chooser.getSelectedFile();
}
else {
mfile = null;
}
}
else {
mfile = null;
}
}
}
Runnable runnable = null;
if (isMapView && mfile != null) {
final File mfileFinal = mfile;
// It is a mapview drawing and the file was found, so:
// 1. We create the drawing if necessary.
if (drawing != mapViewDrawing) {
drawing.clear();
drawing = mapViewDrawing;
mainPanel.setLeftComponent(mapViewDrawing);
mainPanel.setDividerLocation(oldLocation);
notifyDrawingLoaded(basicDrawing, mapViewDrawing);
drawing.clear();
isNewGraph = true;
mainPanel.revalidate();
}
if (isNewGraph) {
drawing.clear();
runnable = new Runnable() {
public void run() {
((MapViewDrawing) drawing).drawGraph(mfileFinal);
notifyRedrawRequest();
}
};
}
}
else if (!isMapView || (isMapView && mfile == null && isNewGraph)) {
if (drawing == mapViewDrawing) {
mapViewDrawing.clear();
drawing = basicDrawing;
mainPanel.setLeftComponent(basicDrawing);
mainPanel.setDividerLocation(oldLocation);
notifyDrawingLoaded(mapViewDrawing, basicDrawing);
isNewGraph = true;
}
if (isNewGraph || palette != this.currentPalette) {
this.currentPalette = palette;
drawing.clear();
runnable = new Runnable() {
public void run() {
drawing.drawGraph(graph, palette);
notifyRedrawRequest();
}
};
}
}
if (runnable != null) {
launchThread(runnable, false);
}
else {
drawing.clearOverlays();
notifyRedrawRequest();
}
}
/**
* @param newClass
*/
private void drawGraph(Class<? extends Drawing> newClass) {
drawGraph(newClass, new BasicGraphPalette());
}
/**
*
*/
private void drawGraph() {
drawGraph(null, this.currentPalette);
}
private void loadGraph(GraphReader reader) {
launchThread(new Runnable() {
@Override
public void run() {
GraphReaderProgressBar progressBar = new GraphReaderProgressBar(MainWindow.this);
progressBar.setLocationRelativeTo(mainPanel.getLeftComponent());
reader.addObserver(progressBar);
try {
graph = reader.read();
reader.close();
}
catch (Exception exception) {
progressBar.setVisible(false);
progressBar.dispose();
progressBar = null;
JOptionPane.showMessageDialog(MainWindow.this,
"<html><p>Unable to read graph from the selected file:</p><p>"
+ exception.getMessage() + "</p>");
exception.printStackTrace(System.out);
return;
}
// In case of....
progressBar.setVisible(false);
progressBar.dispose();
progressBar = null;
String info = graph.getMapId();
if (graph.getMapName() != null && !graph.getMapName().isEmpty()) {
// The \u200e character is the left-to-right mark, we need to avoid issue with
// name that are right-to-left (e.g. arabic names).
info += " - " + graph.getMapName() + "\u200e";
}
info += ", " + graph.size() + " nodes, " + graph.getGraphInformation().getArcCount()
+ " arcs.";
graphInfoPanel.setText(info);
drawGraph();
notifyNewGraphLoaded();
for (JMenuItem item: graphLockItems) {
item.setEnabled(true);
}
}
}, false);
}
/**
* Show and enable the given AlgorithmPanel (and hide all others).
*
* @param algorithmPanel
*/
private void enableAlgorithmPanel(AlgorithmPanel algorithmPanel) {
int dividerLocation = mainPanel.getDividerLocation();
for (AlgorithmPanel panel: algoPanels) {
panel.setVisible(panel == algorithmPanel);
}
mainPanel.setDividerLocation(dividerLocation);
}
private JMenuBar createMenuBar(ActionListener openMapActionListener) {
// Open Map item...
JMenuItem openMapItem = new JMenuItem("Open Map... ", KeyEvent.VK_O);
openMapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
openMapItem.addActionListener(baf.createBlockingAction(openMapActionListener));
// Open Path item...
JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P);
openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK));
openPathItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathInput);
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
try (BinaryPathReader reader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(
new FileInputStream(chooser.getSelectedFile()))))){
Path path = reader.readPath(graph);
pathPanel.addPath(path);
}
catch (MapMismatchException exception) {
JOptionPane.showMessageDialog(MainWindow.this,
"The selected file does not contain a path for the current graph.");
}
catch (IOException e1) {
JOptionPane.showMessageDialog(MainWindow.this,
"Cannot open the selected file.");
}
catch (Exception exception) {
JOptionPane.showMessageDialog(MainWindow.this,
"Unable to read path from the selected file.");
}
}
}
}));
graphLockItems.add(openPathItem);
// Close item
JMenuItem closeItem = new JMenuItem("Quit", KeyEvent.VK_Q);
closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
closeItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MainWindow.this.dispatchEvent(
new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING));
}
});
// Build the first menu.
JMenu fileMenu = new JMenu("File");
fileMenu.add(openMapItem);
fileMenu.add(openPathItem);
fileMenu.addSeparator();
fileMenu.add(closeItem);
// Second menu
JMenuItem drawGraphItem = new JMenuItem("Redraw", KeyEvent.VK_R);
drawGraphItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
drawGraphItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
drawGraph(BasicDrawing.class, basicPalette);
}
}));
graphLockItems.add(drawGraphItem);
JMenuItem drawGraphBWItem = new JMenuItem("Redraw (B&W)", KeyEvent.VK_B);
drawGraphBWItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.ALT_MASK));
drawGraphBWItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
drawGraph(BasicDrawing.class, blackAndWhitePalette);
}
}));
graphLockItems.add(drawGraphBWItem);
JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M);
drawGraphMapsforgeItem
.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK));
drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
drawGraph(MapViewDrawing.class);
}
}));
graphLockItems.add(drawGraphMapsforgeItem);
JMenu graphMenu = new JMenu("Graph");
graphMenu.add(drawGraphItem);
graphMenu.add(drawGraphBWItem);
graphMenu.addSeparator();
graphMenu.add(drawGraphMapsforgeItem);
// Algo menu
JMenu algoMenu = new JMenu("Algorithms");
// Weakly connected components
JMenuItem wccItem = new JMenuItem("Weakly Connected Components");
wccItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
enableAlgorithmPanel(wccPanel);
}
}));
// Shortest path
JMenuItem spItem = new JMenuItem("Shortest-Path");
spItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
enableAlgorithmPanel(spPanel);
}
}));
// Car pooling
JMenuItem cpItem = new JMenuItem("Car Pooling");
cpItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
enableAlgorithmPanel(cpPanel);
}
}));
// Car pooling
JMenuItem psItem = new JMenuItem("Package Switch");
psItem.addActionListener(baf.createBlockingAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
enableAlgorithmPanel(psPanel);
}
}));
graphLockItems.add(wccItem);
graphLockItems.add(spItem);
graphLockItems.add(cpItem);
graphLockItems.add(psItem);
algoMenu.add(wccItem);
algoMenu.addSeparator();
algoMenu.add(spItem);
algoMenu.add(cpItem);
algoMenu.add(psItem);
// Create the menu bar.
JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
menuBar.add(graphMenu);
menuBar.add(algoMenu);
for (JMenuItem item: graphLockItems) {
item.setEnabled(false);
}
return menuBar;
}
private JPanel createStatusBar() {
// create the status bar panel and shove it down the bottom of the frame
JPanel statusPanel = new JPanel();
statusPanel.setBorder(
new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
new EmptyBorder(0, 15, 0, 15)));
statusPanel.setPreferredSize(new Dimension(getWidth(), 38));
statusPanel.setLayout(new BorderLayout());
graphInfoPanel = new JLabel();
graphInfoPanel.setHorizontalAlignment(SwingConstants.LEFT);
statusPanel.add(graphInfoPanel, BorderLayout.WEST);
JLabel threadInfo = new JLabel("Thread running... ");
JLabel threadTimerLabel = new JLabel("00:00:00");
JButton threadButton = new JButton("Stop");
threadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (currentThread.isRunning()) {
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
"Are you sure you want to kill the running thread?",
"Kill Confirmation", JOptionPane.YES_NO_OPTION);
if (confirmed == JOptionPane.YES_OPTION) {
currentThread.interrupt();
}
}
}
});
threadTimer = new Timer(THREAD_TIMER_DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long seconds = currentThread.getDuration().getSeconds();
threadTimerLabel.setText(String.format("%02d:%02d:%02d", seconds / 3600,
seconds / 60 % 60, seconds % 60));
}
});
threadTimer.setInitialDelay(0);
threadPanel = new JPanel();
threadPanel.add(threadInfo);
threadPanel.add(threadTimerLabel);
threadPanel.add(threadButton);
threadPanel.setVisible(false);
statusPanel.add(threadPanel, BorderLayout.EAST);
return statusPanel;
}
public static void main(final String[] args) {
// Try to set system look and feel.
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
MainWindow w = new MainWindow();
w.setExtendedState(JFrame.MAXIMIZED_BOTH);
w.setVisible(true);
}
});
}
}

View file

@ -0,0 +1,424 @@
package org.insa.graphs.gui;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
import org.insa.graphs.gui.drawing.DrawingClickListener;
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Point;
public class NodesInputPanel extends JPanel
implements DrawingClickListener, DrawingChangeListener, GraphChangeListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Color DEFAULT_MARKER_COLOR = Color.BLUE;
/**
* Utility class that can be used to find a node from coordinates in a "fast"
* way.
*
*/
private static class NodeFinder {
// Graph associated with this node finder.
private Graph graph;
/**
* @param graph
*/
public NodeFinder(Graph graph) {
this.graph = graph;
}
/**
* @param point
*
* @return the closest node to the given point, or null if no node is "close
* enough".
*/
public Node findClosestNode(Point point) {
Node minNode = null;
double minDis = Double.POSITIVE_INFINITY;
for (Node node: graph.getNodes()) {
double dlon = point.getLongitude() - node.getPoint().getLongitude();
double dlat = point.getLatitude() - node.getPoint().getLatitude();
double dis = dlon * dlon + dlat * dlat; // No need to square
if (dis < minDis) {
minNode = node;
minDis = dis;
}
}
return minNode;
}
}
/**
* Event data send when a node input has changed.
*
*/
public class InputChangedEvent extends ActionEvent {
/**
*
*/
private static final long serialVersionUID = 3440024811352247731L;
protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled";
protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1;
// List of nodes
List<Node> nodes;
public InputChangedEvent(List<Node> nodes2) {
super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND);
this.nodes = nodes2;
}
List<Node> getNodes() {
return Collections.unmodifiableList(nodes);
}
};
// Node inputs and markers.
private final ArrayList<JTextField> nodeInputs = new ArrayList<>();
private final Map<JTextField, MarkerOverlay> markerTrackers = new IdentityHashMap<JTextField, MarkerOverlay>();
// Component that can be enabled/disabled.
private ArrayList<JComponent> components = new ArrayList<>();
private int inputToFillIndex;
// ActionListener called when all inputs are filled.
private ArrayList<ActionListener> inputChangeListeners = new ArrayList<>();
// Drawing and graph
private Drawing drawing;
private Graph graph;
private NodeFinder nodeFinder;
/**
* Create a new NodesInputPanel.
*
*/
public NodesInputPanel() {
super(new GridBagLayout());
initInputToFill();
}
/**
* Add an InputChanged listener to this panel. This listener will be notified by
* a {@link InputChangedEvent} each time an input in this panel change (click,
* clear, manual input).
*
* @param listener Listener to add.
*
* @see InputChangedEvent
*/
public void addInputChangedListener(ActionListener listener) {
inputChangeListeners.add(listener);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
for (JTextField input: nodeInputs) {
MarkerOverlay marker = markerTrackers.getOrDefault(input, null);
if (marker != null) {
marker.setVisible(visible && !input.getText().trim().isEmpty());
}
}
}
@Override
public void setEnabled(boolean enabled) {
for (JComponent component: components) {
component.setEnabled(enabled);
}
super.setEnabled(enabled);
if (enabled) {
// Enable: Check if there is an input to fill, otherwize find the next one.
if (getInputToFill() == null) {
nextInputToFill();
}
}
else {
// Disable, next input to fill = -1.
this.inputToFillIndex = -1;
}
}
public void clear() {
for (JTextField field: nodeInputs) {
field.setText("");
markerTrackers.put(field, null);
}
initInputToFill();
}
public void addTextField(String label) {
addTextField(label, DEFAULT_MARKER_COLOR);
}
public void addTextField(String label, Color markerColor) {
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
JLabel jLabel = new JLabel(label);
jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD));
JTextField textField = new JTextField();
jLabel.setLabelFor(textField);
c.gridx = 0;
c.gridy = nodeInputs.size();
c.weightx = 0;
c.gridwidth = 1;
c.fill = GridBagConstraints.HORIZONTAL;
add(jLabel, c);
c.gridx = 1;
c.gridy = nodeInputs.size();
c.weightx = 1;
c.gridwidth = 1;
c.fill = GridBagConstraints.HORIZONTAL;
add(textField, c);
JButton clearButton = new JButton("Clear");
c.gridx = 2;
c.gridy = nodeInputs.size();
c.weightx = 0;
c.gridwidth = 1;
c.fill = GridBagConstraints.HORIZONTAL;
add(clearButton, c);
JButton clickButton = new JButton("Click");
c.gridx = 3;
c.gridy = nodeInputs.size();
c.weightx = 0;
c.gridwidth = 1;
c.fill = GridBagConstraints.HORIZONTAL;
add(clickButton, c);
// Did not find something easier that this... ?
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
insertUpdate(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
insertUpdate(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
// Draw marker if possible
Node curnode = getNodeForInput(textField);
MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null);
if (curnode != null) {
if (tracker == null) {
tracker = drawing.drawMarker(curnode.getPoint(), markerColor, Color.BLACK,
AlphaMode.TRANSPARENT);
markerTrackers.put(textField, tracker);
}
else {
tracker.moveTo(curnode.getPoint());
}
tracker.setVisible(true);
}
else if (tracker != null) {
tracker.setVisible(false);
if (getInputToFill() == null) {
nextInputToFill();
}
}
// Create array of nodes
List<Node> nodes = getNodeForInputs();
// Trigger change event.
for (ActionListener lis: inputChangeListeners) {
lis.actionPerformed(new InputChangedEvent(nodes));
}
}
});
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
textField.setText("");
setInputToFill(textField);
}
});
clickButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setInputToFill(textField);
}
});
nodeInputs.add(textField);
components.add(textField);
components.add(clearButton);
components.add(clickButton);
}
/**
* @return Current graph associated with this input panel.
*/
protected Graph getGraph() {
return this.graph;
}
/**
* @return The node for the given text field, or null if the content of the text
* field is invalid.
*/
protected Node getNodeForInput(JTextField textfield) {
try {
Node node = graph.get(Integer.valueOf(textfield.getText().trim()));
return node;
}
catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
return null;
}
}
/**
* @return List of nodes associated with the input. Some nodes may be null if
* their associated input is invalid.
*/
public List<Node> getNodeForInputs() {
List<Node> nodes = new ArrayList<>(nodeInputs.size());
for (JTextField input: nodeInputs) {
nodes.add(getNodeForInput(input));
}
return nodes;
}
/**
* Get the next input that should be filled by a click, or null if none should
* be filled.
*
* @return
*/
protected JTextField getInputToFill() {
if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) {
return null;
}
return nodeInputs.get(inputToFillIndex);
}
/**
* Initialize the next input to fill.
*/
protected void initInputToFill() {
inputToFillIndex = 0;
}
/**
* Set the next input to fill to the given text field.
*
* @param input
*/
protected void setInputToFill(JTextField input) {
inputToFillIndex = nodeInputs.indexOf(input);
}
/**
* Find the next input to fill, if any.
*/
protected void nextInputToFill() {
boolean found = false;
if (inputToFillIndex == -1) {
inputToFillIndex = 0;
}
for (int i = 0; i < nodeInputs.size() && !found; ++i) {
int nextIndex = (i + inputToFillIndex) % nodeInputs.size();
JTextField input = nodeInputs.get(nextIndex);
if (input.getText().trim().isEmpty()) {
inputToFillIndex = nextIndex;
found = true;
}
}
if (!found) {
inputToFillIndex = -1;
}
}
@Override
public void mouseClicked(Point point) {
JTextField input = getInputToFill();
if (input != null) {
Node node = nodeFinder.findClosestNode(point);
input.setText(String.valueOf(node.getId()));
nextInputToFill();
}
}
@Override
public void newGraphLoaded(Graph graph) {
if (graph != this.graph) {
this.clear();
this.graph = graph;
nodeFinder = new NodeFinder(graph);
// Disable if previously disabled...
setEnabled(this.isEnabled());
}
}
@Override
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
if (newDrawing != drawing) {
this.drawing = newDrawing;
}
}
@Override
public void onRedrawRequest() {
for (JTextField input: nodeInputs) {
MarkerOverlay tracker = markerTrackers.getOrDefault(input, null);
if (tracker != null) {
MarkerOverlay newMarker = this.drawing.drawMarker(tracker.getPoint(),
tracker.getColor(), Color.BLACK, AlphaMode.TRANSPARENT);
markerTrackers.put(input, newMarker);
newMarker.setVisible(tracker.isVisible());
tracker.delete();
}
}
}
}

View file

@ -0,0 +1,361 @@
package org.insa.graphs.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
import org.insa.graphs.gui.utils.ColorUtils;
import org.insa.graphs.gui.utils.FileUtils;
import org.insa.graphs.gui.utils.FileUtils.FolderType;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.io.BinaryPathWriter;
public class PathsPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private class PathPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Simple icon that represents a unicolor rectangle.
*
*/
protected class ColorIcon implements Icon {
private Color color;
private int width, height;
public ColorIcon(Color color, int width, int height) {
this.width = width;
this.height = height;
this.color = color;
}
public void setColor(Color color) {
this.color = color;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(this.color);
g.fillRect(x, y, getIconWidth(), getIconHeight());
}
@Override
public int getIconWidth() {
return this.width;
}
@Override
public int getIconHeight() {
return this.height;
}
}
// Solution
private final Path path;
// Path Overlay (not final due to redraw)
private PathOverlay overlay;
// Color button
private final JButton colorButton;
/**
* Create a new bundle with the given path and create a new overlay
* corresponding to the path.
*
* @param path Path for this bundle, must not be null.
*
* @throws IOException If a resource was not found.
*
*/
public PathPanel(Path path, Color color) throws IOException {
super();
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY),
new EmptyBorder(5, 0, 5, 0)));
this.path = path;
this.overlay = drawing.drawPath(this.path, color);
JCheckBox checkbox = new JCheckBox();
checkbox.setSelected(true);
checkbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
overlay.setVisible(checkbox.isSelected());
}
});
JLabel infoPanel = new JLabel();
String info = "";
// Display length
float length = path.getLength();
if (length < 2000) {
info += String.format("Length = %.1f meters", length);
}
else {
info += String.format("Length = %.3f kilometers", length / 1000.);
}
// Display time
info += ", Duration=";
double time = path.getMinimumTravelTime();
int hours = (int) (time / 3600);
int minutes = (int) (time / 60) % 60;
int seconds = ((int) time) % 60;
if (hours > 0) {
info += String.format("%d hours, ", hours);
}
if (minutes > 0) {
info += String.format("%d minutes, ", minutes);
}
info += String.format("%d seconds.", seconds);
infoPanel.setText("<html>" + toString() + "<br/>" + info + "</html>");
infoPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
checkbox.setSelected(!checkbox.isSelected());
overlay.setVisible(checkbox.isSelected());
}
});
Dimension size = new Dimension(24, 24);
ColorIcon icon = new ColorIcon(overlay.getColor(), 14, 14);
colorButton = new JButton(icon);
colorButton.setFocusable(false);
colorButton.setFocusPainted(false);
colorButton.setMinimumSize(size);
colorButton.setPreferredSize(size);
colorButton.setMaximumSize(size);
colorButton.setToolTipText("Pick a color");
colorButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final Color originalColor = overlay.getColor();
JColorChooser chooser = new JColorChooser(overlay.getColor());
chooser.getSelectionModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
icon.setColor(chooser.getSelectionModel().getSelectedColor());
colorButton.repaint();
overlay.setColor(chooser.getSelectionModel().getSelectedColor());
overlay.redraw();
}
});
JColorChooser.createDialog(getTopLevelAncestor(), "Pick a new color", true,
chooser, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
icon.setColor(chooser.getSelectionModel().getSelectedColor());
colorButton.repaint();
overlay.setColor(
chooser.getSelectionModel().getSelectedColor());
overlay.redraw();
}
}, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
icon.setColor(originalColor);
colorButton.repaint();
overlay.setColor(originalColor);
overlay.redraw();
}
}).setVisible(true);
;
}
});
Image saveImg = ImageIO.read(getClass().getResourceAsStream("/save-icon.png"))
.getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
JButton saveButton = new JButton(new ImageIcon(saveImg));
saveButton.setFocusPainted(false);
saveButton.setFocusable(false);
saveButton.setMinimumSize(size);
saveButton.setPreferredSize(size);
saveButton.setMaximumSize(size);
saveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String filepath = String.format("path_%s_%d_%d.path",
path.getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", ""),
path.getOrigin().getId(), path.getDestination().getId());
JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathOutput,
filepath);
if (chooser
.showSaveDialog(getTopLevelAncestor()) == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
try (BinaryPathWriter writer = new BinaryPathWriter(new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(file))))) {
writer.writePath(path);
}
catch (IOException e1) {
JOptionPane.showMessageDialog(getTopLevelAncestor(),
"Unable to write path to the selected file.");
e1.printStackTrace();
}
}
}
});
Image newimg = ImageIO.read(getClass().getResourceAsStream("/delete-icon.png"))
.getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
JButton deleteButton = new JButton(new ImageIcon(newimg));
deleteButton.setFocusPainted(false);
deleteButton.setFocusable(false);
deleteButton.setMinimumSize(size);
deleteButton.setPreferredSize(size);
deleteButton.setMaximumSize(size);
deleteButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
overlay.delete();
PathsPanel.this.removePath(PathPanel.this);
}
});
add(checkbox);
add(Box.createHorizontalStrut(5));
add(infoPanel);
add(Box.createHorizontalGlue());
add(colorButton);
add(saveButton);
add(deleteButton);
}
/**
* Re-draw the current overlay (if any) on the new drawing.
*
*/
public void updateOverlay() {
PathOverlay oldOverlay = this.overlay;
this.overlay = drawing.drawPath(path);
this.overlay.setColor(oldOverlay.getColor());
((ColorIcon) this.colorButton.getIcon()).setColor(this.overlay.getColor());
this.colorButton.repaint();
this.overlay.setVisible(oldOverlay.isVisible());
oldOverlay.delete();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return "Path from #" + path.getOrigin().getId() + " to #"
+ path.getDestination().getId();
}
}
// Solution
private Drawing drawing;
public PathsPanel(Component parent) {
super();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBorder(new EmptyBorder(15, 15, 15, 15));
// Default hidden
this.setVisible(false);
}
public void addPath(Path path) {
try {
this.add(new PathPanel(path, ColorUtils.getColor(this.getComponentCount())));
this.setVisible(true);
this.revalidate();
this.repaint();
}
catch (Exception e) {
e.printStackTrace();
}
}
protected void removePath(PathPanel panel) {
PathsPanel.this.remove(panel);
PathsPanel.this.revalidate();
PathsPanel.this.repaint();
if (this.getComponentCount() == 0) {
this.setVisible(false);
}
}
@Override
public void newGraphLoaded(Graph graph) {
for (Component c: this.getComponents()) {
if (c instanceof PathPanel) {
((PathPanel) c).overlay.delete();
}
}
this.removeAll();
this.setVisible(false);
}
@Override
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
if (newDrawing != drawing) {
drawing = newDrawing;
}
}
@Override
public void onRedrawRequest() {
for (Component c: this.getComponents()) {
if (c instanceof PathPanel) {
((PathPanel) c).updateOverlay();
}
}
}
}

View file

@ -0,0 +1,33 @@
package org.insa.graphs.gui;
import java.time.Duration;
import java.time.Instant;
public interface RunningAction {
/**
* @return true if this action is running.
*/
public boolean isRunning();
/**
* Interrupt this action.
*/
public void interrupt();
/**
* @return Starting time of this action.
*/
public Instant getStartingTime();
/**
* @return Current duration of this action.
*/
public Duration getDuration();
/**
* @return Information for this action.
*/
public String getInformation();
}

View file

@ -0,0 +1,289 @@
package org.insa.graphs.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import org.insa.graphs.algorithm.AbstractInputData;
import org.insa.graphs.algorithm.AbstractSolution;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
public class SolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private class SolutionBundle {
// Solution
private final AbstractSolution solution;
// Path Overlay (not final due to redraw)
private List<PathOverlay> overlays = new ArrayList<>();
/**
* Create a new bundle with the given solution and create a new overlay
* corresponding to the solution (if the solution is feasible).
*
* @param solution Solution for this bundle, must not be null.
*
*/
public SolutionBundle(AbstractSolution solution, boolean createOverlays) {
this.solution = solution;
if (createOverlays) {
this.overlays = createOverlaysFromSolution();
}
}
/**
* @return Solution associated with this bundle.
*/
public AbstractSolution getSolution() {
return this.solution;
}
/**
* @return Data assocaited with this bundle.
*/
public AbstractInputData getData() {
return this.solution.getInputData();
}
/**
* @return Overlays associated with this bundle, or null.
*/
public List<PathOverlay> getOverlays() {
return this.overlays;
}
/**
* @return true if this bundle has overlays.
*/
public boolean hasOverlays() {
return !this.overlays.isEmpty();
}
/**
* Re-draw the current overlay (if any) on the new drawing.
*
*/
public void updateOverlays() {
if (this.overlays.isEmpty()) {
return; // This bundle has no overlay.
}
List<PathOverlay> oldOverlays = this.overlays;
this.overlays = createOverlaysFromSolution();
for (int i = 0; i < oldOverlays.size(); ++i) {
oldOverlays.get(i).delete();
}
}
private List<PathOverlay> createOverlaysFromSolution() {
List<PathOverlay> overlays = new ArrayList<>();
if (solution.isFeasible()) {
Method[] methods = this.solution.getClass().getDeclaredMethods();
for (Method method: methods) {
if (method.getReturnType().equals(Path.class)
&& method.getParameterCount() == 0) {
try {
Path path = (Path) method.invoke(this.solution);
overlays.add(drawing.drawPath(path));
}
catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// This has been check before, so should never happen...
e.printStackTrace();
}
}
}
}
return overlays;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return getData().toString();
}
}
// Solution
private Drawing drawing;
// Solution selector
private final JComboBox<SolutionBundle> solutionSelect;
private final JLabel informationPanel;
// Current bundle
private SolutionBundle currentBundle = null;
public SolutionPanel(Component parent) {
super();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBorder(new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY),
new EmptyBorder(10, 0, 10, 0)));
solutionSelect = new JComboBox<>();
solutionSelect.setBackground(Color.WHITE);
solutionSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
add(solutionSelect);
informationPanel = new JLabel();
informationPanel.setOpaque(true);
informationPanel.setFocusable(false);
// informationPanel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
informationPanel.setHorizontalAlignment(JLabel.LEFT);
add(Box.createVerticalStrut(8));
add(informationPanel);
JButton clearButton = new JButton("Hide");
clearButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (PathOverlay overlay: currentBundle.getOverlays()) {
if (overlay.isVisible()) {
overlay.setVisible(false);
clearButton.setText("Show");
}
else {
overlay.setVisible(true);
clearButton.setText("Hide");
}
}
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(clearButton);
buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
add(Box.createVerticalStrut(4));
add(buttonPanel);
solutionSelect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (currentBundle != null) {
for (PathOverlay overlay: currentBundle.getOverlays()) {
overlay.setVisible(false);
}
}
SolutionBundle bundle = (SolutionBundle) solutionSelect.getSelectedItem();
if (bundle != null) {
updateInformationLabel(bundle);
buttonPanel
.setVisible(bundle.getSolution().isFeasible() && bundle.hasOverlays());
clearButton.setText(bundle.getSolution().isFeasible() ? "Hide" : "Show");
for (PathOverlay overlay: bundle.getOverlays()) {
overlay.setVisible(true);
}
}
currentBundle = bundle;
}
});
}
public void addSolution(AbstractSolution solution) {
addSolution(solution, true);
}
/**
* Add the given solution to the panel.
*
* @param solution the solution to add to the panel
* @param createOverlays Whether or not overlay should be created for this
* solution.
*/
public void addSolution(AbstractSolution solution, boolean createOverlays) {
SolutionBundle bundle = new SolutionBundle(solution, createOverlays);
solutionSelect.addItem(bundle);
solutionSelect.setSelectedItem(bundle);
}
protected void updateInformationLabel(SolutionBundle bundle) {
informationPanel.setText(bundle.getSolution().toString());
revalidate();
repaint();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
solutionSelect.setEnabled(enabled);
if (enabled) {
// Trigger event
solutionSelect.setSelectedItem(currentBundle);
}
else {
SolutionBundle bundle = (SolutionBundle) this.solutionSelect.getSelectedItem();
if (bundle != null) {
for (PathOverlay overlay: bundle.getOverlays()) {
overlay.setVisible(false);
}
}
}
}
@Override
public void newGraphLoaded(Graph graph) {
for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
for (PathOverlay overlay: this.solutionSelect.getItemAt(i).getOverlays()) {
overlay.delete();
}
}
this.solutionSelect.removeAllItems();
this.currentBundle = null;
this.setVisible(false);
}
@Override
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
if (newDrawing != drawing) {
drawing = newDrawing;
}
}
@Override
public void onRedrawRequest() {
for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
this.solutionSelect.getItemAt(i).updateOverlays();
}
}
}

View file

@ -0,0 +1,54 @@
package org.insa.graphs.gui;
import java.io.IOException;
import java.io.OutputStream;
import javax.swing.JTextArea;
public class StreamCapturer extends OutputStream {
private StringBuilder buffer;
private String prefix = null;
private JTextArea output;
/**
* @param output Output JTextArea to which this stream should print.
* @param prefix Prefix to add to each line of this output.
*/
public StreamCapturer(JTextArea output, String prefix) {
this.prefix = prefix;
buffer = new StringBuilder(128);
this.output = output;
}
/**
* Create a new StreamCapturer without prefix.
*
* @param output Output JTextArea to which this stream should print.
*/
public StreamCapturer(JTextArea output) {
this(output, null);
}
@Override
public void write(int b) throws IOException {
char c = (char) b;
String value = Character.toString(c);
buffer.append(value);
if (value.equals("\n")) {
output.append(getPrefix() + buffer.toString());
output.setCaretPosition(output.getText().length());
buffer.delete(0, buffer.length());
}
}
/**
* @return Formatted prefix, or empty string if no prefix is set.
*/
public String getPrefix() {
if (this.prefix == null) {
return "";
}
return "[" + prefix + "] ";
}
}

View file

@ -0,0 +1,62 @@
package org.insa.graphs.gui;
import java.time.Duration;
import java.time.Instant;
public class ThreadWrapper implements RunningAction {
// Thread hold by this wrapper.
private Thread thread;
// Starting time of the thread.
Instant startingTime;
// MainWindow
private MainWindow mainWindow;
public ThreadWrapper(MainWindow mainWindow) {
this.thread = null;
this.mainWindow = mainWindow;
}
public void setThread(Thread thread) {
this.thread = thread;
}
public void startThread() {
this.startingTime = Instant.now();
this.thread.start();
}
public Thread getThread() {
return this.thread;
}
@Override
public boolean isRunning() {
return thread != null && thread.isAlive();
}
@SuppressWarnings("deprecation")
@Override
public void interrupt() {
thread.stop();
this.mainWindow.clearCurrentThread();
}
@Override
public Instant getStartingTime() {
return startingTime;
}
@Override
public Duration getDuration() {
return Duration.between(getStartingTime(), Instant.now());
}
@Override
public String getInformation() {
return getClass().getName();
}
}

View file

@ -0,0 +1,82 @@
package org.insa.graphs.gui.drawing;
import java.awt.Color;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.RoadInformation.RoadType;
public class BasicGraphPalette implements GraphPalette {
// Color types for arc.
private static final Color MOTORWAY_COLOR = Color.RED;
private static final Color BIG_ROAD_COLOR = new Color(255, 105, 0);
private static final Color SMALL_ROAD_COLOR = new Color(255, 200, 0);
private static final Color COASTLINE_COLOR = Color.BLUE;
@Override
public Color getColorForArc(Arc arc) {
RoadType type = arc.getRoadInformation().getType();
switch (type) {
case MOTORWAY:
return MOTORWAY_COLOR;
case TRUNK:
case PRIMARY:
case SECONDARY:
case MOTORWAY_LINK:
case TRUNK_LINK:
case PRIMARY_LINK:
return BIG_ROAD_COLOR;
case SECONDARY_LINK:
case TERTIARY:
case RESIDENTIAL:
case UNCLASSIFIED:
case LIVING_STREET:
case SERVICE:
case ROUNDABOUT:
case PEDESTRIAN:
case CYCLEWAY:
case TRACK:
return SMALL_ROAD_COLOR;
case COASTLINE:
return COASTLINE_COLOR;
}
return Color.BLACK;
}
@Override
public int getWidthForArc(Arc arc) {
RoadType type = arc.getRoadInformation().getType();
int width = 1;
switch (type) {
case MOTORWAY:
width = 2;
break;
case TRUNK:
case PRIMARY:
case SECONDARY:
case MOTORWAY_LINK:
case TRUNK_LINK:
case PRIMARY_LINK:
width = 1;
break;
case SECONDARY_LINK:
case TERTIARY:
case RESIDENTIAL:
case UNCLASSIFIED:
case LIVING_STREET:
case SERVICE:
case ROUNDABOUT:
case PEDESTRIAN:
case CYCLEWAY:
case TRACK:
width = 1;
break;
case COASTLINE:
width = 4;
break;
}
return width;
}
}

View file

@ -0,0 +1,19 @@
package org.insa.graphs.gui.drawing;
import java.awt.Color;
import org.insa.graphs.model.Arc;
public class BlackAndWhiteGraphPalette extends BasicGraphPalette {
// Road colors (index
private final static Color[] ROAD_COLOR_FROM_WIDTH = { null, new Color(140, 140, 140),
new Color(80, 80, 80), new Color(40, 40, 40), new Color(30, 30, 30) };
@Override
public Color getColorForArc(Arc arc) {
int width = getWidthForArc(arc);
return ROAD_COLOR_FROM_WIDTH[width];
}
}

View file

@ -0,0 +1,157 @@
package org.insa.graphs.gui.drawing;
import java.awt.Color;
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.Point;
public interface Drawing {
/**
* Available fill mode for the creation of markers, see the documentation of
* each value for more details.
*/
enum AlphaMode {
/**
* Do not use the original transparency of the inner part to fill it.
*/
OPAQUE,
/**
* Use the original transparency of the inner part to fill it.
*/
TRANSPARENT
}
/**
* Add a listener to click to this drawing.
*
* @param listener DrawingClickListener to add to this Drawing.
*/
public void addDrawingClickListener(DrawingClickListener listener);
/**
* Remove the given listener from the drawing.
*
* @param listener DrawingClickListener to remove from this Drawing.
*/
public void removeDrawingClickListener(DrawingClickListener listener);
/**
* Clear the drawing (overlays and underlying graph/map).
*/
public void clear();
/**
* Remove overlays from the drawing (do not remove the underlying graph/map).
*/
public void clearOverlays();
/**
* Draw a marker at the given position using the given colors and according to
* the given mode.
*
* @param point Position of the marker to draw.
* @param outer Color for the outer part of the marker to draw.
* @param inner Color for the inner part of the marker to draw.
* @param mode Mode for filling the inner par of the marker.
*
* @return A MarkerOverlay instance representing the newly drawn marker.
*/
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode);
/**
* Create a new PointSetOverlay that can be used to add overlay points to this
* drawing.
*
* PointSetOverlay are heavy memory resources, do not use one for each point!
*
* @return A new PointSetOverlay for this drawing.
*/
public PointSetOverlay createPointSetOverlay();
/**
* Create a new PointSetOverlay with the given initial width and color that can
* be used to add overlay points to this drawing.
*
* PointSetOverlay are heavy memory resources, do not use one for each point!
*
* @param width Initial width of points in the overlay.
* @param color Initial width of points in the overlay.
*
* @return A new PointSetOverlay for this drawing.
*/
public PointSetOverlay createPointSetOverlay(int width, Color color);
/**
* Draw the given graph using the given palette.
*
* @param graph Graph to draw.
* @param palette Palette to use to draw the graph.
*
* @see BasicGraphPalette
* @see BlackAndWhiteGraphPalette
*/
public void drawGraph(Graph graph, GraphPalette palette);
/**
* Draw the given graph using a default palette specific to the implementation.
*
* @param graph Graph to draw.
*/
public void drawGraph(Graph graph);
/**
* Draw a path using the given color.
*
* @param path Path to draw.
* @param color Color of the path to draw.
* @param markers true to show origin and destination markers.
*
* @return A PathOverlay instance representing the newly drawn path.
*/
public PathOverlay drawPath(Path path, Color color, boolean markers);
/**
* Draw a path with both origin and destination markers using the given color.
*
* @param path Path to draw.
* @param color Color of the path to draw.
*
* @return A PathOverlay instance representing the newly drawn path.
*
* @see Drawing#drawPath(Path, Color, boolean)
*/
public PathOverlay drawPath(Path path, Color color);
/**
* Draw a path using a default color specific to the implementation
*
* @param path Path to draw.
* @param markers true to show origin and destination markers.
*
* @return A PathOverlay instance representing the newly drawn path.
*
* @see Drawing#drawPath(Path, Color, boolean)
*/
public PathOverlay drawPath(Path path, boolean markers);
/**
* Draw a path with both origin and destination markers using a default color
* specific to the implementation
*
*
* @param path Path to draw.
*
* @return A PathOverlay instance representing the newly drawn path.
*
* @see Drawing#drawPath(Path, Color, boolean)
*/
public PathOverlay drawPath(Path path);
}

View file

@ -0,0 +1,14 @@
package org.insa.graphs.gui.drawing;
import org.insa.graphs.model.Point;
public interface DrawingClickListener {
/**
* Event triggered when a click is made on the map.
*
* @param point Position (on the map) of the mouse click.
*/
public void mouseClicked(Point point);
}

View file

@ -0,0 +1,23 @@
package org.insa.graphs.gui.drawing;
import java.awt.Color;
import org.insa.graphs.model.Arc;
public interface GraphPalette {
/**
* @param arc Arc for which color should be retrieved.
*
* @return Color associated with the given arc.
*/
public Color getColorForArc(Arc arc);
/**
* @param arc Arc for which width should be retrieved.
*
* @return Width associated with the given arc.
*/
public int getWidthForArc(Arc arc);
}

View file

@ -0,0 +1,116 @@
package org.insa.graphs.gui.drawing;
import java.awt.Dimension;
import org.insa.graphs.model.GraphStatistics.BoundingBox;
public class MercatorProjection implements Projection {
public static final double MAX_LATITUDE = 82;
public static final double MIN_LATITUDE = -MAX_LATITUDE;
// From Wikipedia... for the above max/min latitude.
private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746;
private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE);
private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE);
// Bounding box
private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
// Projection of min and max latitude.
private final double minLatitudeProj, maxLatitudeProj;
// Dimension of the image
private final double width, height;
/**
* Create a new MercatorProjection corresponding to the given BoundingBox and
* maxSize.
*
* @param boundingBox Box for this projection.
* @param maxSize Maximum size of any side (width / height) of the image to
* which this projection should draw.
*/
public MercatorProjection(BoundingBox boundingBox, int maxSize) {
// Find minimum/maximum longitude and latitude.
this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
// Compute projection
this.minLatitudeProj = projectY(this.minLatitude);
this.maxLatitudeProj = projectY(this.maxLatitude);
Dimension imageDimension = computeImageSize(maxSize);
this.width = imageDimension.getWidth();
this.height = imageDimension.getHeight();
}
/**
* Compute the projection (without scaling) of the given latitude.
*
* @param latitude Latitude to project.
*
* @return Projection of the given latitude (without scaling).
*/
private static double projectY(double latitude) {
double sinLatitude = Math.sin(latitude * Math.PI / 180.0);
return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2;
}
/**
* Compute the dimension required for drawing a projection of the given box on
* an image, ensuring that none of the side of image is greater than maxSize.
*
* @param maxSize Maximum side of any side of the image.
*
* @return Dimension corresponding to the preferred size for the image.
*/
protected Dimension computeImageSize(int maxSize) {
double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0;
double propHeight = (this.maxLatitudeProj - this.minLatitudeProj)
/ (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT;
return propWidth < propHeight
? new Dimension((int) (maxSize * propWidth / propHeight), maxSize)
: new Dimension(maxSize, (int) (maxSize * propHeight / propWidth));
}
@Override
public double getImageWidth() {
return this.width;
}
@Override
public double getImageHeight() {
return this.height;
}
@Override
public int latitudeToPixelY(float latitude) {
return (int) ((this.maxLatitudeProj - projectY(latitude))
/ (this.maxLatitudeProj - this.minLatitudeProj) * this.height);
}
@Override
public int longitudeToPixelX(float longitude) {
return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude));
}
@Override
public float pixelYToLatitude(double py) {
float y = (float) (this.maxLatitudeProj
- (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj));
return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI);
}
@Override
public float pixelXToLongitude(double px) {
return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude)
+ this.minLongitude);
}
}

View file

@ -0,0 +1,68 @@
package org.insa.graphs.gui.drawing;
import org.insa.graphs.model.GraphStatistics.BoundingBox;
public class PlateCarreProjection implements Projection {
// Bounding box
private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
// Dimension of the image
private final double width, height;
/**
* Create a new PlateCarreProjection corresponding to the given BoundingBox and
* maxSize.
*
* @param boundingBox Box for this projection.
* @param maxSize Maximum size of any side (width / height) of the image to
* which this projection should draw.
*/
public PlateCarreProjection(BoundingBox boundingBox, int maxSize) {
// Find minimum/maximum longitude and latitude.
this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
float diffLon = maxLongitude - minLongitude, diffLat = maxLatitude - minLatitude;
this.width = diffLon < diffLat ? (int) (maxSize * diffLon / diffLat) : maxSize;
this.height = diffLon < diffLat ? maxSize : (int) (maxSize * diffLat / diffLon);
}
@Override
public double getImageWidth() {
return this.width;
}
@Override
public double getImageHeight() {
return this.height;
}
@Override
public int latitudeToPixelY(float latitude) {
return (int) (this.height * (this.maxLatitude - latitude)
/ (this.maxLatitude - this.minLatitude));
}
@Override
public int longitudeToPixelX(float longitude) {
return (int) (this.width * (longitude - this.minLongitude)
/ (this.maxLongitude - this.minLongitude));
}
@Override
public float pixelYToLatitude(double py) {
return (float) (this.maxLatitude
- py / this.height * (this.maxLatitude - this.minLatitude));
}
@Override
public float pixelXToLongitude(double px) {
return (float) (px / this.width * (this.maxLongitude - this.minLongitude)
+ this.minLongitude);
}
}

View file

@ -0,0 +1,51 @@
package org.insa.graphs.gui.drawing;
public interface Projection {
/**
* @return Image width for this projection to work properly.
*/
public double getImageWidth();
/**
* @return Image weight for this projection to work properly.
*/
public double getImageHeight();
/**
* Project the given latitude on the image.
*
* @param latitude Latitude to project.
*
* @return Projected position of the latitude on the image.
*/
public int latitudeToPixelY(float latitude);
/**
* Project the given longitude on the image.
*
* @param longitude Longitude to project.
*
* @return Projected position of the longitude on the image.
*/
public int longitudeToPixelX(float longitude);
/**
* Retrieve the latitude associated to the given projected point.
*
* @param py Projected y-position for which latitude should be retrieved.
*
* @return The original latitude of the point.
*/
public float pixelYToLatitude(double py);
/**
* Retrieve the longitude associated to the given projected point.
*
* @param px Projected x-position for which longitude should be retrieved.
*
* @return The original longitude of the point.
*/
public float pixelXToLongitude(double px);
}

View file

@ -0,0 +1,747 @@
package org.insa.graphs.gui.drawing.components;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JPanel;
import org.insa.graphs.gui.drawing.BasicGraphPalette;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.DrawingClickListener;
import org.insa.graphs.gui.drawing.GraphPalette;
import org.insa.graphs.gui.drawing.MercatorProjection;
import org.insa.graphs.gui.drawing.PlateCarreProjection;
import org.insa.graphs.gui.drawing.Projection;
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
import org.insa.graphs.gui.drawing.overlays.Overlay;
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
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.Point;
import org.insa.graphs.model.GraphStatistics.BoundingBox;
/**
*
*/
public class BasicDrawing extends JPanel implements Drawing {
/**
*
*/
private static final long serialVersionUID = 96779785877771827L;
private abstract class BasicOverlay implements Overlay {
// Visible?
protected boolean visible;
// Color
protected Color color;
public BasicOverlay(Color color) {
this.visible = true;
this.color = color;
}
/**
* @return The Z level of this overlay (>= 1).
*/
public abstract int getZLevel();
@Override
public void setColor(Color color) {
this.color = color;
}
@Override
public Color getColor() {
return this.color;
}
@Override
public void setVisible(boolean visible) {
this.visible = visible;
BasicDrawing.this.repaint();
}
@Override
public boolean isVisible() {
return this.visible;
}
@Override
public void delete() {
BasicDrawing.this.overlays.remove(this);
}
/**
* Draw the given overlay.
*/
public void draw(Graphics2D g) {
if (this.visible) {
drawImpl(g);
}
}
public abstract void drawImpl(Graphics2D g);
public void redraw() {
BasicDrawing.this.repaint();
}
};
private class BasicMarkerOverlay extends BasicOverlay implements MarkerOverlay {
// Marker width and height
public static final int MARKER_WIDTH = 30, MARKER_HEIGHT = 60;
// Point of the marker.
private Point point;
// Image to draw
private Image image;
// Inner color and fill mode.
private Color innerColor;
private final AlphaMode alphaMode;
public BasicMarkerOverlay(Point point, Color color, Color inner, AlphaMode alphaMode) {
super(color);
this.point = point;
this.image = MarkerUtils.getMarkerForColor(color, inner, alphaMode);
this.innerColor = inner;
this.alphaMode = alphaMode;
}
public int getZLevel() {
return 3;
}
@Override
public Point getPoint() {
return point;
}
@Override
public void setColor(Color color) {
this.innerColor = this.innerColor.equals(this.color) ? color : innerColor;
super.setColor(color);
this.image = MarkerUtils.getMarkerForColor(color, this.innerColor, alphaMode);
}
@Override
public void moveTo(Point point) {
this.point = point;
BasicDrawing.this.repaint();
}
@Override
public void drawImpl(Graphics2D graphics) {
int px = projection.longitudeToPixelX(getPoint().getLongitude());
int py = projection.latitudeToPixelY(getPoint().getLatitude());
graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
MARKER_HEIGHT, BasicDrawing.this);
}
};
private class BasicPathOverlay extends BasicOverlay implements PathOverlay {
// List of points
private final List<Point> points;
// Origin / Destination markers.
private BasicMarkerOverlay origin, destination;
public BasicPathOverlay(List<Point> points, Color color, BasicMarkerOverlay origin,
BasicMarkerOverlay destination) {
super(color);
this.points = points;
this.origin = origin;
this.destination = destination;
this.color = color;
}
public int getZLevel() {
return 2;
}
@Override
public void setColor(Color color) {
super.setColor(color);
this.origin.setColor(color);
this.destination.setColor(color);
}
@Override
public void drawImpl(Graphics2D graphics) {
if (!points.isEmpty()) {
graphics.setStroke(new BasicStroke(2));
graphics.setColor(getColor());
Iterator<Point> itPoint = points.iterator();
Point prev = itPoint.next();
while (itPoint.hasNext()) {
Point curr = itPoint.next();
int x1 = projection.longitudeToPixelX(prev.getLongitude());
int x2 = projection.longitudeToPixelX(curr.getLongitude());
int y1 = projection.latitudeToPixelY(prev.getLatitude());
int y2 = projection.latitudeToPixelY(curr.getLatitude());
graphics.drawLine(x1, y1, x2, y2);
prev = curr;
}
}
if (this.origin != null) {
this.origin.draw(graphics);
}
if (this.destination != null) {
this.destination.draw(graphics);
}
}
};
private class BasicPointSetOverlay extends BasicOverlay implements PointSetOverlay {
// Default point width
private static final int DEFAULT_POINT_WIDTH = 5;
// Image for path / points
private final BufferedImage image;
private final Graphics2D graphics;
private int width = DEFAULT_POINT_WIDTH;
public BasicPointSetOverlay() {
super(Color.BLACK);
this.image = new BufferedImage(BasicDrawing.this.width, BasicDrawing.this.height,
BufferedImage.TYPE_4BYTE_ABGR);
this.graphics = image.createGraphics();
this.graphics.setBackground(new Color(0, 0, 0, 0));
}
public int getZLevel() {
return 1;
}
@Override
public void setColor(Color color) {
super.setColor(color);
this.graphics.setColor(color);
}
@Override
public void setWidth(int width) {
this.width = Math.max(2, width);
}
@Override
public void setWidthAndColor(int width, Color color) {
setWidth(width);
setColor(color);
}
@Override
public void addPoint(Point point) {
int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
this.graphics.fillOval(x, y, this.width, this.width);
BasicDrawing.this.repaint();
}
@Override
public void addPoint(Point point, int width) {
setWidth(width);
addPoint(point);
}
@Override
public void addPoint(Point point, Color color) {
setColor(color);
addPoint(point);
}
@Override
public void addPoint(Point point, int width, Color color) {
setWidth(width);
setColor(color);
addPoint(point);
}
@Override
public void drawImpl(Graphics2D g) {
g.drawImage(this.image, 0, 0, BasicDrawing.this);
}
}
/**
* Class encapsulating a set of overlays.
*
*/
private class BasicOverlays {
// List of overlays.
private ArrayList<ArrayList<BasicOverlay>> overlays = new ArrayList<>();
public synchronized void draw(Graphics2D g) {
// Clear overlays.
for (ArrayList<BasicOverlay> arr: this.overlays) {
for (BasicOverlay overlay: arr) {
overlay.draw(g);
}
}
}
public synchronized void remove(BasicOverlay overlay) {
overlays.get(overlay.getZLevel() - 1).remove(overlay);
BasicDrawing.this.repaint();
}
public void clear() {
clear(true);
}
public void clear(boolean repaint) {
// Clear overlays.
for (ArrayList<BasicOverlay> arr: this.overlays) {
arr.clear();
}
// Repaint if requested.
if (repaint) {
BasicDrawing.this.repaint();
}
}
public BasicOverlay add(BasicOverlay marker) {
return add(marker, true);
}
public synchronized BasicOverlay add(BasicOverlay overlay, boolean repaint) {
// Check if we have a level for this...
for (int i = overlays.size(); i < overlay.getZLevel(); ++i) {
overlays.add(new ArrayList<>());
}
// Add overlay to the given list.
overlays.get(overlay.getZLevel() - 1).add(overlay);
// Repaint if requested.
if (repaint) {
BasicDrawing.this.repaint();
}
return overlay;
}
};
// Default path color.
public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
// Default palette.
public static final GraphPalette DEFAULT_PALETTE = new BasicGraphPalette();
// Maximum width for the drawing (in pixels).
private static final int MAXIMUM_DRAWING_WIDTH = 2000;
private Projection projection;
// Width and height of the image
private int width, height;
// Zoom controls
private MapZoomControls zoomControls;
private ZoomAndPanListener zoomAndPanListener;
//
private Image graphImage = null;
private Graphics2D graphGraphics = null;
// List of image for markers
private BasicOverlays overlays = new BasicOverlays();
// Mapping DrawingClickListener -> MouseEventListener
private List<DrawingClickListener> drawingClickListeners = new ArrayList<>();
/**
* Create a new BasicDrawing.
*
*/
public BasicDrawing() {
setLayout(null);
this.setBackground(new Color(245, 245, 245));
this.zoomAndPanListener = new ZoomAndPanListener(this,
ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2);
// Try...
try {
this.zoomControls = new MapZoomControls(this, 0,
ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20);
this.zoomControls.addZoomInListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
zoomAndPanListener.zoomIn();
}
});
this.zoomControls.addZoomOutListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
zoomAndPanListener.zoomOut();
}
});
}
catch (IOException e) {
e.printStackTrace();
}
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent evt) {
if (zoomControls.contains(evt.getPoint())) {
return;
}
Point lonlat = null;
try {
lonlat = getLongitudeLatitude(evt);
}
catch (NoninvertibleTransformException e) {
return;
}
for (DrawingClickListener listener: drawingClickListeners) {
listener.mouseClicked(lonlat);
}
}
});
}
@Override
public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1;
AffineTransform sTransform = g.getTransform();
g.setColor(this.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setTransform(zoomAndPanListener.getCoordTransform());
if (graphImage != null) {
// Draw graph
g.drawImage(graphImage, 0, 0, this);
}
// Draw markers
this.overlays.draw(g);
g.setTransform(sTransform);
if (this.zoomControls != null) {
this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel());
this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20,
this.getHeight() - this.zoomControls.getHeight() - 10, this);
}
}
/*
* (non-Javadoc)
*
* @see org.insa.graphics.drawing.Drawing#clear()
*/
@Override
public void clear() {
if (this.graphGraphics != null) {
this.graphGraphics.clearRect(0, 0, this.width, this.height);
}
this.overlays.clear(false);
this.repaint();
}
/*
* (non-Javadoc)
*
* @see org.insa.graphics.drawing.Drawing#clearOverlays()
*/
@Override
public void clearOverlays() {
this.overlays.clear();
}
/**
* @return The current ZoomAndPanListener associated with this drawing.
*/
public ZoomAndPanListener getZoomAndPanListener() {
return this.zoomAndPanListener;
}
/**
* Return the longitude and latitude corresponding to the given position of the
* MouseEvent.
*
* @param event MouseEvent from which longitude/latitude should be retrieved.
*
* @return Point representing the projection of the MouseEvent position in the
* graph/map.
*
* @throws NoninvertibleTransformException if the actual transformation is
* invalid.
*/
protected Point getLongitudeLatitude(MouseEvent event) throws NoninvertibleTransformException {
// Get the point using the inverse transform of the Zoom/Pan object, this gives
// us
// a point within the drawing box (between [0, 0] and [width, height]).
Point2D ptDst = this.zoomAndPanListener.getCoordTransform()
.inverseTransform(event.getPoint(), null);
// Inverse the "projection" on x/y to get longitude and latitude.
return new Point(projection.pixelXToLongitude(ptDst.getX()),
projection.pixelYToLatitude(ptDst.getY()));
}
/*
* (non-Javadoc)
*
* @see
* org.insa.graphics.drawing.Drawing#addDrawingClickListener(org.insa.graphics.
* drawing.DrawingClickListener)
*/
@Override
public void addDrawingClickListener(DrawingClickListener listener) {
this.drawingClickListeners.add(listener);
}
/*
* (non-Javadoc)
*
* @see org.insa.graphics.drawing.Drawing#removeDrawingClickListener(org.insa.
* graphics.drawing.DrawingClickListener)
*/
@Override
public void removeDrawingClickListener(DrawingClickListener listener) {
this.drawingClickListeners.remove(listener);
}
public BasicMarkerOverlay createMarker(Point point, Color outer, Color inner, AlphaMode mode) {
return new BasicMarkerOverlay(point, outer, inner, mode);
}
@Override
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
return (MarkerOverlay) this.overlays.add(createMarker(point, outer, inner, mode));
}
@Override
public PointSetOverlay createPointSetOverlay() {
return (PointSetOverlay) this.overlays.add(new BasicPointSetOverlay(), false);
}
@Override
public PointSetOverlay createPointSetOverlay(int width, Color color) {
PointSetOverlay ps = createPointSetOverlay();
ps.setWidthAndColor(width, color);
return ps;
}
/**
* Draw the given arc.
*
* @param arc Arc to draw.
* @param palette Palette to use to retrieve color and width for arc, or null to
* use current settings.
*/
protected void drawArc(Arc arc, GraphPalette palette, boolean repaint) {
List<Point> pts = arc.getPoints();
if (!pts.isEmpty()) {
if (palette != null) {
this.graphGraphics.setColor(palette.getColorForArc(arc));
this.graphGraphics.setStroke(new BasicStroke(palette.getWidthForArc(arc)));
}
Iterator<Point> it1 = pts.iterator();
Point prev = it1.next();
while (it1.hasNext()) {
Point curr = it1.next();
int x1 = projection.longitudeToPixelX(prev.getLongitude());
int x2 = projection.longitudeToPixelX(curr.getLongitude());
int y1 = projection.latitudeToPixelY(prev.getLatitude());
int y2 = projection.latitudeToPixelY(curr.getLatitude());
graphGraphics.drawLine(x1, y1, x2, y2);
prev = curr;
}
}
if (repaint) {
this.repaint();
}
}
/**
* Initialize the drawing for the given graph.
*
* @param graph
*/
protected void initialize(Graph graph) {
// Clear everything.
this.clear();
BoundingBox box = graph.getGraphInformation().getBoundingBox();
// Find minimum/maximum longitude and latitude.
float minLon = box.getTopLeftPoint().getLongitude(),
maxLon = box.getBottomRightPoint().getLongitude(),
minLat = box.getBottomRightPoint().getLatitude(),
maxLat = box.getTopLeftPoint().getLatitude();
// Add a little delta to avoid drawing on the edge...
float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
// Create the projection and retrieve width and height for the box.
BoundingBox extendedBox = box.extend(deltaLon, deltaLat, deltaLon, deltaLat);
// Special projection for non-realistic maps...
if (graph.getMapId().startsWith("0x")) {
projection = new PlateCarreProjection(extendedBox, MAXIMUM_DRAWING_WIDTH / 4);
}
else {
projection = new MercatorProjection(extendedBox, MAXIMUM_DRAWING_WIDTH);
}
this.width = (int) projection.getImageWidth();
this.height = (int) projection.getImageHeight();
// Create the image
BufferedImage img = new BufferedImage(this.width, this.height,
BufferedImage.TYPE_3BYTE_BGR);
this.graphImage = img;
this.graphGraphics = img.createGraphics();
this.graphGraphics.setBackground(this.getBackground());
this.graphGraphics.clearRect(0, 0, this.width, this.height);
// Set the zoom and pan listener
double scale = 1 / Math.max(this.width / (double) this.getWidth(),
this.height / (double) this.getHeight());
this.zoomAndPanListener.setCoordTransform(this.graphGraphics.getTransform());
this.zoomAndPanListener.getCoordTransform().translate(
(this.getWidth() - this.width * scale) / 2,
(this.getHeight() - this.height * scale) / 2);
this.zoomAndPanListener.getCoordTransform().scale(scale, scale);
this.zoomAndPanListener.setZoomLevel(0);
this.zoomControls.setZoomLevel(0);
// Repaint
this.repaint();
}
@Override
public void drawGraph(Graph graph, GraphPalette palette) {
int repaintModulo = Math.max(1, graph.size() / 100);
// Initialize the buffered image
this.initialize(graph);
// Remove zoom and pan listener
this.removeMouseListener(zoomAndPanListener);
this.removeMouseMotionListener(zoomAndPanListener);
this.removeMouseWheelListener(zoomAndPanListener);
for (Node node: graph.getNodes()) {
for (Arc arc: node.getSuccessors()) {
// Draw arcs only if there are one-way arcs or if origin is lower than
// destination, avoid drawing two-ways arc twice.
if (arc.getRoadInformation().isOneWay()
|| arc.getOrigin().compareTo(arc.getDestination()) < 0) {
drawArc(arc, palette, false);
}
}
if (node.getId() % repaintModulo == 0) {
this.repaint();
}
}
this.repaint();
// Re-add zoom and pan listener
this.addMouseListener(zoomAndPanListener);
this.addMouseMotionListener(zoomAndPanListener);
this.addMouseWheelListener(zoomAndPanListener);
}
@Override
public void drawGraph(Graph graph) {
drawGraph(graph, DEFAULT_PALETTE);
}
@Override
public PathOverlay drawPath(Path path, Color color, boolean markers) {
List<Point> points = new ArrayList<Point>();
if (!path.isEmpty()) {
points.add(path.getOrigin().getPoint());
for (Arc arc: path.getArcs()) {
Iterator<Point> itPoint = arc.getPoints().iterator();
// Discard origin each time
itPoint.next();
while (itPoint.hasNext()) {
points.add(itPoint.next());
}
}
}
BasicMarkerOverlay origin = null, destination = null;
if (markers && !path.isEmpty()) {
origin = createMarker(path.getOrigin().getPoint(), color, color, AlphaMode.TRANSPARENT);
destination = createMarker(path.getDestination().getPoint(), color, color,
AlphaMode.TRANSPARENT);
}
return (PathOverlay) this.overlays
.add(new BasicPathOverlay(points, color, origin, destination));
}
@Override
public PathOverlay drawPath(Path path, Color color) {
return drawPath(path, color, true);
}
@Override
public PathOverlay drawPath(Path path) {
return drawPath(path, DEFAULT_PATH_COLOR);
}
@Override
public PathOverlay drawPath(Path path, boolean markers) {
return drawPath(path, DEFAULT_PATH_COLOR, markers);
}
}

View file

@ -0,0 +1,521 @@
package org.insa.graphs.gui.drawing.components;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.DrawingClickListener;
import org.insa.graphs.gui.drawing.GraphPalette;
import org.insa.graphs.gui.drawing.overlays.MarkerAutoScaling;
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
import org.insa.graphs.gui.drawing.overlays.Overlay;
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
import org.insa.graphs.gui.drawing.overlays.PolylineAutoScaling;
import org.insa.graphs.model.Arc;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.Point;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.core.util.Parameters;
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
import org.mapsforge.map.awt.util.AwtUtil;
import org.mapsforge.map.awt.view.MapView;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.layer.Layer;
import org.mapsforge.map.layer.Layers;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.hills.HillsRenderConfig;
import org.mapsforge.map.layer.overlay.Marker;
import org.mapsforge.map.layer.overlay.Polygon;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.model.DisplayModel;
import org.mapsforge.map.model.IMapViewPosition;
import org.mapsforge.map.model.Model;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;
/**
*
*/
public class MapViewDrawing extends MapView implements Drawing {
/**
*
*/
private static final long serialVersionUID = 8606967833704938092L;
/**
* Base Overlay for MapViewDrawing overlays.
*
*/
private abstract class MapViewOverlay implements Overlay {
// Marker associated.
protected Layer[] layers;
// Current color
protected Color color;
public MapViewOverlay(Layer[] layers, Color color) {
this.layers = layers;
for (Layer layer: this.layers) {
MapViewDrawing.this.getLayerManager().getLayers().add(layer);
}
this.color = color;
}
@Override
public void setColor(Color color) {
this.color = color;
}
@Override
public Color getColor() {
return this.color;
}
@Override
public void setVisible(boolean visible) {
for (Layer layer: layers) {
layer.setVisible(visible);
}
}
@Override
public boolean isVisible() {
if (this.layers.length == 0) {
return true;
}
return this.layers[0].isVisible();
}
@Override
public void delete() {
Layers mlayers = MapViewDrawing.this.getLayerManager().getLayers();
for (Layer layer: layers) {
mlayers.remove(layer);
}
}
@Override
public void redraw() {
MapViewDrawing.this.getLayerManager().redrawLayers();
}
};
/**
* MarkerOverlay for MapViewDrawing.
*
*/
private class MapViewMarkerOverlay extends MapViewOverlay implements MarkerOverlay {
private final AlphaMode alphaMode;
private Color innerColor;
public MapViewMarkerOverlay(Marker marker, Color outer, Color innerColor,
AlphaMode alphaMode) {
super(new Layer[] { marker }, outer);
this.innerColor = innerColor;
this.alphaMode = alphaMode;
}
@Override
public Point getPoint() {
Marker marker = (Marker) super.layers[0];
return new Point((float) marker.getLatLong().getLongitude(),
(float) marker.getLatLong().getLatitude());
}
@Override
public void setColor(Color outer) {
this.innerColor = this.innerColor.equals(this.color) ? outer : this.innerColor;
super.setColor(color);
MarkerAutoScaling marker = (MarkerAutoScaling) super.layers[0];
marker.setImage(MarkerUtils.getMarkerForColor(color, this.innerColor, this.alphaMode));
}
@Override
public void moveTo(Point point) {
MarkerAutoScaling marker = (MarkerAutoScaling) this.layers[0];
this.delete();
marker = new MarkerAutoScaling(convertPoint(point), marker.getImage());
this.layers[0] = marker;
MapViewDrawing.this.getLayerManager().getLayers().add(marker);
}
};
/**
* PathOverlay for MapViewDrawing.
*
*/
private class MapViewPathOverlay extends MapViewOverlay implements PathOverlay {
public MapViewPathOverlay(PolylineAutoScaling path, MarkerAutoScaling origin,
MarkerAutoScaling destination) {
super(new Layer[] { path, origin, destination }, path.getColor());
}
public MapViewPathOverlay(PolylineAutoScaling path) {
super(new Layer[] { path }, path.getColor());
}
@Override
public void setColor(Color color) {
super.setColor(color);
((PolylineAutoScaling) this.layers[0]).setColor(color);
((MarkerAutoScaling) this.layers[1])
.setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
((MarkerAutoScaling) this.layers[2])
.setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
}
}
/**
* PointSetOverlay for MapViewDrawing - Not currently implemented.
*
*/
private class MapViewPointSetOverlay extends MapViewOverlay implements PointSetOverlay {
private List<Point> points = new ArrayList<>();
private final Polygon polygon;
private List<Point> convexHull(List<Point> p) {
if (p.isEmpty()) {
return new ArrayList<>();
}
p.sort((p1, p2) -> Float.compare(p1.getLongitude(), p2.getLongitude()));
List<Point> h = new ArrayList<>();
// lower hull
for (Point pt: p) {
while (h.size() >= 2 && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
h.remove(h.size() - 1);
}
h.add(pt);
}
// upper hull
int t = h.size() + 1;
for (int i = p.size() - 1; i >= 0; i--) {
Point pt = p.get(i);
while (h.size() >= t && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
h.remove(h.size() - 1);
}
h.add(pt);
}
h.remove(h.size() - 1);
return h;
}
// ccw returns true if the three points make a counter-clockwise turn
private boolean ccw(Point a, Point b, Point c) {
return ((b.getLongitude() - a.getLongitude())
* (c.getLatitude() - a.getLatitude())) > ((b.getLatitude() - a.getLatitude())
* (c.getLongitude() - a.getLongitude()));
}
public MapViewPointSetOverlay() {
super(new Layer[] { new Polygon(GRAPHIC_FACTORY.createPaint(), null, GRAPHIC_FACTORY) },
Color.BLACK);
polygon = (Polygon) this.layers[0];
}
@Override
public void setColor(Color color) {
super.setColor(color);
polygon.getPaintFill().setColor(GRAPHIC_FACTORY.createColor(100, color.getRed(),
color.getGreen(), color.getBlue()));
}
@Override
public void setWidth(int width) {
}
@Override
public void setWidthAndColor(int width, Color color) {
setWidth(width);
setColor(color);
}
@Override
public void addPoint(Point point) {
points.add(point);
this.points = convexHull(points);
polygon.setPoints(this.points.stream().map(MapViewDrawing.this::convertPoint)
.collect(Collectors.toList()));
polygon.requestRedraw();
}
@Override
public void addPoint(Point point, int width) {
setWidth(width);
addPoint(point);
}
@Override
public void addPoint(Point point, Color color) {
setColor(color);
addPoint(point);
}
@Override
public void addPoint(Point point, int width, Color color) {
setWidth(width);
setColor(color);
addPoint(point);
}
};
// Default path color.
public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
// Graphic factory.
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
// Default tile size.
private static final int DEFAULT_TILE_SIZE = 512;
// List of listeners.
private ArrayList<DrawingClickListener> drawingClickListeners = new ArrayList<>();
// Tile size
private int tileSize;
// Zoom controls
private MapZoomControls zoomControls;
public MapViewDrawing() {
super();
Parameters.NUMBER_OF_THREADS = 2;
Parameters.SQUARE_FRAME_BUFFER = false;
getMapScaleBar().setVisible(true);
DisplayModel model = getModel().displayModel;
this.tileSize = DEFAULT_TILE_SIZE;
model.setFixedTileSize(this.tileSize);
this.setZoomLevelMin((byte) 0);
this.setZoomLevelMax((byte) 20);
// Try...
try {
this.zoomControls = new MapZoomControls(this, 0, 0, 20);
this.zoomControls.addZoomInListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getModel().mapViewPosition.zoomIn();
}
});
this.zoomControls.addZoomOutListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getModel().mapViewPosition.zoomOut();
}
});
}
catch (IOException e) {
e.printStackTrace();
}
}
/*
* (non-Javadoc)
*
* @see org.mapsforge.map.awt.view.MapView#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics graphics) {
super.paint(graphics);
if (this.zoomControls != null) {
this.zoomControls.setZoomLevel(this.getModel().mapViewPosition.getZoomLevel());
this.zoomControls.draw((Graphics2D) graphics,
getWidth() - this.zoomControls.getWidth() - 20,
this.getHeight() - this.zoomControls.getHeight() - 10, this);
}
}
/*
* (non-Javadoc)
*
* @see org.insa.graphics.drawing.Drawing#clear()
*/
@Override
public void clear() {
getLayerManager().getLayers().clear();
repaint();
}
/*
* (non-Javadoc)
*
* @see org.insa.graphics.drawing.Drawing#clearOverlays()
*/
@Override
public void clearOverlays() {
Layers layers = getLayerManager().getLayers();
for (Layer layer: layers) {
if (layer instanceof PolylineAutoScaling || layer instanceof MarkerAutoScaling) {
getLayerManager().getLayers().remove(layer, false);
}
}
repaint();
}
protected LatLong convertPoint(Point point) {
return new LatLong(point.getLatitude(), point.getLongitude());
}
private TileRendererLayer createTileRendererLayer(TileCache tileCache,
MapDataStore mapDataStore, IMapViewPosition mapViewPosition,
HillsRenderConfig hillsRenderConfig) {
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapDataStore,
mapViewPosition, false, true, false, GRAPHIC_FACTORY, hillsRenderConfig) {
@Override
public boolean onTap(LatLong tapLatLong, org.mapsforge.core.model.Point layerXY,
org.mapsforge.core.model.Point tapXY) {
if (zoomControls.contains(new java.awt.Point((int) tapXY.x, (int) tapXY.y))) {
return false;
}
Point pt = new Point((float) tapLatLong.getLongitude(),
(float) tapLatLong.getLatitude());
for (DrawingClickListener listener: MapViewDrawing.this.drawingClickListeners) {
listener.mouseClicked(pt);
}
return true;
}
};
tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.DEFAULT);
return tileRendererLayer;
}
@Override
public void addDrawingClickListener(DrawingClickListener listener) {
this.drawingClickListeners.add(listener);
}
@Override
public void removeDrawingClickListener(DrawingClickListener listener) {
this.drawingClickListeners.remove(listener);
}
protected MarkerAutoScaling createMarker(Point point, Color outer, Color inner,
AlphaMode mode) {
Image image = MarkerUtils.getMarkerForColor(outer, inner, mode);
return new MarkerAutoScaling(convertPoint(point), image);
}
@Override
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
return new MapViewMarkerOverlay(createMarker(point, outer, inner, mode), outer, inner,
mode);
}
@Override
public PointSetOverlay createPointSetOverlay() {
return new MapViewPointSetOverlay();
}
@Override
public PointSetOverlay createPointSetOverlay(int width, Color color) {
PointSetOverlay ps = new MapViewPointSetOverlay();
ps.setWidthAndColor(width, color);
return ps;
}
public void drawGraph(File file) {
// Tile cache
TileCache tileCache = AwtUtil.createTileCache(tileSize,
getModel().frameBufferModel.getOverdrawFactor(), 1024,
new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()));
// Layers
Layers layers = getLayerManager().getLayers();
MapDataStore mapDataStore = new MapFile(file);
TileRendererLayer tileRendererLayer = createTileRendererLayer(tileCache, mapDataStore,
getModel().mapViewPosition, null);
layers.add(tileRendererLayer);
BoundingBox boundingBox = mapDataStore.boundingBox();
final Model model = getModel();
if (model.mapViewPosition.getZoomLevel() == 0
|| !boundingBox.contains(model.mapViewPosition.getCenter())) {
byte zoomLevel = LatLongUtils.zoomForBounds(model.mapViewDimension.getDimension(),
boundingBox, model.displayModel.getTileSize());
model.mapViewPosition
.setMapPosition(new MapPosition(boundingBox.getCenterPoint(), zoomLevel));
zoomControls.setZoomLevel(zoomLevel);
}
}
@Override
public void drawGraph(Graph graph, GraphPalette palette) {
throw new RuntimeException("Not implemented, use drawGraph(File).");
}
@Override
public void drawGraph(Graph graph) {
throw new RuntimeException("Not implemented, use drawGraph(File).");
}
@Override
public PathOverlay drawPath(Path path, Color color, boolean markers) {
PolylineAutoScaling line = new PolylineAutoScaling(1, color);
ArrayList<Point> points = new ArrayList<>(path.getArcs().size() * 4);
for (Arc arc: path.getArcs()) {
points.addAll(arc.getPoints());
}
line.addAll(points);
PathOverlay overlay = null;
if (markers) {
MarkerAutoScaling origin = createMarker(path.getOrigin().getPoint(), color, color,
AlphaMode.TRANSPARENT),
destination = createMarker(path.getDestination().getPoint(), color, color,
AlphaMode.TRANSPARENT);
overlay = new MapViewPathOverlay(line, origin, destination);
}
else {
overlay = new MapViewPathOverlay(line);
}
return overlay;
}
@Override
public PathOverlay drawPath(Path path, Color color) {
return drawPath(path, color, true);
}
@Override
public PathOverlay drawPath(Path path) {
return drawPath(path, DEFAULT_PATH_COLOR, true);
}
@Override
public PathOverlay drawPath(Path path, boolean markers) {
return drawPath(path, DEFAULT_PATH_COLOR, markers);
}
}

View file

@ -0,0 +1,209 @@
package org.insa.graphs.gui.drawing.components;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class MapZoomControls {
// Default ID for action events
private static final int ZOOM_IN_ACTION_ID = 0x1;
private static final String ZOOM_IN_ACTION_NAME = "ZoomIn";
private static final int ZOOM_OUT_ACTION_ID = 0x2;
private static final String ZOOM_OUT_ACTION_NAME = "ZoomOut";
// Height
private static final int DEFAULT_HEIGHT = 20;
// Default spacing between mark
private static final int DEFAULT_SPACING = 4;
// Zoom ticks ratio from height (not the current one)
private static final double ZOOM_TICK_HEIGHT_RATIO = 0.3;
// Zoom ticks color
private static final Color ZOOM_TICK_COLOR = Color.GRAY;
// Current zoom ticks ratio from height
private static final double CURRENT_ZOOM_TICK_HEIGHT_RATIO = 0.8;
// Zoom ticks color
private static final Color CURRENT_ZOOM_TICK_COLOR = new Color(25, 25, 25);
// Use half mark or not
private boolean halfMark = true;
private int currentLevel = 0;
private final int minLevel, maxLevel;
// Zoom in/out image and their rectangles.
private final Image zoomIn, zoomOut;
private final Rectangle zoomInRect = new Rectangle(0, 0, 0, 0),
zoomOutRect = new Rectangle(0, 0, 0, 0);
// List of listeners
private final List<ActionListener> zoomInListeners = new ArrayList<>();
private final List<ActionListener> zoomOutListeners = new ArrayList<>();
public MapZoomControls(Component component, final int defaultZoom, final int minZoom,
final int maxZoom) throws IOException {
zoomIn = ImageIO.read(getClass().getResourceAsStream("/zoomIn.png"))
.getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
zoomOut = ImageIO.read(getClass().getResourceAsStream("/zoomOut.png"))
.getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
this.currentLevel = defaultZoom;
this.minLevel = minZoom;
this.maxLevel = maxZoom;
component.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if (zoomInRect.contains(e.getPoint()) || zoomOutRect.contains(e.getPoint())) {
component.setCursor(new Cursor(Cursor.HAND_CURSOR));
}
else {
component.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
});
component.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (zoomInRect.contains(e.getPoint()) && currentLevel < maxLevel) {
currentLevel += 1;
for (ActionListener al: zoomInListeners) {
al.actionPerformed(
new ActionEvent(this, ZOOM_IN_ACTION_ID, ZOOM_IN_ACTION_NAME));
}
}
else if (zoomOutRect.contains(e.getPoint()) && currentLevel > minLevel) {
currentLevel -= 1;
for (ActionListener al: zoomOutListeners) {
al.actionPerformed(
new ActionEvent(this, ZOOM_OUT_ACTION_ID, ZOOM_OUT_ACTION_NAME));
}
}
component.repaint();
}
});
}
/**
* Add a zoom-in listener.
*
* @param listener Zoom-in listener to add to this MapZoomControls instance.
*/
public void addZoomInListener(ActionListener listener) {
this.zoomInListeners.add(listener);
}
/**
* Add a zoom-out listener.
*
* @param listener Zoom-out listener to add to this MapZoomControls instance.
*/
public void addZoomOutListener(ActionListener listener) {
this.zoomOutListeners.add(listener);
}
/**
* @return the current zoom level.
*/
public int getZoomLevel() {
return this.currentLevel;
}
/**
* Set the current zoom level without requesting a redraw.
*
* @param level Zoom level to set.
*/
public void setZoomLevel(int level) {
this.currentLevel = level;
}
/**
* @return Height of this "component" when drawn.
*/
public int getHeight() {
return DEFAULT_HEIGHT;
}
/**
* @return Width of this "component" when drawn.
*/
public int getWidth() {
return DEFAULT_HEIGHT + 2 + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2
+ DEFAULT_HEIGHT;
}
/**
* Check if a point is contained inside an element of this zoom controls, useful
* to avoid spurious click listeners.
*
* @param point Point to check.
*
* @return true if the given point correspond to an element of this zoom
* controls.
*/
public boolean contains(Point point) {
return zoomInRect.contains(point) || zoomOutRect.contains(point);
}
protected void draw(Graphics2D g, int xoffset, int yoffset, ImageObserver observer) {
int height = getHeight();
// Draw icon
g.drawImage(zoomOut, xoffset, yoffset, observer);
zoomOutRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
g.setStroke(new BasicStroke(1));
// Draw ticks
xoffset += DEFAULT_HEIGHT + 2;
g.setColor(ZOOM_TICK_COLOR);
g.drawLine(xoffset, yoffset + height / 2,
xoffset + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1,
yoffset + height / 2);
for (int i = 0; i <= (this.maxLevel - this.minLevel); i += halfMark ? 2 : 1) {
g.drawLine(xoffset + i * DEFAULT_SPACING,
yoffset + (int) (height * (1 - ZOOM_TICK_HEIGHT_RATIO) / 2),
xoffset + i * DEFAULT_SPACING,
yoffset + (int) (height * (1 + ZOOM_TICK_HEIGHT_RATIO) / 2));
}
// Draw current ticks
g.setColor(CURRENT_ZOOM_TICK_COLOR);
g.drawLine(xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
yoffset + (int) (height * (1 - CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2),
xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
yoffset + (int) (height * (1 + CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2));
xoffset += (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2;
g.drawImage(zoomIn, xoffset, yoffset, observer);
zoomInRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
}
}

View file

@ -0,0 +1,177 @@
package org.insa.graphs.gui.drawing.components;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener {
public static final int DEFAULT_MIN_ZOOM_LEVEL = -20;
public static final int DEFAULT_MAX_ZOOM_LEVEL = 10;
public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2;
private Component targetComponent;
private int zoomLevel = 0;
private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL;
private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL;
private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR;
private Point dragStartScreen;
private Point dragEndScreen;
private AffineTransform coordTransform = new AffineTransform();
public ZoomAndPanListener(Component targetComponent) {
this.targetComponent = targetComponent;
}
public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel,
double zoomMultiplicationFactor) {
this.targetComponent = targetComponent;
this.minZoomLevel = minZoomLevel;
this.maxZoomLevel = maxZoomLevel;
this.zoomMultiplicationFactor = zoomMultiplicationFactor;
}
public void translate(double dx, double dy) {
coordTransform.translate(dx, dy);
targetComponent.repaint();
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
dragStartScreen = e.getPoint();
dragEndScreen = null;
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
moveCamera(e);
}
public void mouseWheelMoved(MouseWheelEvent e) {
zoomCamera(e);
}
private void moveCamera(MouseEvent e) {
try {
dragEndScreen = e.getPoint();
Point2D.Float dragStart = transformPoint(dragStartScreen);
Point2D.Float dragEnd = transformPoint(dragEndScreen);
double dx = dragEnd.getX() - dragStart.getX();
double dy = dragEnd.getY() - dragStart.getY();
coordTransform.translate(dx, dy);
dragStartScreen = dragEndScreen;
dragEndScreen = null;
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
private void zoomCamera(MouseWheelEvent e) {
try {
int wheelRotation = e.getWheelRotation();
Point p = e.getPoint();
if (wheelRotation < 0) {
if (zoomLevel < maxZoomLevel) {
zoomLevel++;
Point2D p1 = transformPoint(p);
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
}
else {
if (zoomLevel > minZoomLevel) {
zoomLevel--;
Point2D p1 = transformPoint(p);
coordTransform.scale(1 / zoomMultiplicationFactor,
1 / zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
}
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
AffineTransform inverse = coordTransform.createInverse();
Point2D.Float p2 = new Point2D.Float();
inverse.transform(p1, p2);
return p2;
}
public int getZoomLevel() {
return zoomLevel;
}
public void setZoomLevel(int zoomLevel) {
this.zoomLevel = zoomLevel;
}
public void zoomIn() {
try {
Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
zoomLevel++;
Point2D p1 = transformPoint(p);
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
public void zoomOut() {
try {
Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
zoomLevel--;
Point2D p1 = transformPoint(p);
coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
Point2D p2 = transformPoint(p);
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
targetComponent.repaint();
}
catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
public AffineTransform getCoordTransform() {
return coordTransform;
}
public void setCoordTransform(AffineTransform coordTransform) {
this.coordTransform = coordTransform;
}
}

View file

@ -0,0 +1,73 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.Point;
import org.mapsforge.map.awt.graphics.AwtBitmap;
import org.mapsforge.map.layer.overlay.Marker;
/**
* Class extending the default Mapsforge's {@link Marker} with auto-scaling.
*
* Mapsforge's Markers do not scale with zoom level, this class aims at
* correcting this. Internally, this image stores an {@link Image} instance and
* scale it when a redraw is requested.
*
* @see MarkerUtils#getMarkerForColor(java.awt.Color, java.awt.Color,
* org.insa.graphics.drawing.Drawing.AlphaMode)
* @see PaintUtils#getStrokeWidth(int, byte)
*/
public class MarkerAutoScaling extends Marker {
// Original image.
private Image image;
/**
* Create a new MarkerAutoScaling at the given position with the given image.
*
* @param latLong Initial position of the marker.
* @param image Image for this marker.
*/
public MarkerAutoScaling(LatLong latLong, Image image) {
super(latLong, null, 0, 0);
this.image = image;
}
/**
* Set a new image for this marker overlay
*
* @param image New image to set.
*/
public void setImage(Image image) {
this.image = image;
}
/**
* @return Current image (marker) of this overlay.
*/
public Image getImage() {
return image;
}
@Override
public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
Point topLeftPoint) {
int width = (int) PaintUtils.getStrokeWidth(8, zoomLevel),
height = (int) PaintUtils.getStrokeWidth(16, zoomLevel);
BufferedImage bfd = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g = bfd.createGraphics();
g.drawImage(
this.image.getScaledInstance(bfd.getWidth(), bfd.getHeight(), Image.SCALE_SMOOTH),
0, 0, null);
setBitmap(new AwtBitmap(bfd));
setVerticalOffset(-height / 2);
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
g.dispose();
}
}

View file

@ -0,0 +1,19 @@
package org.insa.graphs.gui.drawing.overlays;
import org.insa.graphs.model.Point;
public interface MarkerOverlay extends Overlay {
/**
* @return The current position of this marker.
*/
public Point getPoint();
/**
* Move this marker to the specified location.
*
* @param point New position for the marker.
*/
public void moveTo(Point point);
}

View file

@ -0,0 +1,97 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
public class MarkerUtils {
/**
* Create an image to represent a marker using the given color for the outer and
* inner part, and the given mode for the inner part.
*
* @param outer Outer color of the marker.
* @param inner Inner color of the marker.
* @param mode Mode to use to fill the inner part of the marker.
*
* @return An image representing a marker.
*/
public static Image getMarkerForColor(Color outer, Color inner, AlphaMode mode) {
// create image
int[][] mask = readMarkerMask();
BufferedImage image = new BufferedImage(mask[0].length, mask.length,
BufferedImage.TYPE_4BYTE_ABGR);
// Color[] map = getColorMapping(color);
int outerRGB = outer.getRGB() & 0x00ffffff;
for (int i = 0; i < image.getHeight(); ++i) {
for (int j = 0; j < image.getWidth(); ++j) {
// If we are in the "inner" part of the marker...
if (i >= MIN_Y_CENTER && i < MAX_Y_CENTER && j >= MIN_X_CENTER && j < MAX_X_CENTER
&& mask[i][j] != MAXIMUM_INNER_MASK_VALUE) {
// Don't ask... https://stackoverflow.com/a/29321264/2666289
// Basically, this compute a "middle" color between outer and inner depending on
// the current mask value.
double t = 1 - (mask[i][j] - MINIMUM_INNER_MASK_VALUE)
/ (double) (MAXIMUM_INNER_MASK_VALUE - MINIMUM_INNER_MASK_VALUE);
int r = (int) Math.sqrt((1 - t) * outer.getRed() * outer.getRed()
+ t * inner.getRed() * inner.getRed());
int g = (int) Math.sqrt((1 - t) * outer.getGreen() * outer.getGreen()
+ t * inner.getGreen() * inner.getGreen());
int b = (int) Math.sqrt((1 - t) * outer.getBlue() * outer.getBlue()
+ t * inner.getBlue() * inner.getBlue());
int a = mode == AlphaMode.OPAQUE ? MAXIMUM_INNER_MASK_VALUE : mask[i][j];
image.setRGB(j, i, (a << 24) | (r << 16) | (g << 8) | b);
}
// Otherwize, just fill with the outer color and set the alpha value properly.
else {
image.setRGB(j, i, outerRGB | (mask[i][j] << 24));
}
}
}
return image;
}
// Mask cache
private static int[][] MASK_CACHE = null;
// Hand-made... These defines the "center" of the marker, that can be filled
// with a different color.
private static final int MIN_X_CENTER = 40, MAX_X_CENTER = 101, MIN_Y_CENTER = 40,
MAX_Y_CENTER = 100;
private static final int MINIMUM_INNER_MASK_VALUE = 116, MAXIMUM_INNER_MASK_VALUE = 249;
/**
* @return Retrieve the mask from the mask file or from the cache.
*/
private static int[][] readMarkerMask() {
if (MASK_CACHE == null) {
try {
DataInputStream dis = new DataInputStream(
MarkerUtils.class.getResourceAsStream("/marker_mask.bin"));
int nrows = dis.readInt();
int ncols = dis.readInt();
MASK_CACHE = new int[nrows][ncols];
for (int i = 0; i < nrows; ++i) {
for (int j = 0; j < ncols; ++j) {
MASK_CACHE[i][j] = dis.readUnsignedByte();
}
}
dis.close();
}
catch (Exception e) {
e.printStackTrace();
MASK_CACHE = null;
}
}
return MASK_CACHE;
}
}

View file

@ -0,0 +1,42 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Color;
public interface Overlay {
/**
* Set the color of this overlay.
*
* @param color New color for the overlay.
*/
public void setColor(Color color);
/**
* @return The current color of this overlay.
*/
public Color getColor();
/**
* Show or hide this marker - A marker should be visible when created.
*
* @param visible true to show the marker, false to hide.
*/
public void setVisible(boolean visible);
/**
* @return true if this overlay is visible.
*/
public boolean isVisible();
/**
* Delete this marker.
*/
public void delete();
/**
* Request a redraw of this overlay - This can start a full redraw of the inner
* drawing.
*/
public void redraw();
}

View file

@ -0,0 +1,65 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Color;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
import org.mapsforge.map.layer.overlay.Marker;
import org.mapsforge.map.layer.overlay.Polyline;
public class PaintUtils {
// Graphic factory.
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
/**
* Convert the given AWT color to a mapsforge compatible color.
*
* @param color AWT color to convert.
*
* @return Integer value representing a corresponding mapsforge color.
*/
public static int convertColor(Color color) {
return GRAPHIC_FACTORY.createColor(color.getAlpha(), color.getRed(), color.getGreen(),
color.getBlue());
}
/**
* Convert the given mapsforge color to an AWT Color.
*
* @param color Integer value representing a mapsforge color.
*
* @return AWT color corresponding to the given value.
*/
public static Color convertColor(int color) {
return new Color(color, true);
}
/**
* Compute an updated value for the given width at the given zoom level. This
* function can be used to automatically scale {@link Polyline} or
* {@link Marker} when zooming (which is not done by default in Mapsforge).
*
* @param width Original width to convert.
* @param zoomLevel Zoom level for which the width should be computed.
*
* @return Actual width at the given zoom level.
*/
public static float getStrokeWidth(int width, byte zoomLevel) {
float mul = 1;
if (zoomLevel < 6) {
mul = 1;
}
else if (zoomLevel < 10) {
mul += (zoomLevel - 5) * 0.5;
}
else if (zoomLevel < 13) {
mul = 3.5f;
}
else {
mul += 2 * (zoomLevel - 8) / 3;
}
return width * mul;
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.gui.drawing.overlays;
public interface PathOverlay extends Overlay {
}

View file

@ -0,0 +1,70 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Color;
import org.insa.graphs.model.Point;
public interface PointSetOverlay extends Overlay {
/**
* Set the width of this overlay for future addPoint().
*
* @param width New default width for this overlay.
*/
public void setWidth(int width);
/**
* Set color and width for this overlay for future addPoint().
*
* @param width New default width for this overlay.
* @param color New default color for this overlay.
*/
public void setWidthAndColor(int width, Color color);
/**
* Add a new point using the current width and color.
*
* @param point Position of the point to add.
*
* @see #setWidth(int)
* @see #setColor(Color)
*/
public void addPoint(Point point);
/**
* Set the current width and then add a new point.
*
* @param point Position of the point to add.
* @param width New default width for this overlay.
*
* @see #setWidth(int)
* @see PointSetOverlay#addPoint(Point)
*/
public void addPoint(Point point, int width);
/**
* Set the current color and then add a new point.
*
* @param point Position of the point to add.
* @param color New default color for this overlay.
*
* @see #setColor(Color)
* @see PointSetOverlay#addPoint(Point)
*/
public void addPoint(Point point, Color color);
/**
* Add a new point at the given location, with the given color and width, and
* update the current width and color.
*
* @param point Position of the point to add.
* @param width New default width for this overlay.
* @param color New default color for this overlay.
*
* @see #setWidth(int)
* @see #setColor(Color)
* @see PointSetOverlay#addPoint(Point)
*/
public void addPoint(Point point, int width, Color color);
}

View file

@ -0,0 +1,92 @@
package org.insa.graphs.gui.drawing.overlays;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import org.insa.graphs.model.Point;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
import org.mapsforge.map.layer.overlay.Polyline;
/**
* Class extending the default Mapsforge's {@link Polyline} with auto-scaling.
*
* Mapsforge's Polylines do not scale with zoom level, this class aims at
* correcting this. When a redraw is requested, the width of the line is
* recomputed for the current zoom level.
*
* @see PaintUtils#getStrokeWidth(int, byte)
*/
public class PolylineAutoScaling extends Polyline {
// Graphic factory.
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
// Original width of the polyline.
private final int width;
/**
* Create a new PolylineAutoScaling with the given width and color.
*
* @param width Original width of the line (independent of the zoom level).
* @param color Color of the line.
*
* @see PaintUtils#getStrokeWidth(int, byte)
*/
public PolylineAutoScaling(int width, Color color) {
super(GRAPHIC_FACTORY.createPaint(), GRAPHIC_FACTORY);
getPaintStroke().setColor(PaintUtils.convertColor(color));
getPaintStroke().setStyle(Style.STROKE);
this.width = width;
}
/**
* Set the color for this polyline.
*
* @param color New color for this polyline.
*/
public void setColor(Color color) {
getPaintStroke().setColor(PaintUtils.convertColor(color));
}
/**
* @return Color of this polyline.
*/
public Color getColor() {
return PaintUtils.convertColor(getPaintStroke().getColor());
}
/**
* @param point Point to add to this line.
*/
public void add(Point point) {
getLatLongs().add(new LatLong(point.getLatitude(), point.getLongitude()));
}
/**
* @param points Points to add to this line.
*/
public void addAll(Collection<? extends Point> points) {
ArrayList<LatLong> latlongs = new ArrayList<>(points.size());
for (Point point: points) {
latlongs.add(new LatLong(point.getLatitude(), point.getLongitude()));
}
getLatLongs().addAll(latlongs);
}
@Override
public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
org.mapsforge.core.model.Point topLeftPoint) {
// Update paint stroke with width for level
this.getPaintStroke().setStrokeWidth(PaintUtils.getStrokeWidth(width, zoomLevel));
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
}
}

View file

@ -0,0 +1,42 @@
package org.insa.graphs.gui.observers;
import java.awt.Color;
import org.insa.graphs.algorithm.shortestpath.ShortestPathObserver;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
import org.insa.graphs.model.Node;
public class ShortestPathGraphicObserver implements ShortestPathObserver {
// Drawing and Graph drawing
protected Drawing drawing;
protected PointSetOverlay psOverlay1, psOverlay2;
public ShortestPathGraphicObserver(Drawing drawing) {
this.drawing = drawing;
psOverlay1 = drawing.createPointSetOverlay(1, Color.CYAN);
psOverlay2 = drawing.createPointSetOverlay(1, Color.BLUE);
}
@Override
public void notifyOriginProcessed(Node node) {
// drawing.drawMarker(node.getPoint(), Color.RED);
}
@Override
public void notifyNodeReached(Node node) {
psOverlay1.addPoint(node.getPoint());
}
@Override
public void notifyNodeMarked(Node node) {
psOverlay2.addPoint(node.getPoint());
}
@Override
public void notifyDestinationReached(Node node) {
// drawing.drawMarker(node.getPoint(), Color.RED);
}
}

View file

@ -0,0 +1,42 @@
package org.insa.graphs.gui.observers;
import java.awt.Color;
import java.util.ArrayList;
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentObserver;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
import org.insa.graphs.model.Node;
public class WeaklyConnectedComponentGraphicObserver implements WeaklyConnectedComponentObserver {
private static final Color[] COLORS = { Color.BLUE, Color.ORANGE, Color.GREEN, Color.YELLOW,
Color.RED };
// Drawing + Graph drawing
private PointSetOverlay grPoints;
// Current index color
private int cindex = 0;
public WeaklyConnectedComponentGraphicObserver(Drawing drawing) {
this.grPoints = drawing.createPointSetOverlay();
this.grPoints.setWidth(1);
}
@Override
public void notifyStartComponent(Node curNode) {
this.grPoints.setColor(COLORS[cindex]);
}
@Override
public void notifyNewNodeInComponent(Node node) {
this.grPoints.addPoint(node.getPoint());
}
@Override
public void notifyEndComponent(ArrayList<Node> nodes) {
cindex = (cindex + 1) % COLORS.length;
}
}

View file

@ -0,0 +1,78 @@
package org.insa.graphs.gui.simple;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.insa.graphs.gui.drawing.Drawing;
import org.insa.graphs.gui.drawing.components.BasicDrawing;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.io.BinaryGraphReader;
import org.insa.graphs.model.io.BinaryPathReader;
import org.insa.graphs.model.io.GraphReader;
import org.insa.graphs.model.io.PathReader;
public class Launch {
/**
* Create a new Drawing inside a JFrame an return it.
*
* @return The created drawing.
*
* @throws Exception if something wrong happens when creating the graph.
*/
public static Drawing createDrawing() throws Exception {
BasicDrawing basicDrawing = new BasicDrawing();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("BE Graphes - Launch");
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setSize(new Dimension(800, 600));
frame.setContentPane(basicDrawing);
frame.validate();
}
});
return basicDrawing;
}
public static void main(String[] args) throws Exception {
// Visit these directory to see the list of available files on Commetud.
final String mapName = "./Maps/haute-garonne.mapgr";
final String pathName = "./Paths/path_fr31_insa_aeroport_length.path";
// Create a graph reader.
final GraphReader reader = new BinaryGraphReader(
new DataInputStream(new BufferedInputStream(new FileInputStream(mapName))));
// DONE: Read the graph.
final Graph graph = reader.read();
// Create the drawing:
final Drawing drawing = createDrawing();
// DONE: Draw the graph on the drawing.
drawing.drawGraph(graph);
// DONE: Create a PathReader.
final PathReader pathReader = new BinaryPathReader(
new DataInputStream(new BufferedInputStream(new FileInputStream(pathName))));
// DONE: Read the path.
final Path path = pathReader.readPath(graph);
// DONE: Draw the path.
drawing.drawPath(path);
}
}

View file

@ -0,0 +1,21 @@
package org.insa.graphs.gui.utils;
import java.awt.Color;
public class ColorUtils {
private static final Color[] COLORS = { // List of available colors
new Color(57, 172, 115), // Forest (Green)
new Color(246, 67, 63), // Red
new Color(110, 56, 172), // Purple
new Color(53, 191, 179), // Cyan
new Color(219, 136, 48), // Orange / Brown
new Color(110, 110, 110), // Gray
new Color(56, 104, 172) // Blue
};
public static Color getColor(int i) {
return COLORS[i % COLORS.length];
}
}

View file

@ -0,0 +1,151 @@
package org.insa.graphs.gui.utils;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.EnumMap;
import java.util.Map;
import java.util.prefs.Preferences;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
public class FileUtils {
// Preferences
private static Preferences preferences = Preferences.userRoot().node(FileUtils.class.getName());
/**
* Type of folder with associated preferred folder and path filters.
*
*/
public enum FolderType {
/**
* Folder type for graph files input (*.mapgr).
*/
Map,
/**
* Folder type for path inputs (*.path).
*/
PathInput,
/**
* Folder type for path outputs (*.path).
*/
PathOutput
}
private static class PreferencesEntry {
public String key;
public String value;
public PreferencesEntry(String key, String value) {
this.key = key;
this.value = value;
}
}
// Map folder type -> PreferencesEntry
private static final Map<FolderType, PreferencesEntry> folderToEntry = new EnumMap<>(
FolderType.class);
// Map folder type -> File Filter
private static final Map<FolderType, FileFilter> folderToFilter = new EnumMap<>(
FolderType.class);
static {
// Populate folderToEntry
folderToEntry.put(FolderType.Map, new PreferencesEntry("DefaultMapFolder",
"/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps"));
folderToEntry.put(FolderType.PathInput, new PreferencesEntry("DefaultPathInputFolder",
"/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Paths"));
folderToEntry.put(FolderType.PathOutput,
new PreferencesEntry("DefaultPathOutputsFolder", "paths"));
// Populate folderToFilter
folderToFilter.put(FolderType.Map, new FileNameExtensionFilter("Graph files", "mapgr"));
folderToFilter.put(FolderType.PathInput, new FileNameExtensionFilter("Path files", "path"));
folderToFilter.put(FolderType.PathOutput,
new FileNameExtensionFilter("Path files", "path"));
}
/**
* @param folderType Type of folder to retrieve.
*
* @return A File instance pointing to the preferred folder for the given type.
*
* @see FolderType
*/
public static File getPreferredFolder(FolderType folderType) {
PreferencesEntry entry = folderToEntry.get(folderType);
File folder = new File(preferences.get(entry.key, entry.value));
if (!folder.exists()) {
folder = new File(System.getProperty("user.dir"));
}
return folder;
}
/**
* @param folderType Type of folder to update.
* @param newPreferredFolder New preferred folder.
*/
public static void updatePreferredFolder(FolderType folderType, File newPreferredFolder) {
PreferencesEntry entry = folderToEntry.get(folderType);
preferences.put(entry.key, newPreferredFolder.getAbsolutePath());
}
/**
* @param folderType Type of folder for which the filter should be retrieved.
*
* @return A FileFilter corresponding to input graph files.
*/
public static FileFilter getFileFilter(FolderType folderType) {
return folderToFilter.get(folderType);
}
/**
* @param folderType Type of folder for which a file chooser should be created.
* @param defaultFileName Default file name to show, or null to not show any
* file.
*
* @return A new JFileChooser pointing to the preferred folder for the given
* folderType, with the given default file selected (if given).
*/
public static JFileChooser createFileChooser(FolderType folderType, String defaultFileName) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(getPreferredFolder(folderType));
if (defaultFileName != null) {
chooser.setSelectedFile(new File(chooser.getCurrentDirectory().getAbsolutePath()
+ File.separator + defaultFileName));
}
chooser.setFileFilter(getFileFilter(folderType));
chooser.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
if (chooser.getSelectedFile().exists()) {
updatePreferredFolder(folderType,
chooser.getSelectedFile().getParentFile());
}
}
}
});
return chooser;
}
/**
* @param folderType Type of folder for which a file chooser should be created.
*
* @return A new JFileChooser pointing to the preferred folder for the given
* folderType.
*
* @see #createFileChooser(FolderType, String)
*/
public static JFileChooser createFileChooser(FolderType folderType) {
return createFileChooser(folderType, null);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

17
be-graphes-model/pom.xml Normal file
View file

@ -0,0 +1,17 @@
<?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,297 @@
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.
*
* Implemented.
*/
public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes) throws IllegalArgumentException {
List<Arc> arcs = new ArrayList<Arc>();
if (nodes.size() == 1)
return new Path(graph, nodes.get(0));
for (int i = 0; i < nodes.size() - 1; i++) {
double min_time = Double.MAX_VALUE;
Arc best = null;
for (Arc a : nodes.get(i).getSuccessors()) {
if (a.getDestination() == nodes.get(i + 1) && a.getMinimumTravelTime() < min_time) {
min_time = a.getMinimumTravelTime();
best = a;
}
}
if (min_time == Double.MAX_VALUE) {
throw new IllegalArgumentException("At least two nodes cannot be connected.");
}
arcs.add(best);
}
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.
*
*
*/
public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes) throws IllegalArgumentException {
List<Arc> arcs = new ArrayList<Arc>();
if (nodes.size() == 1)
return new Path(graph, nodes.get(0));
for (int i = 0; i < nodes.size() - 1; i++) {
float shortest_length = Float.MAX_VALUE;
Arc best = null;
for (Arc a : nodes.get(i).getSuccessors()) {
if (a.getDestination() == nodes.get(i + 1) && a.getLength() < shortest_length) {
shortest_length = a.getLength();
best = a;
}
}
if (shortest_length == Float.MAX_VALUE) {
throw new IllegalArgumentException("At least two nodes cannot be connected.");
}
arcs.add(best);
}
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.
*
* Implemented.
*/
public boolean isValid() {
// DONE
if (isEmpty() || arcs.isEmpty())
return true;
for (int i = 0; i < arcs.size() - 1; ++i) {
if (arcs.get(i).getDestination() != arcs.get(i + 1).getOrigin()) {
return false;
}
}
return true && arcs.get(0).getOrigin() == getOrigin();
}
/**
* Compute the length of this path (in meters).
*
* @return Total length of the path (in meters).
*
* Implemented.
*/
public float getLength() {
// DONE
float length = 0;
for (Arc i : arcs)
length += i.getLength();
return length;
}
/**
* 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).
*
* Implemented.
*/
public double getTravelTime(double speed) {
// DONE
double time = 0;
for (Arc i : arcs)
time += i.getTravelTime(speed);
return time;
}
/**
* 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).
*
* Implemented.
*/
public double getMinimumTravelTime() {
// DONE
double time = 0;
for (Arc i : arcs)
time += i.getMinimumTravelTime();
return time;
}
}

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.)";
}
}

Some files were not shown because too many files have changed in this diff Show more