Browse Source

Importation du code depuis GitHub

gasc 2 years ago
commit
59329c4dbc
100 changed files with 9963 additions and 0 deletions
  1. 28
    0
      README.md
  2. 25
    0
      be-graphes-algos/pom.xml
  3. 87
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractAlgorithm.java
  4. 97
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractInputData.java
  5. 88
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractSolution.java
  6. 128
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AlgorithmFactory.java
  7. 43
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/ArcInspector.java
  8. 177
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/ArcInspectorFactory.java
  9. 24
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingAlgorithm.java
  10. 13
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingData.java
  11. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingGraphicObserver.java
  12. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingObserver.java
  13. 11
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingSolution.java
  14. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingTextObserver.java
  15. 29
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchAlgorithm.java
  16. 13
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchData.java
  17. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchGraphicObserver.java
  18. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchObserver.java
  19. 11
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchSolution.java
  20. 5
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchTextObserver.java
  21. 58
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/AStarAlgorithm.java
  22. 100
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/BellmanFordAlgorithm.java
  23. 154
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/DijkstraAlgorithm.java
  24. 69
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathAlgorithm.java
  25. 47
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathData.java
  26. 37
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathObserver.java
  27. 74
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathSolution.java
  28. 37
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathTextObserver.java
  29. 234
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinaryHeap.java
  30. 198
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinaryHeapFormatter.java
  31. 64
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinarySearchTree.java
  32. 32
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/ElementNotFoundException.java
  33. 16
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/EmptyPriorityQueueException.java
  34. 81
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/PriorityQueue.java
  35. 30
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentObserver.java
  36. 36
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentTextObserver.java
  37. 159
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsAlgorithm.java
  38. 20
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsData.java
  39. 57
    0
      be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsSolution.java
  40. 15
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/AStarTest.java
  41. 17
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BellmanFordTest.java
  42. 15
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BinaryHeapTest.java
  43. 15
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BinarySearchTreeTest.java
  44. 16
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/DijkstraTest.java
  45. 196
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/PccAlgorithmTest.java
  46. 320
    0
      be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/PriorityQueueTest.java
  47. 87
    0
      be-graphes-gui/pom.xml
  48. 381
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/AlgorithmPanel.java
  49. 56
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/BlockingActionFactory.java
  50. 24
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/DrawingChangeListener.java
  51. 14
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/GraphChangeListener.java
  52. 123
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/GraphReaderProgressBar.java
  53. 862
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java
  54. 424
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/NodesInputPanel.java
  55. 361
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/PathsPanel.java
  56. 33
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/RunningAction.java
  57. 289
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/SolutionPanel.java
  58. 54
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/StreamCapturer.java
  59. 62
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/ThreadWrapper.java
  60. 82
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/BasicGraphPalette.java
  61. 19
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/BlackAndWhiteGraphPalette.java
  62. 157
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/Drawing.java
  63. 14
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/DrawingClickListener.java
  64. 23
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/GraphPalette.java
  65. 116
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/MercatorProjection.java
  66. 68
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/PlateCarreProjection.java
  67. 51
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/Projection.java
  68. 747
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/BasicDrawing.java
  69. 521
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/MapViewDrawing.java
  70. 209
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/MapZoomControls.java
  71. 177
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/ZoomAndPanListener.java
  72. 73
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerAutoScaling.java
  73. 19
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerOverlay.java
  74. 97
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerUtils.java
  75. 42
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/Overlay.java
  76. 65
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PaintUtils.java
  77. 5
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PathOverlay.java
  78. 70
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PointSetOverlay.java
  79. 92
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PolylineAutoScaling.java
  80. 42
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/observers/ShortestPathGraphicObserver.java
  81. 42
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/observers/WeaklyConnectedComponentGraphicObserver.java
  82. 78
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/simple/Launch.java
  83. 21
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/utils/ColorUtils.java
  84. 151
    0
      be-graphes-gui/src/main/java/org/insa/graphs/gui/utils/FileUtils.java
  85. BIN
      be-graphes-gui/src/main/resources/delete-icon.png
  86. BIN
      be-graphes-gui/src/main/resources/marker_mask.bin
  87. BIN
      be-graphes-gui/src/main/resources/save-icon.png
  88. BIN
      be-graphes-gui/src/main/resources/zoomIn.png
  89. BIN
      be-graphes-gui/src/main/resources/zoomOut.png
  90. 17
    0
      be-graphes-model/pom.xml
  91. 236
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/AccessRestrictions.java
  92. 70
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/Arc.java
  93. 55
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/ArcBackward.java
  94. 68
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/ArcForward.java
  95. 131
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/Graph.java
  96. 204
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/GraphStatistics.java
  97. 159
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/Node.java
  98. 297
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/Path.java
  99. 74
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/Point.java
  100. 0
    0
      be-graphes-model/src/main/java/org/insa/graphs/model/RoadInformation.java

+ 28
- 0
README.md View File

1
+# I3MIIL11 - Bureau d'étude graphes
2
+
3
+Répertoire de travail pour le bureau d'étude graphes de 3 MIC à l'INSA de Toulouse.
4
+
5
+## Remarques
6
+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.
7
+
8
+## Algorithme de Dijkstra (PCC)
9
+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 :
10
+
11
+![2 chemins](rsc/img1.png)
12
+
13
+Exemple de résultat avec observateurs :
14
+
15
+![avec observateurs](rsc/img2.png)
16
+
17
+## Test
18
+### Test avec oracle 
19
+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).
20
+ 
21
+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.*
22
+
23
+### Test d'optimalité (tests sans oracle)
24
+Plusieurs tests sans oracle ont été implémentés :
25
+- Test des sous-chemins : tous les sous-chemins doivent également suivre les mêmes nodes
26
+- Test du vol d'oiseau : la distance en vol d'oiseau doit être plus courte que la distance obtenue
27
+- 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é
28
+

+ 25
- 0
be-graphes-algos/pom.xml View File

1
+<?xml version="1.0"?>
2
+<project
3
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+	xmlns="http://maven.apache.org/POM/4.0.0"
5
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
6
+	<modelVersion>4.0.0</modelVersion>
7
+	
8
+	<parent>
9
+		<groupId>org.insa.graphs</groupId>
10
+		<artifactId>be-graphes-all</artifactId>
11
+		<version>0.0.1-SNAPSHOT</version>
12
+	</parent>
13
+	
14
+	<artifactId>be-graphes-algos</artifactId>
15
+	<name>be-graphes-algos</name>
16
+	
17
+	<dependencies>
18
+		<dependency>
19
+			<groupId>org.insa.graphs</groupId>
20
+			<artifactId>be-graphes-model</artifactId>
21
+			<version>${project.version}</version>
22
+		</dependency>
23
+	</dependencies>
24
+	
25
+</project>

+ 87
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractAlgorithm.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import java.time.Duration;
4
+import java.time.Instant;
5
+import java.util.ArrayList;
6
+
7
+/**
8
+ * Base class for algorithm classes.
9
+ *
10
+ * @param <Observer> Observer type for the algorithm.
11
+ */
12
+public abstract class AbstractAlgorithm<Observer> {
13
+
14
+    // Input data for the algorithm
15
+    protected final AbstractInputData data;
16
+
17
+    // List of observers for the algorithm
18
+    protected final ArrayList<Observer> observers;
19
+
20
+    /**
21
+     * Create a new algorithm with an empty list of observers.
22
+     * 
23
+     * @param data Input data for the algorithm.
24
+     */
25
+    protected AbstractAlgorithm(AbstractInputData data) {
26
+        this.data = data;
27
+        this.observers = new ArrayList<Observer>();
28
+    }
29
+
30
+    /**
31
+     * Create a new algorithm with the given list of observers.
32
+     * 
33
+     * @param data Input data for the algorithm.
34
+     * @param observers Initial list of observers for the algorithm.
35
+     */
36
+    protected AbstractAlgorithm(AbstractInputData data, ArrayList<Observer> observers) {
37
+        this.data = data;
38
+        this.observers = observers;
39
+    }
40
+
41
+    /**
42
+     * Add an observer to this algorithm.
43
+     * 
44
+     * @param observer Observer to add to this algorithm.
45
+     */
46
+    public void addObserver(Observer observer) {
47
+        observers.add(observer);
48
+    }
49
+
50
+    /**
51
+     * @return The list of observers for this algorithm.
52
+     */
53
+    public ArrayList<Observer> getObservers() {
54
+        return observers;
55
+    }
56
+
57
+    /**
58
+     * @return Input for this algorithm.
59
+     */
60
+    public AbstractInputData getInputData() {
61
+        return data;
62
+    }
63
+
64
+    /**
65
+     * Run the algorithm and return the solution.
66
+     * 
67
+     * This methods internally time the call to doRun() and update the result of the
68
+     * call with the computed solving time.
69
+     * 
70
+     * @return The solution found by the algorithm (may not be a feasible solution).
71
+     */
72
+    public AbstractSolution run() {
73
+        Instant start = Instant.now();
74
+        AbstractSolution solution = this.doRun();
75
+        solution.setSolvingTime(Duration.between(start, Instant.now()));
76
+        return solution;
77
+    }
78
+
79
+    /**
80
+     * Abstract method that should be implemented by child class.
81
+     * 
82
+     * @return The solution found, must not be null (use an infeasible or unknown
83
+     * status if necessary).
84
+     */
85
+    protected abstract AbstractSolution doRun();
86
+
87
+}

+ 97
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractInputData.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import org.insa.graphs.model.Arc;
4
+import org.insa.graphs.model.Graph;
5
+import org.insa.graphs.model.GraphStatistics;
6
+
7
+/**
8
+ * Base class for algorithm input data classes. This class contains the basic
9
+ * data that are required by most graph algorithms, i.e. a graph, a mode (time /
10
+ * length) and a filter for the arc.
11
+ *
12
+ */
13
+public abstract class AbstractInputData {
14
+
15
+    /**
16
+     * Enum specifying the top mode of the algorithms.
17
+     * 
18
+     * @see ArcInspector
19
+     */
20
+    public enum Mode {
21
+        TIME, LENGTH
22
+    }
23
+
24
+    // Graph
25
+    private final Graph graph;
26
+
27
+    // Arc filter.
28
+    protected final ArcInspector arcInspector;
29
+
30
+    /**
31
+     * Create a new AbstractInputData instance for the given graph, mode and filter.
32
+     * 
33
+     * @param graph Graph for this input data.
34
+     * @param arcInspector Arc inspector for this input data.
35
+     */
36
+    protected AbstractInputData(Graph graph, ArcInspector arcInspector) {
37
+        this.graph = graph;
38
+        this.arcInspector = arcInspector;
39
+    }
40
+
41
+    /**
42
+     * @return Graph associated with this input.
43
+     */
44
+    public Graph getGraph() {
45
+        return graph;
46
+    }
47
+
48
+    /**
49
+     * Retrieve the cost associated with the given arc according to the underlying
50
+     * arc inspector.
51
+     * 
52
+     * @param arc Arc for which cost should be retrieved.
53
+     * 
54
+     * @return Cost for the given arc.
55
+     * 
56
+     * @see ArcInspector
57
+     */
58
+    public double getCost(Arc arc) {
59
+        return this.arcInspector.getCost(arc);
60
+    }
61
+
62
+    /**
63
+     * @return Mode associated with this input data.
64
+     * 
65
+     * @see Mode
66
+     */
67
+    public Mode getMode() {
68
+        return this.arcInspector.getMode();
69
+    }
70
+
71
+    /**
72
+     * Retrieve the maximum speed associated with this input data, or
73
+     * {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is associated. The maximum
74
+     * speed associated with input data is different from the maximum speed
75
+     * associated with graph (accessible via {@link Graph#getGraphInformation()}).
76
+     * 
77
+     * @return The maximum speed for this inspector, or
78
+     *         {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
79
+     */
80
+    public int getMaximumSpeed() {
81
+        return this.arcInspector.getMaximumSpeed();
82
+    }
83
+
84
+    /**
85
+     * Check if the given arc is allowed for the filter corresponding to this input.
86
+     * 
87
+     * @param arc Arc to check.
88
+     * 
89
+     * @return true if the given arc is allowed.
90
+     * 
91
+     * @see ArcInspector
92
+     */
93
+    public boolean isAllowed(Arc arc) {
94
+        return this.arcInspector.isAllowed(arc);
95
+    }
96
+
97
+}

+ 88
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AbstractSolution.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import java.time.Duration;
4
+
5
+/**
6
+ * Base class for solution classes returned by the algorithm. This class
7
+ * contains the basic information that any solution should have: status of the
8
+ * solution (unknown, infeasible, etc.), solving time and the original input
9
+ * data.
10
+ */
11
+public abstract class AbstractSolution {
12
+
13
+    /**
14
+     * Possible status for a solution.
15
+     *
16
+     */
17
+    public enum Status {
18
+        UNKNOWN, INFEASIBLE, FEASIBLE, OPTIMAL,
19
+    };
20
+
21
+    // Status of the solution.
22
+    private final Status status;
23
+
24
+    // Solving time for the solution.
25
+    private Duration solvingTime;
26
+
27
+    // Original input of the solution.
28
+    private final AbstractInputData data;
29
+
30
+    /**
31
+     * Create a new abstract solution with unknown status.
32
+     * 
33
+     * @param data
34
+     */
35
+    protected AbstractSolution(AbstractInputData data) {
36
+        this.data = data;
37
+        this.solvingTime = Duration.ZERO;
38
+        this.status = Status.UNKNOWN;
39
+    }
40
+
41
+    /**
42
+     * 
43
+     * @param data
44
+     * @param status
45
+     */
46
+    protected AbstractSolution(AbstractInputData data, Status status) {
47
+        this.data = data;
48
+        this.status = status;
49
+    }
50
+
51
+    /**
52
+     * @return Original input for this solution.
53
+     */
54
+    public AbstractInputData getInputData() {
55
+        return data;
56
+    }
57
+
58
+    /**
59
+     * @return Status of this solution.
60
+     */
61
+    public Status getStatus() {
62
+        return status;
63
+    }
64
+
65
+    /**
66
+     * @return Solving time of this solution.
67
+     */
68
+    public Duration getSolvingTime() {
69
+        return solvingTime;
70
+    }
71
+
72
+    /**
73
+     * Set the solving time of this solution.
74
+     * 
75
+     * @param solvingTime Solving time for the solution.
76
+     */
77
+    protected void setSolvingTime(Duration solvingTime) {
78
+        this.solvingTime = solvingTime;
79
+    }
80
+
81
+    /**
82
+     * @return true if the solution is feasible or optimal.
83
+     */
84
+    public boolean isFeasible() {
85
+        return status == Status.FEASIBLE || status == Status.OPTIMAL;
86
+    }
87
+
88
+}

+ 128
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/AlgorithmFactory.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import java.lang.reflect.Constructor;
4
+import java.util.IdentityHashMap;
5
+import java.util.LinkedHashMap;
6
+import java.util.Map;
7
+import java.util.Set;
8
+import java.util.TreeSet;
9
+
10
+import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
11
+import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
12
+import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
13
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
14
+import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
15
+
16
+/**
17
+ * Factory class used to register and retrieve algorithms based on their common
18
+ * ancestor and name.
19
+ *
20
+ */
21
+public class AlgorithmFactory {
22
+
23
+    // Map between algorithm names and class.
24
+    private final static Map<Class<? extends AbstractAlgorithm<?>>, Map<String, Class<? extends AbstractAlgorithm<?>>>> ALGORITHMS = new IdentityHashMap<>();
25
+
26
+    static {
27
+        // Register weakly-connected components algorithm:
28
+        registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic",
29
+                WeaklyConnectedComponentsAlgorithm.class);
30
+
31
+        // Register shortest path algorithm:
32
+        registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class);
33
+        registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class);
34
+        registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class);
35
+
36
+        // Register your algorithms here:
37
+        // registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm",
38
+        // MyCarPoolingAlgorithm.class);
39
+    }
40
+
41
+    /**
42
+     * Register the given algorithm class with the given name as a child class of
43
+     * the given base algorithm.
44
+     * 
45
+     * @param baseAlgorithm Base algorithm class that corresponds to the newly
46
+     *                      registered algorithm class (e.g., generic algorithm
47
+     *                      class for the problem).
48
+     * @param name          Name for the registered algorithm class.
49
+     * @param algoClass     Algorithm class to register.
50
+     */
51
+    public static void registerAlgorithm(Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
52
+            String name, Class<? extends AbstractAlgorithm<?>> algoClass) {
53
+        if (!ALGORITHMS.containsKey(baseAlgorithm)) {
54
+            ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>());
55
+        }
56
+        ALGORITHMS.get(baseAlgorithm).put(name, algoClass);
57
+    }
58
+
59
+    /**
60
+     * Create an instance of the given algorithm class using the given input data.
61
+     * Assuming algorithm correspond to a class "Algorithm", this function returns
62
+     * an object equivalent to `new Algorithm(data)`.
63
+     * 
64
+     * @param algorithm Class of the algorithm to create.
65
+     * @param data      Input data for the algorithm.
66
+     * 
67
+     * @return A new instance of the given algorithm class using the given data.
68
+     * 
69
+     * @throws Exception if something wrong happens when constructing the object,
70
+     *                   i.e. the given input data does not correspond to the given
71
+     *                   algorithm and/or no constructor that takes a single
72
+     *                   parameter of type (data.getClass()) exists.
73
+     */
74
+    public static AbstractAlgorithm<?> createAlgorithm(
75
+            Class<? extends AbstractAlgorithm<?>> algorithm, AbstractInputData data)
76
+            throws Exception {
77
+        // Retrieve the set of constructors for the given algorithm class.
78
+        Constructor<?>[] constructors = algorithm.getDeclaredConstructors();
79
+
80
+        // Within this set, find the constructor that can be called with "data" (only).
81
+        AbstractAlgorithm<?> constructed = null;
82
+        for (Constructor<?> c: constructors) {
83
+            Class<?>[] params = c.getParameterTypes();
84
+            if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) {
85
+                c.setAccessible(true);
86
+                constructed = (AbstractAlgorithm<?>) c.newInstance(new Object[] { data });
87
+                break;
88
+            }
89
+        }
90
+        return constructed;
91
+    }
92
+
93
+    /**
94
+     * Return the algorithm class corresponding to the given base algorithm class
95
+     * and name. The algorithm must have been previously registered using
96
+     * registerAlgorithm.
97
+     * 
98
+     * @param baseAlgorithm Base algorithm class for the algorithm to retrieve.
99
+     * @param name          Name of the algorithm to retrieve.
100
+     * 
101
+     * @return Class corresponding to the given name.
102
+     * 
103
+     * @see #registerAlgorithm
104
+     */
105
+    public static Class<? extends AbstractAlgorithm<?>> getAlgorithmClass(
106
+            Class<? extends AbstractAlgorithm<?>> baseAlgorithm, String name) {
107
+        return ALGORITHMS.get(baseAlgorithm).get(name);
108
+    }
109
+
110
+    /**
111
+     * Return the list of names corresponding to the registered algorithm classes
112
+     * for the given base algorithm class.
113
+     * 
114
+     * @param baseAlgorithm Base algorithm class for the algorithm class names to
115
+     *                      retrieve.
116
+     * 
117
+     * @return Names of the currently registered algorithms.
118
+     * 
119
+     * @see #registerAlgorithm
120
+     */
121
+    public static Set<String> getAlgorithmNames(
122
+            Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
123
+        if (!ALGORITHMS.containsKey(baseAlgorithm)) {
124
+            return new TreeSet<>();
125
+        }
126
+        return ALGORITHMS.get(baseAlgorithm).keySet();
127
+    }
128
+}

+ 43
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/ArcInspector.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData.Mode;
4
+import org.insa.graphs.model.Arc;
5
+import org.insa.graphs.model.GraphStatistics;
6
+
7
+/**
8
+ * This class can be used to indicate to an algorithm which arcs can be used and
9
+ * the costs of the usable arcs..
10
+ *
11
+ */
12
+public interface ArcInspector {
13
+
14
+    /**
15
+     * Check if the given arc can be used (is allowed).
16
+     * 
17
+     * @param arc Arc to check.
18
+     * 
19
+     * @return true if the given arc is allowed.
20
+     */
21
+    public boolean isAllowed(Arc arc);
22
+
23
+    /**
24
+     * Find the cost of the given arc.
25
+     * 
26
+     * @param arc Arc for which the cost should be returned.
27
+     * 
28
+     * @return Cost of the arc.
29
+     */
30
+    public double getCost(Arc arc);
31
+
32
+    /**
33
+     * @return The maximum speed for this inspector, or
34
+     *         {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
35
+     */
36
+    public int getMaximumSpeed();
37
+
38
+    /**
39
+     * @return Mode for this arc inspector.
40
+     */
41
+    public Mode getMode();
42
+
43
+}

+ 177
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/ArcInspectorFactory.java View File

1
+package org.insa.graphs.algorithm;
2
+
3
+import java.util.ArrayList;
4
+import java.util.EnumSet;
5
+import java.util.List;
6
+
7
+import org.insa.graphs.algorithm.AbstractInputData.Mode;
8
+import org.insa.graphs.model.Arc;
9
+import org.insa.graphs.model.GraphStatistics;
10
+import org.insa.graphs.model.AccessRestrictions.AccessMode;
11
+import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
12
+
13
+public class ArcInspectorFactory {
14
+
15
+    /**
16
+     * @return List of all arc filters in this factory.
17
+     */
18
+    public static List<ArcInspector> getAllFilters() {
19
+        List<ArcInspector> filters = new ArrayList<>();
20
+
21
+        // Common filters:
22
+
23
+        // No filter (all arcs allowed):
24
+        filters.add(new ArcInspector() {
25
+            @Override
26
+            public boolean isAllowed(Arc arc) {
27
+                return true;
28
+            }
29
+
30
+            @Override
31
+            public double getCost(Arc arc) {
32
+                return arc.getLength();
33
+            }
34
+
35
+            @Override
36
+            public int getMaximumSpeed() {
37
+                return GraphStatistics.NO_MAXIMUM_SPEED;
38
+            }
39
+
40
+            @Override
41
+            public Mode getMode() {
42
+                return Mode.LENGTH;
43
+            }
44
+
45
+            @Override
46
+            public String toString() {
47
+                return "Shortest path, all roads allowed";
48
+            }
49
+        });
50
+
51
+        // Only road allowed for cars and length:
52
+        filters.add(new ArcInspector() {
53
+            @Override
54
+            public boolean isAllowed(Arc arc) {
55
+                return arc.getRoadInformation().getAccessRestrictions()
56
+                        .isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
57
+                                .of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
58
+            }
59
+
60
+            @Override
61
+            public double getCost(Arc arc) {
62
+                return arc.getLength();
63
+            }
64
+
65
+            @Override
66
+            public int getMaximumSpeed() {
67
+                return GraphStatistics.NO_MAXIMUM_SPEED;
68
+            }
69
+
70
+            @Override
71
+            public Mode getMode() {
72
+                return Mode.LENGTH;
73
+            }
74
+
75
+            @Override
76
+            public String toString() {
77
+                return "Shortest path, only roads open for cars";
78
+            }
79
+        });
80
+
81
+        // Only road allowed for cars and time:
82
+
83
+        filters.add(new ArcInspector() {
84
+            @Override
85
+            public boolean isAllowed(Arc arc) {
86
+                return true;
87
+            }
88
+
89
+            @Override
90
+            public double getCost(Arc arc) {
91
+                return arc.getMinimumTravelTime();
92
+            }
93
+
94
+            @Override
95
+            public int getMaximumSpeed() {
96
+                return GraphStatistics.NO_MAXIMUM_SPEED;
97
+            }
98
+
99
+            @Override
100
+            public Mode getMode() {
101
+                return Mode.TIME;
102
+            }
103
+
104
+            @Override
105
+            public String toString() {
106
+                return "Fastest path, all roads allowed";
107
+            }
108
+        });
109
+
110
+        filters.add(new ArcInspector() {
111
+            @Override
112
+            public boolean isAllowed(Arc arc) {
113
+                return arc.getRoadInformation().getAccessRestrictions()
114
+                        .isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
115
+                                .of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
116
+            }
117
+
118
+            @Override
119
+            public double getCost(Arc arc) {
120
+                return arc.getMinimumTravelTime();
121
+            }
122
+
123
+            @Override
124
+            public int getMaximumSpeed() {
125
+                return GraphStatistics.NO_MAXIMUM_SPEED;
126
+            }
127
+
128
+            @Override
129
+            public Mode getMode() {
130
+                return Mode.TIME;
131
+            }
132
+
133
+            @Override
134
+            public String toString() {
135
+                return "Fastest path, only roads open for cars";
136
+            }
137
+        });
138
+
139
+        // Non-private roads for pedestrian and bicycle:
140
+        filters.add(new ArcInspector() {
141
+
142
+            @Override
143
+            public boolean isAllowed(Arc arc) {
144
+                return arc.getRoadInformation().getAccessRestrictions()
145
+                        .isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet
146
+                                .of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
147
+            }
148
+
149
+            @Override
150
+            public double getCost(Arc arc) {
151
+                return arc.getTravelTime(
152
+                        Math.min(getMaximumSpeed(), arc.getRoadInformation().getMaximumSpeed()));
153
+            }
154
+
155
+            @Override
156
+            public String toString() {
157
+                return "Fastest path for pedestrian";
158
+            }
159
+
160
+            @Override
161
+            public int getMaximumSpeed() {
162
+                return 5;
163
+            }
164
+
165
+            @Override
166
+            public Mode getMode() {
167
+                return Mode.TIME;
168
+            }
169
+        });
170
+
171
+        // Add your own filters here (do not forget to implement toString()
172
+        // to get an understandable output!):
173
+
174
+        return filters;
175
+    }
176
+
177
+}

+ 24
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingAlgorithm.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+import org.insa.graphs.algorithm.AbstractAlgorithm;
4
+
5
+public abstract class CarPoolingAlgorithm extends AbstractAlgorithm<CarPoolingObserver> {
6
+
7
+    protected CarPoolingAlgorithm(CarPoolingData data) {
8
+        super(data);
9
+    }
10
+
11
+    @Override
12
+    public CarPoolingSolution run() {
13
+        return (CarPoolingSolution) super.run();
14
+    }
15
+
16
+    @Override
17
+    protected abstract CarPoolingSolution doRun();
18
+
19
+    @Override
20
+    public CarPoolingData getInputData() {
21
+        return (CarPoolingData) super.getInputData();
22
+    }
23
+
24
+}

+ 13
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingData.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData;
4
+import org.insa.graphs.algorithm.ArcInspector;
5
+import org.insa.graphs.model.Graph;
6
+
7
+public class CarPoolingData extends AbstractInputData {
8
+
9
+    protected CarPoolingData(Graph graph, ArcInspector arcFilter) {
10
+        super(graph, arcFilter);
11
+    }
12
+
13
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingGraphicObserver.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+public class CarPoolingGraphicObserver implements CarPoolingObserver {
4
+
5
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingObserver.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+public interface CarPoolingObserver {
4
+
5
+}

+ 11
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingSolution.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+import org.insa.graphs.algorithm.AbstractSolution;
4
+
5
+public class CarPoolingSolution extends AbstractSolution {
6
+
7
+    protected CarPoolingSolution(CarPoolingData data, Status status) {
8
+        super(data, status);
9
+    }
10
+
11
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/carpooling/CarPoolingTextObserver.java View File

1
+package org.insa.graphs.algorithm.carpooling;
2
+
3
+public class CarPoolingTextObserver implements CarPoolingObserver {
4
+
5
+}

+ 29
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchAlgorithm.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+import org.insa.graphs.algorithm.AbstractAlgorithm;
4
+
5
+public abstract class PackageSwitchAlgorithm extends AbstractAlgorithm<PackageSwitchObserver> {
6
+
7
+    /**
8
+     * Create a new PackageSwitchAlgorithm with the given data.
9
+     * 
10
+     * @param data
11
+     */
12
+    protected PackageSwitchAlgorithm(PackageSwitchData data) {
13
+        super(data);
14
+    }
15
+
16
+    @Override
17
+    public PackageSwitchSolution run() {
18
+        return (PackageSwitchSolution) super.run();
19
+    }
20
+
21
+    @Override
22
+    protected abstract PackageSwitchSolution doRun();
23
+
24
+    @Override
25
+    public PackageSwitchData getInputData() {
26
+        return (PackageSwitchData) super.getInputData();
27
+    }
28
+
29
+}

+ 13
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchData.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData;
4
+import org.insa.graphs.algorithm.ArcInspector;
5
+import org.insa.graphs.model.Graph;
6
+
7
+public class PackageSwitchData extends AbstractInputData {
8
+
9
+    protected PackageSwitchData(Graph graph, ArcInspector arcFilter) {
10
+        super(graph, arcFilter);
11
+    }
12
+
13
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchGraphicObserver.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
4
+
5
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchObserver.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+public interface PackageSwitchObserver {
4
+
5
+}

+ 11
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchSolution.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+import org.insa.graphs.algorithm.AbstractSolution;
4
+
5
+public class PackageSwitchSolution extends AbstractSolution {
6
+
7
+    protected PackageSwitchSolution(PackageSwitchData data, Status status) {
8
+        super(data, status);
9
+    }
10
+
11
+}

+ 5
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/packageswitch/PackageSwitchTextObserver.java View File

1
+package org.insa.graphs.algorithm.packageswitch;
2
+
3
+public class PackageSwitchTextObserver implements PackageSwitchObserver {
4
+
5
+}

+ 58
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/AStarAlgorithm.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData.Mode;
4
+import org.insa.graphs.model.Node;
5
+import org.insa.graphs.model.Point;
6
+
7
+public class AStarAlgorithm extends DijkstraAlgorithm {
8
+
9
+    Node arrivee;
10
+    Mode mode;
11
+    double max_speed;
12
+
13
+    class LabelStar extends Label {
14
+
15
+        Node arrivee;
16
+        double heuristiqueArrive;
17
+
18
+        public LabelStar(Node sommet_courant, Node pere, double cout, Node arrivee) {
19
+            super(sommet_courant, pere, cout);
20
+            this.arrivee = arrivee;
21
+
22
+            switch (mode) {
23
+                case TIME:
24
+                    this.heuristiqueArrive = this.sommet_courant.getPoint().distanceTo(arrivee.getPoint()) / max_speed;
25
+                    break;
26
+                case LENGTH:
27
+                    this.heuristiqueArrive = Point.distance(super.sommet_courant.getPoint(), arrivee.getPoint());
28
+                    break;
29
+            }
30
+        }
31
+
32
+        @Override
33
+        public int compareTo(Label other) throws IllegalArgumentException {
34
+            return Double.compare(
35
+                this.cout + heuristiqueArrive, 
36
+                other.cout + ((LabelStar)other).heuristiqueArrive);
37
+        }
38
+    }
39
+
40
+    public AStarAlgorithm(ShortestPathData data) {
41
+        super(data);
42
+        this.arrivee = data.getDestination();
43
+        this.mode = data.getMode();
44
+        this.max_speed = (double) data.getMaximumSpeed()/3.6;
45
+        if (this.max_speed < 0){
46
+            this.max_speed = (double) data.getGraph().getGraphInformation().getMaximumSpeed() / 3.6;                
47
+        }
48
+    }
49
+
50
+    @Override
51
+    public Label createLabel(Node n) {
52
+        return new LabelStar(n, null, Float.MAX_VALUE, arrivee);
53
+    }
54
+
55
+    public Label createLabel(Node n, double cout) {
56
+        return new LabelStar(n, null, cout, arrivee);
57
+    }
58
+}

+ 100
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/BellmanFordAlgorithm.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Arrays;
5
+import java.util.Collections;
6
+
7
+import org.insa.graphs.algorithm.AbstractSolution.Status;
8
+import org.insa.graphs.model.Arc;
9
+import org.insa.graphs.model.Graph;
10
+import org.insa.graphs.model.Node;
11
+import org.insa.graphs.model.Path;
12
+
13
+public class BellmanFordAlgorithm extends ShortestPathAlgorithm {
14
+
15
+    public BellmanFordAlgorithm(ShortestPathData data) {
16
+        super(data);
17
+    }
18
+
19
+    @Override
20
+    protected ShortestPathSolution doRun() {
21
+
22
+        // Retrieve the graph.
23
+        ShortestPathData data = getInputData();
24
+        Graph graph = data.getGraph();
25
+
26
+        final int nbNodes = graph.size();
27
+
28
+        // Initialize array of distances.
29
+        double[] distances = new double[nbNodes];
30
+        Arrays.fill(distances, Double.POSITIVE_INFINITY);
31
+        distances[data.getOrigin().getId()] = 0;
32
+
33
+        // Notify observers about the first event (origin processed).
34
+        notifyOriginProcessed(data.getOrigin());
35
+
36
+        // Initialize array of predecessors.
37
+        Arc[] predecessorArcs = new Arc[nbNodes];
38
+
39
+        // Actual algorithm, we will assume the graph does not contain negative
40
+        // cycle...
41
+        boolean found = false;
42
+        for (int i = 0; !found && i < nbNodes; ++i) {
43
+            found = true;
44
+            for (Node node: graph.getNodes()) {
45
+                for (Arc arc: node.getSuccessors()) {
46
+
47
+                    // Small test to check allowed roads...
48
+                    if (!data.isAllowed(arc)) {
49
+                        continue;
50
+                    }
51
+
52
+                    // Retrieve weight of the arc.
53
+                    double w = data.getCost(arc);
54
+                    double oldDistance = distances[arc.getDestination().getId()];
55
+                    double newDistance = distances[node.getId()] + w;
56
+
57
+                    if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) {
58
+                        notifyNodeReached(arc.getDestination());
59
+                    }
60
+
61
+                    // Check if new distances would be better, if so update...
62
+                    if (newDistance < oldDistance) {
63
+                        found = false;
64
+                        distances[arc.getDestination().getId()] = distances[node.getId()] + w;
65
+                        predecessorArcs[arc.getDestination().getId()] = arc;
66
+                    }
67
+                }
68
+            }
69
+        }
70
+
71
+        ShortestPathSolution solution = null;
72
+
73
+        // Destination has no predecessor, the solution is infeasible...
74
+        if (predecessorArcs[data.getDestination().getId()] == null) {
75
+            solution = new ShortestPathSolution(data, Status.INFEASIBLE);
76
+        }
77
+        else {
78
+
79
+            // The destination has been found, notify the observers.
80
+            notifyDestinationReached(data.getDestination());
81
+
82
+            // Create the path from the array of predecessors...
83
+            ArrayList<Arc> arcs = new ArrayList<>();
84
+            Arc arc = predecessorArcs[data.getDestination().getId()];
85
+            while (arc != null) {
86
+                arcs.add(arc);
87
+                arc = predecessorArcs[arc.getOrigin().getId()];
88
+            }
89
+
90
+            // Reverse the path...
91
+            Collections.reverse(arcs);
92
+
93
+            // Create the final solution.
94
+            solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs));
95
+        }
96
+
97
+        return solution;
98
+    }
99
+
100
+}

+ 154
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/DijkstraAlgorithm.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.HashMap;
6
+
7
+import org.insa.graphs.algorithm.AbstractSolution.Status;
8
+import org.insa.graphs.model.Arc;
9
+import org.insa.graphs.model.Node;
10
+import org.insa.graphs.model.Path;
11
+
12
+import org.insa.graphs.algorithm.utils.*;
13
+
14
+public class DijkstraAlgorithm extends ShortestPathAlgorithm {
15
+
16
+    class Label implements Comparable<Label> {
17
+        Node sommet_courant, pere;
18
+        Boolean marque;
19
+        double cout;
20
+
21
+        public Label(Node sommet_courant, Node pere, double cout) {
22
+            this.sommet_courant = sommet_courant;
23
+            this.pere = pere;
24
+            this.marque = false;
25
+            this.cout = cout;
26
+        }
27
+
28
+        public double getCost() {
29
+            return cout;
30
+        }
31
+
32
+        public void setMark() {
33
+            this.marque = true;
34
+        }
35
+
36
+        public int compareTo(Label other) {
37
+            return Double.compare(cout, other.cout);
38
+        }
39
+    }
40
+
41
+    public DijkstraAlgorithm(ShortestPathData data) {
42
+        super(data);
43
+    }
44
+
45
+    public Label createLabel(Node n){
46
+        return new Label(n, null, Float.MAX_VALUE);
47
+    }
48
+
49
+    public Label createLabel(Node n, double cout) {
50
+        return new Label(n, null, cout);
51
+    }
52
+
53
+    @Override
54
+    protected ShortestPathSolution doRun() {
55
+        // Récupération des données du problème
56
+        final ShortestPathData data = getInputData();
57
+
58
+        // Cas triviaux
59
+        if (data.getOrigin() == data.getDestination())
60
+            return new ShortestPathSolution(data, Status.OPTIMAL, new Path(data.getGraph(), data.getOrigin()));
61
+
62
+        // Tas des nodes visités, initialisée avec le node d'origine
63
+        BinaryHeap<Label> visite = new BinaryHeap<Label>();
64
+        visite.insert(new Label(data.getOrigin(), null, 0));
65
+
66
+        notifyOriginProcessed(data.getOrigin());
67
+
68
+        // Map de correspondance Node - Label initialisée avec des labels par défaut.
69
+        HashMap<Node, Label> map = new HashMap<Node, Label>();
70
+        for (Node n : data.getGraph().getNodes())
71
+            map.put(n, createLabel(n));
72
+
73
+        /// Varibles de fonctionnement
74
+        // Varible contenant le node le plus intéressant
75
+        Label smallest;
76
+        // Variable contenant le node destination
77
+        Node dest = data.getDestination();
78
+
79
+        while (true) {
80
+            // Est-ce qu'il reste des nodes à explorer ?
81
+            // Si oui, on récupère le plus intéressant
82
+            // Sinon, il n'y a pas de solution --> on quitte
83
+            try {
84
+                smallest = visite.deleteMin();
85
+            } catch (EmptyPriorityQueueException e) {
86
+                return new ShortestPathSolution(data, Status.INFEASIBLE);
87
+            }
88
+
89
+            // On marque ce node
90
+            smallest.setMark();
91
+            notifyNodeMarked(smallest.sommet_courant);
92
+
93
+            // Si on est arrivé, on arrête
94
+            if (smallest.sommet_courant == dest) break;
95
+
96
+            // Pour chacun des sommets fils, on regarde s'ils proposent des chemins plus intéressants
97
+            for (Arc arc : smallest.sommet_courant.getSuccessors()) {
98
+
99
+                // On test si le chemin n'est pas permis, on skip
100
+                if (!data.isAllowed(arc)) continue;
101
+
102
+                Node node = arc.getDestination();
103
+                Label l = map.get(node);
104
+
105
+                // S'il est déjà marqué, on skip
106
+                if (l.marque) continue;
107
+
108
+                double n_cout = smallest.getCost() + data.getCost(arc);
109
+
110
+                //if (l.compareTo(createLabel(node, n_cout)) == 1) {
111
+                if (l.cout > n_cout) {
112
+                    visite.tryremove(l);
113
+                    l.cout = n_cout;
114
+                    l.pere = smallest.sommet_courant;
115
+                    l.sommet_courant = node;
116
+                    notifyNodeReached(node);
117
+                    visite.insert(l);
118
+                }
119
+            }
120
+        }
121
+
122
+        notifyDestinationReached(smallest.sommet_courant);
123
+
124
+        // Liste des nodes formant la route entre notre origine et destination
125
+        ArrayList<Node> nodes = new ArrayList<Node>();
126
+        Label last = smallest;
127
+
128
+        // On remplit la liste en se référant au père à chaque fois
129
+        nodes.add(last.sommet_courant);
130
+        while (true) {
131
+            smallest = map.get(smallest.pere);
132
+            nodes.add(smallest.sommet_courant);
133
+            if (smallest == map.get(data.getOrigin())) break;
134
+        }
135
+
136
+        // On inverve le sens des nodes 
137
+        Collections.reverse(nodes);
138
+
139
+        // On créé le chemin
140
+        Path p = Path.createShortestPathFromNodes(data.getGraph(), nodes);
141
+
142
+        /** Vérification des résultats --> Toutes les tests sont ok */
143
+        // Vérification des coûts croisants (l'adjectif, pas la patisserie)
144
+        //for (Node n: nodes) System.out.println(map.get(n).cout);
145
+
146
+        // Vérification du chemin
147
+        //if (p.isValid()) System.out.println("Chemin valide !");
148
+        //System.out.println("CD: " + last.cout + "; CPCC: " + p.getLength());
149
+
150
+
151
+        // On créé on chemin à partir de la liste que l'on donne en solution
152
+        return new ShortestPathSolution(data, Status.OPTIMAL, p);
153
+    }
154
+}

+ 69
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathAlgorithm.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import org.insa.graphs.algorithm.AbstractAlgorithm;
4
+import org.insa.graphs.model.Node;
5
+
6
+public abstract class ShortestPathAlgorithm extends AbstractAlgorithm<ShortestPathObserver> {
7
+
8
+    protected ShortestPathAlgorithm(ShortestPathData data) {
9
+        super(data);
10
+    }
11
+
12
+    @Override
13
+    public ShortestPathSolution run() {
14
+        return (ShortestPathSolution) super.run();
15
+    }
16
+
17
+    @Override
18
+    protected abstract ShortestPathSolution doRun();
19
+
20
+    @Override
21
+    public ShortestPathData getInputData() {
22
+        return (ShortestPathData) super.getInputData();
23
+    }
24
+
25
+    /**
26
+     * Notify all observers that the origin has been processed.
27
+     * 
28
+     * @param node Origin.
29
+     */
30
+    public void notifyOriginProcessed(Node node) {
31
+        for (ShortestPathObserver obs: getObservers()) {
32
+            obs.notifyOriginProcessed(node);
33
+        }
34
+    }
35
+
36
+    /**
37
+     * Notify all observers that a node has been reached for the first time.
38
+     * 
39
+     * @param node Node that has been reached.
40
+     */
41
+    public void notifyNodeReached(Node node) {
42
+        for (ShortestPathObserver obs: getObservers()) {
43
+            obs.notifyNodeReached(node);
44
+        }
45
+    }
46
+
47
+    /**
48
+     * Notify all observers that a node has been marked, i.e. its final value has
49
+     * been set.
50
+     * 
51
+     * @param node Node that has been marked.
52
+     */
53
+    public void notifyNodeMarked(Node node) {
54
+        for (ShortestPathObserver obs: getObservers()) {
55
+            obs.notifyNodeMarked(node);
56
+        }
57
+    }
58
+
59
+    /**
60
+     * Notify all observers that the destination has been reached.
61
+     * 
62
+     * @param node Destination.
63
+     */
64
+    public void notifyDestinationReached(Node node) {
65
+        for (ShortestPathObserver obs: getObservers()) {
66
+            obs.notifyDestinationReached(node);
67
+        }
68
+    }
69
+}

+ 47
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathData.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData;
4
+import org.insa.graphs.algorithm.ArcInspector;
5
+import org.insa.graphs.model.Graph;
6
+import org.insa.graphs.model.Node;
7
+
8
+public class ShortestPathData extends AbstractInputData {
9
+
10
+    // Origin and destination nodes.
11
+    private final Node origin, destination;
12
+
13
+    /**
14
+     * Construct a new instance of ShortestPathInputData with the given parameters.
15
+     * 
16
+     * @param graph Graph in which the path should be looked for.
17
+     * @param origin Origin node of the path.
18
+     * @param destination Destination node of the path.
19
+     * @param arcInspector Filter for arcs (used to allow only a specific set of
20
+     *        arcs in the graph to be used).
21
+     */
22
+    public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) {
23
+        super(graph, arcInspector);
24
+        this.origin = origin;
25
+        this.destination = destination;
26
+    }
27
+
28
+    /**
29
+     * @return Origin node for the path.
30
+     */
31
+    public Node getOrigin() {
32
+        return origin;
33
+    }
34
+
35
+    /**
36
+     * @return Destination node for the path.
37
+     */
38
+    public Node getDestination() {
39
+        return destination;
40
+    }
41
+
42
+    @Override
43
+    public String toString() {
44
+        return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " ["
45
+                + this.arcInspector.toString().toLowerCase() + "]";
46
+    }
47
+}

+ 37
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathObserver.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import org.insa.graphs.model.Node;
4
+
5
+public interface ShortestPathObserver {
6
+	
7
+	/**
8
+	 * Notify the observer that the origin has been processed.
9
+	 * 
10
+	 * @param node Origin.
11
+	 */
12
+	public void notifyOriginProcessed(Node node);
13
+	
14
+	/**
15
+	 * Notify the observer that a node has been reached for the first
16
+	 * time.
17
+	 * 
18
+	 * @param node Node that has been reached.
19
+	 */
20
+	public void notifyNodeReached(Node node);
21
+	
22
+	/**
23
+	 * Notify the observer that a node has been marked, i.e. its final
24
+	 * value has been set.
25
+	 * 
26
+	 * @param node Node that has been marked.
27
+	 */
28
+	public void notifyNodeMarked(Node node);
29
+
30
+	/**
31
+	 * Notify the observer that the destination has been reached.
32
+	 * 
33
+	 * @param node Destination.
34
+	 */
35
+	public void notifyDestinationReached(Node node);
36
+	
37
+}

+ 74
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathSolution.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData.Mode;
4
+import org.insa.graphs.model.Arc;
5
+import org.insa.graphs.model.Path;
6
+import org.insa.graphs.algorithm.AbstractSolution;
7
+
8
+public class ShortestPathSolution extends AbstractSolution {
9
+
10
+    // Optimal solution.
11
+    private final Path path;
12
+
13
+    /**
14
+     * Create a new infeasible shortest-path solution for the given input and
15
+     * status.
16
+     * 
17
+     * @param data Original input data for this solution.
18
+     * @param status Status of the solution (UNKNOWN / INFEASIBLE).
19
+     */
20
+    public ShortestPathSolution(ShortestPathData data, Status status) {
21
+        super(data, status);
22
+        this.path = null;
23
+    }
24
+
25
+    /**
26
+     * Create a new shortest-path solution.
27
+     * 
28
+     * @param data Original input data for this solution.
29
+     * @param status Status of the solution (FEASIBLE / OPTIMAL).
30
+     * @param path Path corresponding to the solution.
31
+     */
32
+    public ShortestPathSolution(ShortestPathData data, Status status, Path path) {
33
+        super(data, status);
34
+        this.path = path;
35
+    }
36
+
37
+    @Override
38
+    public ShortestPathData getInputData() {
39
+        return (ShortestPathData) super.getInputData();
40
+    }
41
+
42
+    /**
43
+     * @return The path of this solution, if any.
44
+     */
45
+    public Path getPath() {
46
+        return path;
47
+    }
48
+
49
+    @Override
50
+    public String toString() {
51
+        String info = null;
52
+        if (!isFeasible()) {
53
+            info = String.format("No path found from node #%d to node #%d",
54
+                    getInputData().getOrigin().getId(), getInputData().getDestination().getId());
55
+        }
56
+        else {
57
+            double cost = 0;
58
+            for (Arc arc: getPath().getArcs()) {
59
+                cost += getInputData().getCost(arc);
60
+            }
61
+            info = String.format("Found a path from node #%d to node #%d",
62
+                    getInputData().getOrigin().getId(), getInputData().getDestination().getId());
63
+            if (getInputData().getMode() == Mode.LENGTH) {
64
+                info = String.format("%s, %.4f kilometers", info, cost / 1000.0);
65
+            }
66
+            else {
67
+                info = String.format("%s, %.4f minutes", info, cost / 60.0);
68
+            }
69
+        }
70
+        info += " in " + getSolvingTime().getSeconds() + " seconds.";
71
+        return info;
72
+    }
73
+
74
+}

+ 37
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/shortestpath/ShortestPathTextObserver.java View File

1
+package org.insa.graphs.algorithm.shortestpath;
2
+
3
+import java.io.PrintStream;
4
+
5
+import org.insa.graphs.model.Node;
6
+
7
+public class ShortestPathTextObserver implements ShortestPathObserver {
8
+
9
+    private final PrintStream stream;
10
+
11
+    public ShortestPathTextObserver(PrintStream stream) {
12
+        this.stream = stream;
13
+    }
14
+
15
+    @Override
16
+    public void notifyOriginProcessed(Node node) {
17
+        // TODO Auto-generated method stub
18
+
19
+    }
20
+
21
+    @Override
22
+    public void notifyNodeReached(Node node) {
23
+        stream.println("Node " + node.getId() + " reached.");
24
+    }
25
+
26
+    @Override
27
+    public void notifyNodeMarked(Node node) {
28
+        stream.println("Node " + node.getId() + " marked.");
29
+    }
30
+
31
+    @Override
32
+    public void notifyDestinationReached(Node node) {
33
+        // TODO Auto-generated method stub
34
+
35
+    }
36
+
37
+}

+ 234
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinaryHeap.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+import java.util.ArrayList;
4
+
5
+/**
6
+ * Implements a binary heap containing elements of type E.
7
+ *
8
+ * Note that all comparisons are based on the compareTo method, hence E must
9
+ * implement Comparable
10
+ * 
11
+ * @author Mark Allen Weiss
12
+ * @author DLB
13
+ */
14
+public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
15
+
16
+    // Number of elements in heap.
17
+    private int currentSize;
18
+
19
+    // The heap array.
20
+    protected final ArrayList<E> array;
21
+
22
+    /**
23
+     * Construct a new empty binary heap.
24
+     */
25
+    public BinaryHeap() {
26
+        this.currentSize = 0;
27
+        this.array = new ArrayList<E>();
28
+    }
29
+
30
+    /**
31
+     * Construct a copy of the given heap.
32
+     * 
33
+     * @param heap Binary heap to copy.
34
+     */
35
+    public BinaryHeap(BinaryHeap<E> heap) {
36
+        this.currentSize = heap.currentSize;
37
+        this.array = new ArrayList<E>(heap.array);
38
+    }
39
+
40
+    /**
41
+     * Set an element at the given index.
42
+     * 
43
+     * @param index Index at which the element should be set.
44
+     * @param value Element to set.
45
+     */
46
+    private void arraySet(int index, E value) {
47
+        if (index == this.array.size()) {
48
+            this.array.add(value);
49
+        }
50
+        else {
51
+            this.array.set(index, value);
52
+        }
53
+    }
54
+
55
+    /**
56
+     * @return Index of the parent of the given index.
57
+     */
58
+    protected int indexParent(int index) {
59
+        return (index - 1) / 2;
60
+    }
61
+
62
+    /**
63
+     * @return Index of the left child of the given index.
64
+     */
65
+    protected int indexLeft(int index) {
66
+        return index * 2 + 1;
67
+    }
68
+
69
+    /**
70
+     * Internal method to percolate up in the heap.
71
+     * 
72
+     * @param index Index at which the percolate begins.
73
+     */
74
+    private void percolateUp(int index) {
75
+        E x = this.array.get(index);
76
+
77
+        for (; index > 0
78
+                && x.compareTo(this.array.get(indexParent(index))) < 0; index = indexParent(
79
+                        index)) {
80
+            E moving_val = this.array.get(indexParent(index));
81
+            this.arraySet(index, moving_val);
82
+        }
83
+
84
+        this.arraySet(index, x);
85
+    }
86
+
87
+    /**
88
+     * Internal method to percolate down in the heap.
89
+     * 
90
+     * @param index Index at which the percolate begins.
91
+     */
92
+    private void percolateDown(int index) {
93
+        int ileft = indexLeft(index);
94
+        int iright = ileft + 1;
95
+
96
+        if (ileft < this.currentSize) {
97
+            E current = this.array.get(index);
98
+            E left = this.array.get(ileft);
99
+            boolean hasRight = iright < this.currentSize;
100
+            E right = (hasRight) ? this.array.get(iright) : null;
101
+
102
+            if (!hasRight || left.compareTo(right) < 0) {
103
+                // Left is smaller
104
+                if (left.compareTo(current) < 0) {
105
+                    this.arraySet(index, left);
106
+                    this.arraySet(ileft, current);
107
+                    this.percolateDown(ileft);
108
+                }
109
+            }
110
+            else {
111
+                // Right is smaller
112
+                if (right.compareTo(current) < 0) {
113
+                    this.arraySet(index, right);
114
+                    this.arraySet(iright, current);
115
+                    this.percolateDown(iright);
116
+                }
117
+            }
118
+        }
119
+    }
120
+
121
+    @Override
122
+    public boolean isEmpty() {
123
+        return this.currentSize == 0;
124
+    }
125
+
126
+    @Override
127
+    public int size() {
128
+        return this.currentSize;
129
+    }
130
+
131
+    @Override
132
+    public void insert(E x) {
133
+        int index = this.currentSize++;
134
+        this.arraySet(index, x);
135
+        this.percolateUp(index);
136
+    }
137
+
138
+    @Override
139
+    public void remove(E x) throws ElementNotFoundException {
140
+        // Difficile de faire mieux que du O(n) ici 
141
+        // Surtout qu'un tas binaire n'est pas fait pour
142
+        // chercher un élément...
143
+        int ie = this.array.indexOf(x);
144
+
145
+        //int ie = 0;
146
+    	//while (ie<this.currentSize && !this.array.get(ie).equals(x)) ie++;
147
+
148
+
149
+        if (this.isEmpty() || ie < 0 || ie >= this.currentSize) 
150
+            throw new ElementNotFoundException(x);
151
+
152
+        E last = this.array.get(--this.currentSize);
153
+        this.arraySet(ie, last);
154
+        this.percolateUp(ie);
155
+        this.percolateDown(ie);
156
+    }
157
+
158
+    /** Try to remove an element without raising any exception */
159
+    public void tryremove(E x) {
160
+        try {
161
+            remove(x);
162
+        }
163
+        catch (ElementNotFoundException e) {
164
+            ;
165
+        }
166
+    }
167
+
168
+    public boolean isIn(E x) {
169
+        return this.array.contains(x);
170
+    }
171
+
172
+    @Override
173
+    public E findMin() throws EmptyPriorityQueueException {
174
+        if (isEmpty())
175
+            throw new EmptyPriorityQueueException();
176
+        return this.array.get(0);
177
+    }
178
+
179
+    @Override
180
+    public E deleteMin() throws EmptyPriorityQueueException {
181
+        E minItem = findMin();
182
+        E lastItem = this.array.get(--this.currentSize);
183
+        this.arraySet(0, lastItem);
184
+        this.percolateDown(0);
185
+        return minItem;
186
+    }
187
+
188
+    /**
189
+     * Creates a multi-lines string representing a sorted view of this binary heap.
190
+     * 
191
+     * @return a string containing a sorted view this binary heap.
192
+     */
193
+    public String toStringSorted() {
194
+        return BinaryHeapFormatter.toStringSorted(this, -1);
195
+    }
196
+
197
+    /**
198
+     * Creates a multi-lines string representing a sorted view of this binary heap.
199
+     * 
200
+     * @param maxElement Maximum number of elements to display. or {@code -1} to
201
+     *                   display all the elements.
202
+     * 
203
+     * @return a string containing a sorted view this binary heap.
204
+     */
205
+    public String toStringSorted(int maxElement) {
206
+        return BinaryHeapFormatter.toStringSorted(this, maxElement);
207
+    }
208
+
209
+    /**
210
+     * Creates a multi-lines string representing a tree view of this binary heap.
211
+     * 
212
+     * @return a string containing a tree view of this binary heap.
213
+     */
214
+    public String toStringTree() {
215
+        return BinaryHeapFormatter.toStringTree(this, Integer.MAX_VALUE);
216
+    }
217
+
218
+    /**
219
+     * Creates a multi-lines string representing a tree view of this binary heap.
220
+     * 
221
+     * @param maxDepth Maximum depth of the tree to display.
222
+     * 
223
+     * @return a string containing a tree view of this binary heap.
224
+     */
225
+    public String toStringTree(int maxDepth) {
226
+        return BinaryHeapFormatter.toStringTree(this, maxDepth);
227
+    }
228
+
229
+    @Override
230
+    public String toString() {
231
+        return BinaryHeapFormatter.toStringTree(this, 8);
232
+    }
233
+
234
+}

+ 198
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinaryHeapFormatter.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+import java.util.ArrayList;
4
+
5
+public class BinaryHeapFormatter {
6
+
7
+    /**
8
+     * This class is used by {@link #toStringTree}, and simply contains three string
9
+     * accumulating. This is an immutable class.
10
+     *
11
+     */
12
+    private static class Context {
13
+
14
+        // Output text:
15
+        public final String acu;
16
+
17
+        // Margin to get back exactly under the current position:
18
+        public final String margin;
19
+
20
+        // Last margin used for the last child of a node. The markers are different:
21
+        public final String lastmargin;
22
+
23
+        /**
24
+         * Creaet a new {@code Context}.
25
+         * 
26
+         * @param acu        The accumulated string.
27
+         * @param margin     The current margin.
28
+         * @param lastMargin The last margin used.
29
+         */
30
+        public Context(String acu, String margin, String lastMargin) {
31
+            this.acu = acu;
32
+            this.margin = margin;
33
+            this.lastmargin = lastMargin;
34
+        }
35
+
36
+        /**
37
+         * Creates a new context by appending newlines to this context.
38
+         * 
39
+         * @param n Number of newlines to append.
40
+         * 
41
+         * @return a new context with {@code n} newlines appended.
42
+         */
43
+        public Context appendNewlines(int n) {
44
+            if (n <= 0) {
45
+                return this;
46
+            }
47
+            else {
48
+                return (new Context(this.acu + "\n" + this.margin, this.margin, this.lastmargin)
49
+                        .appendNewlines(n - 1));
50
+            }
51
+        }
52
+
53
+        /**
54
+         * Creates a new context by appending the given string to this context.
55
+         * 
56
+         * @param count Number of spaces to add to the margin, or {@code null} to use
57
+         *              the length of the string.
58
+         * @param text  String to append.
59
+         * 
60
+         * @return a new context with {@code text} appended.
61
+         */
62
+        public Context appendText(Integer count, String text) {
63
+            int cnt = (count == null) ? text.length() : count;
64
+            final String spaces = new String(new char[cnt]).replace('\0', ' ');
65
+            return new Context(this.acu + text, this.margin + spaces, this.lastmargin + spaces);
66
+        }
67
+
68
+        /**
69
+         * Creates a new context by appending a branch to this context.
70
+         * 
71
+         * @param n     Number of spaces to add to the margin, or {@code null} to use
72
+         *              the length of the string.
73
+         * @param label Name of the branch.
74
+         * 
75
+         * @return a new context with the branch appended.
76
+         */
77
+        public Context appendBranch(Integer count, String label) {
78
+            final Context ctxt = this.appendText(count, label);
79
+
80
+            if (count == null) {
81
+                return new Context(ctxt.acu + "_", ctxt.margin + "|", ctxt.margin + " ");
82
+            }
83
+            else {
84
+                return new Context(ctxt.acu, ctxt.margin + "|", ctxt.margin + " ")
85
+                        .appendNewlines(1);
86
+
87
+            }
88
+        }
89
+    }
90
+
91
+    /*
92
+     * Input : ready to write the current node at the current context position.
93
+     * Output : the last character of acu is the last character of the current node.
94
+     */
95
+    protected static <E extends Comparable<E>> Context toStringLoop(BinaryHeap<E> heap,
96
+            Context ctxt, int node, int max_depth) {
97
+
98
+        if (max_depth < 0) {
99
+            return ctxt.appendText(null, "...");
100
+        }
101
+        else {
102
+            E nodeval = heap.array.get(node);
103
+            String nodevals = nodeval.toString();
104
+
105
+            ArrayList<Integer> childs = new ArrayList<Integer>();
106
+            // Add childs
107
+            int index_left = heap.indexLeft(node);
108
+            int index_right = index_left + 1;
109
+
110
+            if (index_left < heap.size()) {
111
+                childs.add(index_left);
112
+            }
113
+            if (index_right < heap.size()) {
114
+                childs.add(index_right);
115
+            }
116
+
117
+            Context ctxt2 = childs.isEmpty() ? ctxt.appendText(null, nodevals)
118
+                    : ctxt.appendBranch(1, nodevals);
119
+
120
+            for (int ch = 0; ch < childs.size(); ch++) {
121
+                boolean is_last = (ch == childs.size() - 1);
122
+                int child = childs.get(ch);
123
+
124
+                if (is_last) {
125
+                    Context ctxt3 = new Context(ctxt2.acu, ctxt2.lastmargin, ctxt2.lastmargin);
126
+                    ctxt2 = new Context(toStringLoop(heap, ctxt3.appendText(null, "___"), child,
127
+                            max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin);
128
+                }
129
+                else {
130
+                    ctxt2 = new Context(toStringLoop(heap, ctxt2.appendText(null, "___"), child,
131
+                            max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin).appendNewlines(2);
132
+                }
133
+            }
134
+
135
+            return ctxt2;
136
+        }
137
+    }
138
+
139
+    /**
140
+     * Creates a multi-lines string representing a tree view of the given binary
141
+     * heap.
142
+     * 
143
+     * @param heap     The binary heap to display.
144
+     * @param maxDepth Maximum depth of the tree to display.
145
+     * 
146
+     * @return a string containing a tree view of the given binary heap.
147
+     */
148
+    public static <E extends Comparable<E>> String toStringTree(BinaryHeap<E> heap, int maxDepth) {
149
+        final Context init_context = new Context("   ", "   ", "   ");
150
+        final Context result = toStringLoop(heap, init_context, 0, maxDepth);
151
+        return result.acu;
152
+    }
153
+
154
+    /**
155
+     * Creates a multi-lines string representing a sorted view of the given binary
156
+     * heap.
157
+     * 
158
+     * @param heap       The binary heap to display.
159
+     * @param maxElement Maximum number of elements to display. or {@code -1} to
160
+     *                   display all the elements.
161
+     * 
162
+     * @return a string containing a sorted view the given binary heap.
163
+     */
164
+    public static <E extends Comparable<E>> String toStringSorted(BinaryHeap<E> heap,
165
+            int max_elements) {
166
+        String result = "";
167
+        final BinaryHeap<E> copy = new BinaryHeap<E>(heap);
168
+
169
+        final String truncate;
170
+        if (max_elements < 0 || max_elements >= heap.size()) {
171
+            truncate = "";
172
+        }
173
+        else {
174
+            truncate = ", only " + max_elements + " elements are shown";
175
+        }
176
+
177
+        result += "========  Sorted HEAP  (size = " + heap.size() + truncate + ")  ========\n\n";
178
+
179
+        while (!copy.isEmpty() && max_elements-- != 0) {
180
+            result += copy.deleteMin() + "\n";
181
+        }
182
+
183
+        result += "\n--------  End of heap  --------";
184
+
185
+        return result;
186
+    }
187
+
188
+    public static void main(String[] args) {
189
+        final BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
190
+
191
+        for (int i = 0; i < 12; i++) {
192
+            heap.insert(i);
193
+        }
194
+
195
+        System.out.println(heap.toStringSorted(-1));
196
+        System.out.println(heap.toStringTree(6));
197
+    }
198
+}

+ 64
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/BinarySearchTree.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+import java.util.SortedSet;
4
+import java.util.TreeSet;
5
+
6
+public class BinarySearchTree<E extends Comparable<E>> implements PriorityQueue<E> {
7
+
8
+    // Underlying implementation
9
+    private final SortedSet<E> sortedSet;
10
+
11
+    /**
12
+     * Create a new empty binary search tree.
13
+     */
14
+    public BinarySearchTree() {
15
+        this.sortedSet = new TreeSet<>();
16
+    }
17
+
18
+    /**
19
+     * Create a copy of the given binary search tree.
20
+     * 
21
+     * @param bst Binary search tree to copy.
22
+     */
23
+    public BinarySearchTree(BinarySearchTree<E> bst) {
24
+        this.sortedSet = new TreeSet<>(bst.sortedSet);
25
+    }
26
+
27
+    @Override
28
+    public boolean isEmpty() {
29
+        return sortedSet.isEmpty();
30
+    }
31
+
32
+    @Override
33
+    public int size() {
34
+        return sortedSet.size();
35
+    }
36
+
37
+    @Override
38
+    public void insert(E x) {
39
+        sortedSet.add(x);
40
+    }
41
+
42
+    @Override
43
+    public void remove(E x) throws ElementNotFoundException {
44
+        if (!sortedSet.remove(x)) {
45
+            throw new ElementNotFoundException(x);
46
+        }
47
+    }
48
+
49
+    @Override
50
+    public E findMin() throws EmptyPriorityQueueException {
51
+        if (isEmpty()) {
52
+            throw new EmptyPriorityQueueException();
53
+        }
54
+        return sortedSet.first();
55
+    }
56
+
57
+    @Override
58
+    public E deleteMin() throws EmptyPriorityQueueException {
59
+        E min = findMin();
60
+        remove(min);
61
+        return min;
62
+    }
63
+
64
+}

+ 32
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/ElementNotFoundException.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+public class ElementNotFoundException extends RuntimeException {
4
+
5
+    /**
6
+     * 
7
+     */
8
+    private static final long serialVersionUID = 1L;
9
+
10
+    // Element not found
11
+    private final Object element;
12
+
13
+    /**
14
+     * @param element Element that was not found.
15
+     */
16
+    public ElementNotFoundException(Object element) {
17
+        this.element = element;
18
+    }
19
+
20
+    /**
21
+     * @return The element that was not found.
22
+     */
23
+    public Object getElement() {
24
+        return this.element;
25
+    }
26
+
27
+    @Override
28
+    public String toString() {
29
+        return "element not found: " + element;
30
+    }
31
+
32
+}

+ 16
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/EmptyPriorityQueueException.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+public class EmptyPriorityQueueException extends RuntimeException {
4
+
5
+    /**
6
+     * 
7
+     */
8
+    private static final long serialVersionUID = 1L;
9
+
10
+    /**
11
+     * 
12
+     */
13
+    public EmptyPriorityQueueException() {
14
+    }
15
+
16
+}

+ 81
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/utils/PriorityQueue.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+/**
4
+ * Interface representing a basic priority queue.
5
+ * 
6
+ * Implementation should enforce the required complexity of each method.
7
+ * 
8
+ */
9
+public interface PriorityQueue<E extends Comparable<E>> {
10
+
11
+    /**
12
+     * Check if the priority queue is empty.
13
+     * 
14
+     * <p>
15
+     * <b>Complexity:</b> <i>O(1)</i>
16
+     * </p>
17
+     * 
18
+     * @return true if the queue is empty, false otherwise.
19
+     */
20
+    public boolean isEmpty();
21
+
22
+    /**
23
+     * Get the number of elements in this queue.
24
+     * 
25
+     * <p>
26
+     * <b>Complexity:</b> <i>O(1)</i>
27
+     * </p>
28
+     * 
29
+     * @return Current size (number of elements) of this queue.
30
+     */
31
+    public int size();
32
+
33
+    /**
34
+     * Insert the given element into the queue.
35
+     * 
36
+     * <p>
37
+     * <b>Complexity:</b> <i>O(log n)</i>
38
+     * </p>
39
+     * 
40
+     * @param x Item to insert.
41
+     */
42
+    public void insert(E x);
43
+
44
+    /**
45
+     * Remove the given element from the priority queue.
46
+     * 
47
+     * <p>
48
+     * <b>Complexity:</b> <i>O(log n)</i>
49
+     * </p>
50
+     * 
51
+     * @param x Item to remove.
52
+     */
53
+    public void remove(E x) throws ElementNotFoundException;
54
+
55
+    /**
56
+     * Retrieve (but not remove) the smallest item in the queue.
57
+     * 
58
+     * <p>
59
+     * <b>Complexity:</b> <i>O(1)</i>
60
+     * </p>
61
+     * 
62
+     * @return The smallest item in the queue.
63
+     * 
64
+     * @throws EmptyPriorityQueueException if this queue is empty.
65
+     */
66
+    public E findMin() throws EmptyPriorityQueueException;
67
+
68
+    /**
69
+     * Remove and return the smallest item from the priority queue.
70
+     * 
71
+     * <p>
72
+     * <b>Complexity:</b> <i>O(log n)</i>
73
+     * </p>
74
+     * 
75
+     * @return The smallest item in the queue.
76
+     * 
77
+     * @throws EmptyPriorityQueueException if this queue is empty.
78
+     */
79
+    public E deleteMin() throws EmptyPriorityQueueException;
80
+
81
+}

+ 30
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentObserver.java View File

1
+package org.insa.graphs.algorithm.weakconnectivity;
2
+
3
+import java.util.ArrayList;
4
+
5
+import org.insa.graphs.model.Node;
6
+
7
+public interface WeaklyConnectedComponentObserver {
8
+
9
+	/**
10
+	 * Notify that the algorithm is entering a new component.
11
+	 * 
12
+	 * @param curNode Starting node for the component.
13
+	 */
14
+	public void notifyStartComponent(Node curNode);
15
+	
16
+	/**
17
+	 * Notify that a new node has been found for the current component.
18
+	 * 
19
+	 * @param node New node found for the current component.
20
+	 */
21
+	public void notifyNewNodeInComponent(Node node);
22
+	
23
+	/**
24
+	 * Notify that the algorithm has computed a new component.
25
+	 * 
26
+	 * @param nodes List of nodes in the component.
27
+	 */
28
+	public void notifyEndComponent(ArrayList<Node> nodes);
29
+
30
+}

+ 36
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentTextObserver.java View File

1
+package org.insa.graphs.algorithm.weakconnectivity;
2
+
3
+import java.io.PrintStream;
4
+import java.util.ArrayList;
5
+
6
+import org.insa.graphs.model.Node;
7
+
8
+public class WeaklyConnectedComponentTextObserver implements WeaklyConnectedComponentObserver {
9
+	
10
+	// Number of the current component.
11
+	private int numComponent = 1;
12
+	
13
+	// Output stream
14
+	PrintStream stream;
15
+
16
+	public WeaklyConnectedComponentTextObserver(PrintStream stream) {
17
+		this.stream = stream;
18
+	}
19
+
20
+	@Override
21
+	public void notifyStartComponent(Node curNode) {
22
+		stream.print("Entering component #" + numComponent + " from node #" + curNode.getId() + "... ");
23
+	}
24
+
25
+	@Override
26
+	public void notifyNewNodeInComponent(Node node) {
27
+	}
28
+
29
+	@Override
30
+	public void notifyEndComponent(ArrayList<Node> nodes) {
31
+		stream.println(nodes.size() + " nodes found.");
32
+		stream.flush();
33
+		numComponent += 1;
34
+	}
35
+
36
+}

+ 159
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsAlgorithm.java View File

1
+package org.insa.graphs.algorithm.weakconnectivity;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Arrays;
5
+import java.util.HashSet;
6
+import java.util.LinkedList;
7
+import java.util.Queue;
8
+
9
+import org.insa.graphs.algorithm.AbstractAlgorithm;
10
+import org.insa.graphs.algorithm.AbstractSolution.Status;
11
+import org.insa.graphs.model.Arc;
12
+import org.insa.graphs.model.Graph;
13
+import org.insa.graphs.model.Node;
14
+
15
+public class WeaklyConnectedComponentsAlgorithm
16
+        extends AbstractAlgorithm<WeaklyConnectedComponentObserver> {
17
+
18
+    /**
19
+     * @param data Input data for this algorithm.
20
+     */
21
+    public WeaklyConnectedComponentsAlgorithm(WeaklyConnectedComponentsData data) {
22
+        super(data);
23
+    }
24
+
25
+    @Override
26
+    public WeaklyConnectedComponentsSolution run() {
27
+        return (WeaklyConnectedComponentsSolution) super.run();
28
+    }
29
+
30
+    @Override
31
+    public WeaklyConnectedComponentsData getInputData() {
32
+        return (WeaklyConnectedComponentsData) super.getInputData();
33
+    }
34
+
35
+    /**
36
+     * Notify all observers that the algorithm is entering a new component.
37
+     * 
38
+     * @param curNode Starting node for the component.
39
+     */
40
+    protected void notifyStartComponent(Node curNode) {
41
+        for (WeaklyConnectedComponentObserver obs: getObservers()) {
42
+            obs.notifyStartComponent(curNode);
43
+        }
44
+    }
45
+
46
+    /**
47
+     * Notify all observers that a new node has been found for the current
48
+     * component.
49
+     * 
50
+     * @param node New node found for the current component.
51
+     */
52
+    protected void notifyNewNodeInComponent(Node node) {
53
+        for (WeaklyConnectedComponentObserver obs: getObservers()) {
54
+            obs.notifyNewNodeInComponent(node);
55
+        }
56
+    }
57
+
58
+    /**
59
+     * Notify all observers that the algorithm has computed a new component.
60
+     * 
61
+     * @param nodes List of nodes in the component.
62
+     */
63
+    protected void notifyEndComponent(ArrayList<Node> nodes) {
64
+        for (WeaklyConnectedComponentObserver obs: getObservers()) {
65
+            obs.notifyEndComponent(nodes);
66
+        }
67
+    }
68
+
69
+    /**
70
+     * @return An adjacency list for the undirected graph equivalent to the stored
71
+     *         graph.
72
+     */
73
+    protected ArrayList<HashSet<Integer>> createUndirectedGraph() {
74
+        int nNodes = getInputData().getGraph().size();
75
+        ArrayList<HashSet<Integer>> res = new ArrayList<HashSet<Integer>>(nNodes);
76
+        for (int i = 0; i < nNodes; ++i) {
77
+            res.add(new HashSet<Integer>());
78
+        }
79
+
80
+        for (Node node: getInputData().getGraph().getNodes()) {
81
+            for (Arc arc: node.getSuccessors()) {
82
+                res.get(node.getId()).add(arc.getDestination().getId());
83
+                if (arc.getRoadInformation().isOneWay()) {
84
+                    res.get(arc.getDestination().getId()).add(node.getId());
85
+                }
86
+            }
87
+        }
88
+
89
+        return res;
90
+    }
91
+
92
+    /**
93
+     * Apply a breadth first search algorithm on the given undirected graph
94
+     * (adjacency list), starting at node cur, and marking nodes in marked.
95
+     * 
96
+     * @param marked
97
+     * @param cur
98
+     * 
99
+     * @return
100
+     */
101
+    protected ArrayList<Node> bfs(ArrayList<HashSet<Integer>> ugraph, boolean[] marked, int cur) {
102
+        Graph graph = getInputData().getGraph();
103
+        ArrayList<Node> component = new ArrayList<Node>();
104
+
105
+        // Using a queue because we are doing a BFS
106
+        Queue<Integer> queue = new LinkedList<Integer>();
107
+
108
+        // Notify observers about the current component.
109
+        notifyStartComponent(graph.get(cur));
110
+
111
+        // Add original node and loop until the queue is empty.
112
+        queue.add(cur);
113
+        marked[cur] = true;
114
+        while (!queue.isEmpty()) {
115
+            Node node = graph.get(queue.remove());
116
+            component.add(node);
117
+
118
+            // Notify observers
119
+            notifyNewNodeInComponent(node);
120
+
121
+            for (Integer destId: ugraph.get(node.getId())) {
122
+                Node dest = graph.get(destId);
123
+                if (!marked[dest.getId()]) {
124
+                    queue.add(destId);
125
+                    marked[destId] = true;
126
+                }
127
+            }
128
+        }
129
+
130
+        notifyEndComponent(component);
131
+
132
+        return component;
133
+    }
134
+
135
+    @Override
136
+    protected WeaklyConnectedComponentsSolution doRun() {
137
+
138
+        Graph graph = getInputData().getGraph();
139
+        ArrayList<HashSet<Integer>> ugraph = createUndirectedGraph();
140
+        boolean[] marked = new boolean[graph.size()];
141
+        Arrays.fill(marked, false);
142
+
143
+        ArrayList<ArrayList<Node>> components = new ArrayList<ArrayList<Node>>();
144
+
145
+        // perform algorithm
146
+        int cur = 0;
147
+        while (cur < marked.length) {
148
+            // Apply BFS
149
+            components.add(this.bfs(ugraph, marked, cur));
150
+
151
+            // Find next non-marked
152
+            for (; cur < marked.length && marked[cur]; ++cur)
153
+                ;
154
+        }
155
+
156
+        return new WeaklyConnectedComponentsSolution(getInputData(), Status.OPTIMAL, components);
157
+    }
158
+
159
+}

+ 20
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsData.java View File

1
+package org.insa.graphs.algorithm.weakconnectivity;
2
+
3
+import org.insa.graphs.algorithm.AbstractInputData;
4
+import org.insa.graphs.model.Graph;
5
+
6
+public class WeaklyConnectedComponentsData extends AbstractInputData {
7
+
8
+    /**
9
+     * @param graph Graph for which components should be retrieved.
10
+     */
11
+    public WeaklyConnectedComponentsData(Graph graph) {
12
+        super(graph, null);
13
+    }
14
+
15
+    @Override
16
+    public String toString() {
17
+        return "Weakly-connected components from #0.";
18
+    }
19
+
20
+}

+ 57
- 0
be-graphes-algos/src/main/java/org/insa/graphs/algorithm/weakconnectivity/WeaklyConnectedComponentsSolution.java View File

1
+package org.insa.graphs.algorithm.weakconnectivity;
2
+
3
+import java.util.ArrayList;
4
+
5
+import org.insa.graphs.algorithm.AbstractSolution;
6
+import org.insa.graphs.model.Node;
7
+
8
+public class WeaklyConnectedComponentsSolution extends AbstractSolution {
9
+
10
+    // Components
11
+    private ArrayList<ArrayList<Node>> components;
12
+
13
+    protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data) {
14
+        super(data);
15
+    }
16
+
17
+    protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data, Status status,
18
+            ArrayList<ArrayList<Node>> components) {
19
+        super(data, status);
20
+        this.components = components;
21
+    }
22
+
23
+    @Override
24
+    public WeaklyConnectedComponentsData getInputData() {
25
+        return (WeaklyConnectedComponentsData) super.getInputData();
26
+    }
27
+
28
+    /**
29
+     * @return Components of the solution, if any.
30
+     */
31
+    public ArrayList<ArrayList<Node>> getComponents() {
32
+        return components;
33
+    }
34
+
35
+    /*
36
+     * (non-Javadoc)
37
+     * 
38
+     * @see java.lang.Object#toString()
39
+     */
40
+    @Override
41
+    public String toString() {
42
+        int nIsolated = 0;
43
+        int nGt10 = 0;
44
+        for (ArrayList<Node> component: components) {
45
+            if (component.size() == 1) {
46
+                nIsolated += 1;
47
+            }
48
+            else if (component.size() > 10) {
49
+                nGt10 += 1;
50
+            }
51
+        }
52
+        return "Found " + components.size() + " components (" + nGt10 + " with more than 10 nodes, "
53
+                + nIsolated + " isolated nodes) in " + getSolvingTime().getSeconds() + " seconds.";
54
+
55
+    }
56
+
57
+}

+ 15
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/AStarTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+import java.io.IOException;
3
+
4
+import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
5
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
6
+import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
7
+
8
+public class AStarTest extends PccAlgorithmTest {
9
+    public AStarTest() throws IOException {}
10
+
11
+    @Override
12
+    protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
13
+        return new AStarAlgorithm(data);
14
+    }
15
+}

+ 17
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BellmanFordTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+import java.io.IOException;
3
+
4
+import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
5
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
6
+import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
7
+
8
+// Note : Faire l'ensemble des tests avec Bellman-Ford est long, l'algorithme étant peu efficace.
9
+
10
+public class BellmanFordTest extends PccAlgorithmTest {
11
+    public BellmanFordTest() throws IOException {}
12
+
13
+    @Override
14
+    protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
15
+        return new BellmanFordAlgorithm(data);
16
+    }
17
+}

+ 15
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BinaryHeapTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+public class BinaryHeapTest extends PriorityQueueTest {
4
+
5
+    @Override
6
+    public PriorityQueue<MutableInteger> createQueue() {
7
+        return new BinaryHeap<>();
8
+    }
9
+
10
+    @Override
11
+    public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
12
+        return new BinaryHeap<>((BinaryHeap<MutableInteger>) queue);
13
+    }
14
+
15
+}

+ 15
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/BinarySearchTreeTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+public class BinarySearchTreeTest extends PriorityQueueTest {
4
+
5
+    @Override
6
+    public PriorityQueue<MutableInteger> createQueue() {
7
+        return new BinarySearchTree<>();
8
+    }
9
+
10
+    @Override
11
+    public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
12
+        return new BinarySearchTree<>((BinarySearchTree<MutableInteger>) queue);
13
+    }
14
+
15
+}

+ 16
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/DijkstraTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
3
+import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
4
+
5
+import java.io.IOException;
6
+
7
+import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
8
+
9
+public class DijkstraTest extends PccAlgorithmTest {
10
+    public DijkstraTest() throws IOException {}
11
+
12
+    @Override
13
+    protected ShortestPathAlgorithm createAlgorithm(ShortestPathData data) {
14
+        return new DijkstraAlgorithm(data);
15
+    }
16
+}

+ 196
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/PccAlgorithmTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+import static org.junit.Assert.assertTrue;
5
+
6
+import org.junit.Test;
7
+
8
+import java.io.BufferedInputStream;
9
+import java.io.DataInputStream;
10
+import java.io.FileInputStream;
11
+import java.io.IOException;
12
+import java.util.List;
13
+
14
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
15
+import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
16
+import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
17
+
18
+import org.insa.graphs.algorithm.ArcInspector;
19
+import org.insa.graphs.algorithm.ArcInspectorFactory;
20
+import org.insa.graphs.algorithm.AbstractInputData.Mode;
21
+import org.insa.graphs.algorithm.AbstractSolution.*;
22
+
23
+import org.insa.graphs.model.Graph;
24
+import org.insa.graphs.model.Arc;
25
+import org.insa.graphs.model.Node;
26
+import org.insa.graphs.model.Path;
27
+
28
+import org.insa.graphs.model.io.BinaryGraphReader;
29
+import org.insa.graphs.model.io.BinaryPathReader;
30
+import org.insa.graphs.model.io.GraphReader;
31
+import org.insa.graphs.model.io.PathReader;
32
+
33
+
34
+public abstract class PccAlgorithmTest {
35
+
36
+    // Oracle (généré à partir du Bellman Ford)
37
+    String hauteGaronneFileName = "./Maps/haute-garonne.mapgr";
38
+    String lfboInsaPathName = "./Paths/LFBO_INSA_correct.path";
39
+    Graph hauteGaronne;
40
+    Path LfboInsa;
41
+
42
+    // Outils
43
+    PathReader pathReader;
44
+    ArcInspector inspector;
45
+
46
+    // Solution trouvée par l'algorithme donné pour le chemin LFBO - INSA
47
+    ShortestPathSolution solutionTrouveeLfboInsa;
48
+
49
+    class Scenario {
50
+        Graph graph;
51
+        Mode nc;
52
+        Node origine;
53
+        Node destination;
54
+
55
+        public Scenario(Graph graph, Mode nc, Node origine, Node destination) {
56
+            this.graph = graph;
57
+            this.origine = origine;
58
+            this.destination = destination;
59
+            this.nc = nc;
60
+        }
61
+    }
62
+
63
+
64
+    protected abstract ShortestPathAlgorithm createAlgorithm(ShortestPathData data);
65
+
66
+    /**
67
+     * Fonction utilitaire, test un scenario donné
68
+     * @param scenario
69
+     * @return
70
+     */
71
+    public ShortestPathSolution testScenario(Scenario scenario) {
72
+        // Préparation des données
73
+        ShortestPathData data = new ShortestPathData(scenario.graph, scenario.origine, scenario.destination, inspector);
74
+        // Préparation de l'algorithme
75
+        ShortestPathAlgorithm algo = createAlgorithm(data);
76
+        // Lancement de l'algorithme
77
+        return algo.run();
78
+    }
79
+
80
+    /**
81
+     * Fonction utilitaire, fait calculer le chemin LFBO - INSA à l'algorithme donné
82
+     * @return
83
+     */
84
+    public ShortestPathSolution doLFBO_INSA() {
85
+        // Récupération du chemin
86
+        Path correctPath = LfboInsa;
87
+        
88
+        // Création du scénario
89
+        Scenario ScnLfboInsa = new Scenario(hauteGaronne, Mode.LENGTH, correctPath.getOrigin(), correctPath.getDestination());
90
+
91
+        return testScenario(ScnLfboInsa);
92
+    }
93
+
94
+    /**
95
+     * Constructeur, lit les fichiers liés à l'oracle et calcul LFBO - INSA
96
+     * @throws IOException
97
+     */
98
+    public PccAlgorithmTest() throws IOException {
99
+        inspector = ArcInspectorFactory.getAllFilters().get(0);
100
+
101
+        GraphReader reader = new BinaryGraphReader(new DataInputStream(new BufferedInputStream(new FileInputStream(hauteGaronneFileName))));
102
+        hauteGaronne = reader.read();
103
+
104
+        pathReader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(new FileInputStream(lfboInsaPathName))));
105
+        LfboInsa = pathReader.readPath(hauteGaronne);
106
+
107
+        solutionTrouveeLfboInsa = doLFBO_INSA();
108
+    }
109
+
110
+    @Test
111
+    public void testLFBO_INSA() {
112
+        Path pathTrouve = solutionTrouveeLfboInsa.getPath();
113
+
114
+        // Test de la solution trouvée
115
+        assertTrue(solutionTrouveeLfboInsa.isFeasible());
116
+        assertEquals(solutionTrouveeLfboInsa.getStatus(), Status.OPTIMAL);
117
+
118
+        assertEquals(pathTrouve.getOrigin(), LfboInsa.getOrigin());
119
+        assertEquals(pathTrouve.getDestination(), LfboInsa.getDestination());
120
+    
121
+        assertEquals(pathTrouve.getLength(), LfboInsa.getLength(), 0.01);
122
+    }
123
+
124
+    @Test
125
+    public void testOneNodePath()  {
126
+        Node onlyOne = LfboInsa.getOrigin();
127
+
128
+        // Création du scénario
129
+        Scenario ScnOneNode = new Scenario(hauteGaronne, Mode.LENGTH, onlyOne, onlyOne);
130
+
131
+        // Test du scénario
132
+        ShortestPathSolution solutionTrouvee = testScenario(ScnOneNode);
133
+
134
+        // Test de la solution trouvée
135
+        assertTrue(solutionTrouvee.isFeasible());
136
+        assertEquals(solutionTrouvee.getStatus(), Status.OPTIMAL);
137
+    
138
+        assertEquals(solutionTrouvee.getPath().getLength(), 0.0, 0.01);
139
+    }
140
+
141
+    /**
142
+     * Il n'y a pas beaucoup de solution pour vérifier l'optimalité d'un chemin sans oracle.
143
+     * On peut cependant vérifier que les sous-chemin sont également optimaux.
144
+     * Note : La meilleure solution reste de démontrer l'optimalité du A* et de prendre 
145
+     * une heurestique admissible.
146
+     */
147
+    @Test
148
+    public void testSousChemin() {
149
+        Path pathTrouve = solutionTrouveeLfboInsa.getPath();
150
+        
151
+        Node n = pathTrouve.getOrigin();
152
+        Node m = pathTrouve.getArcs().get(30).getDestination();
153
+        Scenario ScnSousChemin = new Scenario(pathTrouve.getGraph(), Mode.LENGTH, n, m);
154
+        ShortestPathSolution sol = testScenario(ScnSousChemin);
155
+
156
+        List<Arc> arcsCheminOriginal = pathTrouve.getArcs();
157
+        List<Arc> arcsSousChemin = sol.getPath().getArcs();
158
+        for (int i = 0; i < 30; ++i) {
159
+            assertTrue(arcsCheminOriginal.get(i) == arcsSousChemin.get(i));
160
+        }
161
+    }
162
+
163
+    /**
164
+     * Ce test permet de vérifier que la distance à vol d'oiseau est toujours plus faible
165
+     * que la longueur du chemin retourné par l'algorithme
166
+     */
167
+    @Test
168
+    public void testVolDOiseau() {
169
+        Path pathTrouve = solutionTrouveeLfboInsa.getPath();
170
+
171
+        double dx = LfboInsa.getOrigin().getPoint().getLongitude() - LfboInsa.getDestination().getPoint().getLongitude();
172
+        double dy = LfboInsa.getOrigin().getPoint().getLatitude() - LfboInsa.getDestination().getPoint().getLatitude();
173
+
174
+
175
+        assertTrue(pathTrouve.getLength() >= Math.sqrt(dx * dx + dy * dy));
176
+    }
177
+
178
+    @Test
179
+    public void testInegaliteTriangulaire() {
180
+        // Récupération d'un point légèrement dé-axé
181
+        Node pointDePassage = LfboInsa.getOrigin().getSuccessors().get(0).getDestination().getSuccessors().get(0).getDestination().getSuccessors().get(0).getDestination();
182
+        
183
+        // Récupération de la solution directe
184
+        Path pathTrouve = solutionTrouveeLfboInsa.getPath();
185
+
186
+        Scenario Scn1 = new Scenario(LfboInsa.getGraph(), Mode.LENGTH, pathTrouve.getOrigin(), pointDePassage);
187
+        Scenario Scn2 = new Scenario(LfboInsa.getGraph(), Mode.LENGTH, pointDePassage, pathTrouve.getDestination());
188
+        ShortestPathSolution sol1 = testScenario(Scn1);
189
+        ShortestPathSolution sol2 = testScenario(Scn2);
190
+
191
+        // Vérification de l'inégalité triangulaire
192
+        assertTrue(sol1.getPath().getLength() + sol2.getPath().getLength() >= pathTrouve.getLength());
193
+    }
194
+
195
+
196
+}

+ 320
- 0
be-graphes-algos/src/test/java/org/insa/graphs/algorithm/utils/PriorityQueueTest.java View File

1
+package org.insa.graphs.algorithm.utils;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+import static org.junit.Assert.assertTrue;
5
+import static org.junit.Assert.fail;
6
+
7
+import java.util.ArrayList;
8
+import java.util.Arrays;
9
+import java.util.Collection;
10
+import java.util.Collections;
11
+import java.util.List;
12
+import java.util.stream.IntStream;
13
+
14
+import org.junit.Assume;
15
+import org.junit.Before;
16
+import org.junit.Test;
17
+import org.junit.runner.RunWith;
18
+import org.junit.runners.Parameterized;
19
+import org.junit.runners.Parameterized.Parameter;
20
+import org.junit.runners.Parameterized.Parameters;
21
+
22
+@RunWith(Parameterized.class)
23
+public abstract class PriorityQueueTest {
24
+
25
+    /**
26
+     * Needs to be implemented by child class to actually provide priority queue
27
+     * implementation.
28
+     * 
29
+     * @return A new instance of a PriorityQueue implementation.
30
+     */
31
+    public abstract PriorityQueue<MutableInteger> createQueue();
32
+
33
+    /**
34
+     * Needs to be implemented by child class to actually provide priority queue
35
+     * implementation.
36
+     * 
37
+     * @param queue Queue to copy.
38
+     * 
39
+     * @return Copy of the given queue.
40
+     */
41
+    public abstract PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue);
42
+
43
+    protected static class MutableInteger implements Comparable<MutableInteger> {
44
+
45
+        // Actual value
46
+        private int value;
47
+
48
+        public MutableInteger(int value) {
49
+            this.value = value;
50
+        }
51
+
52
+        /**
53
+         * @return The integer value stored inside this MutableInteger.
54
+         */
55
+        public int get() {
56
+            return this.value;
57
+        }
58
+
59
+        /**
60
+         * Update the integer value stored inside this MutableInteger.
61
+         * 
62
+         * @param value New value to set.
63
+         */
64
+        public void set(int value) {
65
+            this.value = value;
66
+        }
67
+
68
+        @Override
69
+        public int compareTo(MutableInteger other) {
70
+            return Integer.compare(this.value, other.value);
71
+        }
72
+
73
+        @Override
74
+        public String toString() {
75
+            return Integer.toString(get());
76
+        }
77
+
78
+    };
79
+
80
+    protected static class TestParameters<E extends Comparable<E>> {
81
+
82
+        // Data to insert
83
+        public final E[] data;
84
+        public final int[] deleteOrder;
85
+
86
+        public TestParameters(E[] data, int[] deleteOrder) {
87
+            this.data = data;
88
+            this.deleteOrder = deleteOrder;
89
+        }
90
+
91
+    };
92
+
93
+    /**
94
+     * Set of parameters.
95
+     * 
96
+     */
97
+    @Parameters
98
+    public static Collection<Object> data() {
99
+        Collection<Object> objects = new ArrayList<>();
100
+
101
+        // Empty queue
102
+        objects.add(new TestParameters<>(new MutableInteger[0], new int[0]));
103
+
104
+        // Queue with 50 elements from 0 to 49, inserted in order and deleted in order.
105
+        objects.add(new TestParameters<>(
106
+                IntStream.range(0, 50).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
107
+                IntStream.range(0, 50).toArray()));
108
+
109
+        // Queue with 20 elements from 0 to 19, inserted in order, deleted in the given
110
+        // order.
111
+        objects.add(new TestParameters<>(
112
+                IntStream.range(0, 20).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
113
+                new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, 6, 1,
114
+                        11 }));
115
+
116
+        // Queue with 7 elements.
117
+        objects.add(
118
+                new TestParameters<>(
119
+                        Arrays.stream(new int[] { 8, 1, 6, 3, 4, 5, 9 })
120
+                                .mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
121
+                        new int[] { 6, 5, 0, 1, 4, 2, 3 }));
122
+
123
+        // Queue with 7 elements.
124
+        objects.add(
125
+                new TestParameters<>(
126
+                        Arrays.stream(new int[] { 1, 7, 4, 8, 9, 6, 5 })
127
+                                .mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
128
+                        new int[] { 2, 0, 1, 3, 4, 5, 6 }));
129
+
130
+        // Queue with 13 elements.
131
+        objects.add(new TestParameters<>(
132
+                Arrays.stream(new int[] { 1, 7, 2, 8, 9, 3, 4, 10, 11, 12, 13, 5, 6 })
133
+                        .mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
134
+                new int[] { 3, 4, 0, 2, 5, 6, 1, 7, 8, 9, 10, 11, 12 }));
135
+
136
+        return objects;
137
+    }
138
+
139
+    @Parameter
140
+    public TestParameters<MutableInteger> parameters;
141
+
142
+    // Actual queue.
143
+    private PriorityQueue<MutableInteger> queue;
144
+
145
+    @Before
146
+    public void init() {
147
+        // Create the range queue
148
+        this.queue = createQueue();
149
+
150
+        for (MutableInteger v: parameters.data) {
151
+            this.queue.insert(v);
152
+        }
153
+    }
154
+
155
+    @Test
156
+    public void testIsEmpty() {
157
+        assertEquals(parameters.data.length == 0, this.queue.isEmpty());
158
+    }
159
+
160
+    @Test
161
+    public void testSize() {
162
+        assertEquals(parameters.data.length, this.queue.size());
163
+    }
164
+
165
+    @Test
166
+    public void testInsert() {
167
+        PriorityQueue<MutableInteger> queue = createQueue();
168
+        int size = 0;
169
+        for (MutableInteger x: parameters.data) {
170
+            queue.insert(x);
171
+            assertEquals(++size, queue.size());
172
+        }
173
+        assertEquals(parameters.data.length, queue.size());
174
+        MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
175
+        Arrays.sort(range);
176
+
177
+        for (MutableInteger mi: range) {
178
+            assertEquals(mi.get(), queue.deleteMin().value);
179
+            assertEquals(--size, queue.size());
180
+        }
181
+    }
182
+
183
+    @Test(expected = EmptyPriorityQueueException.class)
184
+    public void testEmptyFindMin() {
185
+        Assume.assumeTrue(queue.isEmpty());
186
+        queue.findMin();
187
+    }
188
+
189
+    @Test
190
+    public void testFindMin() {
191
+        Assume.assumeFalse(queue.isEmpty());
192
+        assertEquals(Collections.min(Arrays.asList(parameters.data)).get(), queue.findMin().get());
193
+    }
194
+
195
+    @Test(expected = EmptyPriorityQueueException.class)
196
+    public void testEmptyDeleteMin() {
197
+        Assume.assumeTrue(queue.isEmpty());
198
+        queue.deleteMin();
199
+    }
200
+
201
+    @Test
202
+    public void testDeleteMin() {
203
+        int size = parameters.data.length;
204
+        assertEquals(size, queue.size());
205
+        MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
206
+        Arrays.sort(range);
207
+        for (MutableInteger x: range) {
208
+            assertEquals(x, queue.deleteMin());
209
+            size -= 1;
210
+            assertEquals(size, queue.size());
211
+        }
212
+        assertEquals(0, queue.size());
213
+        assertTrue(queue.isEmpty());
214
+    }
215
+
216
+    @Test(expected = ElementNotFoundException.class)
217
+    public void testRemoveEmpty() {
218
+        Assume.assumeTrue(queue.isEmpty());
219
+        queue.remove(new MutableInteger(0));
220
+    }
221
+
222
+    @Test
223
+    public void testRemoveNotFound() {
224
+        Assume.assumeFalse(queue.isEmpty());
225
+        List<MutableInteger> data = Arrays.asList(parameters.data);
226
+        MutableInteger min = new MutableInteger(Collections.min(data).get() - 1),
227
+                max = new MutableInteger(Collections.max(data).get() + 1);
228
+        try {
229
+            queue.remove(min);
230
+            fail("Expected exception " + ElementNotFoundException.class.getName());
231
+        }
232
+        catch (ElementNotFoundException e) {
233
+            assertEquals(min, e.getElement());
234
+        }
235
+        try {
236
+            queue.remove(max);
237
+            fail("Expected exception " + ElementNotFoundException.class.getName());
238
+        }
239
+        catch (ElementNotFoundException e) {
240
+            assertEquals(max, e.getElement());
241
+        }
242
+    }
243
+
244
+    @Test
245
+    public void testDeleteThenRemove() {
246
+        Assume.assumeFalse(queue.isEmpty());
247
+        while (!queue.isEmpty()) {
248
+            MutableInteger min = queue.deleteMin();
249
+            try {
250
+                queue.remove(min);
251
+                fail("Expected exception " + ElementNotFoundException.class.getName());
252
+            }
253
+            catch (ElementNotFoundException e) {
254
+                assertEquals(min, e.getElement());
255
+            }
256
+        }
257
+    }
258
+
259
+    @Test
260
+    public void testRemoveTwice() {
261
+        Assume.assumeFalse(queue.isEmpty());
262
+        for (MutableInteger data: parameters.data) {
263
+            PriorityQueue<MutableInteger> copyQueue = this.createQueue(this.queue);
264
+            copyQueue.remove(data);
265
+            try {
266
+                copyQueue.remove(data);
267
+                fail("Expected exception " + ElementNotFoundException.class.getName());
268
+            }
269
+            catch (ElementNotFoundException e) {
270
+                assertEquals(data, e.getElement());
271
+            }
272
+        }
273
+    }
274
+
275
+    @Test
276
+    public void testRemove() {
277
+        int size1 = queue.size();
278
+        for (int i = 0; i < parameters.deleteOrder.length; ++i) {
279
+            // Remove from structure
280
+            queue.remove(parameters.data[parameters.deleteOrder[i]]);
281
+
282
+            // Copy the remaining elements
283
+            PriorityQueue<MutableInteger> copyTree = createQueue(queue);
284
+
285
+            // Retrieve all remaining elements in both structures
286
+            ArrayList<MutableInteger> remains_in = new ArrayList<>(),
287
+                    remains_cp = new ArrayList<>();
288
+            for (int j = i + 1; j < parameters.deleteOrder.length; ++j) {
289
+                remains_in.add(parameters.data[parameters.deleteOrder[j]]);
290
+                remains_cp.add(copyTree.deleteMin());
291
+            }
292
+
293
+            Collections.sort(remains_in);
294
+
295
+            // Check that the copy is now empty, and that both list contains all
296
+            // elements.
297
+            assertTrue(copyTree.isEmpty());
298
+            assertEquals(remains_in, remains_cp);
299
+
300
+            // Check that the size of the original tree is correct.
301
+            assertEquals(--size1, queue.size());
302
+        }
303
+        assertTrue(queue.isEmpty());
304
+    }
305
+
306
+    @Test
307
+    public void testRemoveThenAdd() {
308
+        Assume.assumeFalse(queue.isEmpty());
309
+        int min = Collections.min(Arrays.asList(parameters.data)).get();
310
+        for (MutableInteger mi: parameters.data) {
311
+            queue.remove(mi);
312
+            assertEquals(parameters.data.length - 1, queue.size());
313
+            mi.set(--min);
314
+            queue.insert(mi);
315
+            assertEquals(parameters.data.length, queue.size());
316
+            assertEquals(min, queue.findMin().get());
317
+        }
318
+    }
319
+
320
+}

+ 87
- 0
be-graphes-gui/pom.xml View File

1
+<?xml version="1.0"?>
2
+<project
3
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+	xmlns="http://maven.apache.org/POM/4.0.0"
5
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
6
+	<modelVersion>4.0.0</modelVersion>
7
+	<parent>
8
+		<groupId>org.insa.graphs</groupId>
9
+		<artifactId>be-graphes-all</artifactId>
10
+		<version>0.0.1-SNAPSHOT</version>
11
+	</parent>
12
+
13
+	<properties>
14
+		<mapsforge.version>0.13.0</mapsforge.version>
15
+	</properties>
16
+
17
+	<artifactId>be-graphes-gui</artifactId>
18
+	<name>be-graphes-gui</name>
19
+	
20
+	<!-- Repository for Salamander (dependency of Mapsforge). -->
21
+	<repositories>
22
+		<repository>
23
+			<id>jitpack.io</id>
24
+			<url>https://jitpack.io</url>
25
+		</repository>
26
+	</repositories>
27
+
28
+	<dependencies>
29
+	
30
+		<dependency>
31
+			<groupId>org.insa.graphs</groupId>
32
+			<artifactId>be-graphes-model</artifactId>
33
+			<version>${project.version}</version>
34
+		</dependency>
35
+		
36
+		<dependency>
37
+			<groupId>org.insa.graphs</groupId>
38
+			<artifactId>be-graphes-algos</artifactId>
39
+			<version>${project.version}</version>
40
+		</dependency>
41
+
42
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-core -->
43
+		<dependency>
44
+			<groupId>net.sf.kxml</groupId>
45
+			<artifactId>kxml2</artifactId>
46
+			<version>2.3.0</version>
47
+		</dependency>
48
+
49
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
50
+		<dependency>
51
+			<groupId>org.mapsforge</groupId>
52
+			<artifactId>mapsforge-themes</artifactId>
53
+			<version>${mapsforge.version}</version>
54
+		</dependency>
55
+
56
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map -->
57
+		<dependency>
58
+			<groupId>org.mapsforge</groupId>
59
+			<artifactId>mapsforge-map</artifactId>
60
+			<version>${mapsforge.version}</version>
61
+		</dependency>
62
+
63
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-awt -->
64
+		<dependency>
65
+			<groupId>org.mapsforge</groupId>
66
+			<artifactId>mapsforge-map-awt</artifactId>
67
+			<version>${mapsforge.version}</version>
68
+		</dependency>
69
+
70
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
71
+		<dependency>
72
+			<groupId>org.mapsforge</groupId>
73
+			<artifactId>mapsforge-themes</artifactId>
74
+			<version>${mapsforge.version}</version>
75
+		</dependency>
76
+
77
+		<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
78
+		<dependency>
79
+			<groupId>org.mapsforge</groupId>
80
+			<artifactId>mapsforge-map-reader</artifactId>
81
+			<version>${mapsforge.version}</version>
82
+		</dependency>
83
+
84
+	</dependencies>
85
+
86
+
87
+</project>

+ 381
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/AlgorithmPanel.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Color;
4
+import java.awt.Component;
5
+import java.awt.Font;
6
+import java.awt.GridBagConstraints;
7
+import java.awt.GridBagLayout;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+import java.awt.event.ComponentAdapter;
11
+import java.awt.event.ComponentEvent;
12
+import java.util.ArrayList;
13
+import java.util.List;
14
+
15
+import javax.swing.Box;
16
+import javax.swing.BoxLayout;
17
+import javax.swing.JButton;
18
+import javax.swing.JCheckBox;
19
+import javax.swing.JComboBox;
20
+import javax.swing.JComponent;
21
+import javax.swing.JLabel;
22
+import javax.swing.JPanel;
23
+import javax.swing.border.EmptyBorder;
24
+
25
+import org.insa.graphs.algorithm.AbstractAlgorithm;
26
+import org.insa.graphs.algorithm.AlgorithmFactory;
27
+import org.insa.graphs.algorithm.ArcInspector;
28
+import org.insa.graphs.algorithm.ArcInspectorFactory;
29
+import org.insa.graphs.gui.NodesInputPanel.InputChangedEvent;
30
+import org.insa.graphs.gui.drawing.Drawing;
31
+import org.insa.graphs.gui.utils.ColorUtils;
32
+import org.insa.graphs.model.Node;
33
+
34
+public class AlgorithmPanel extends JPanel implements DrawingChangeListener {
35
+
36
+    /**
37
+     * 
38
+     */
39
+    private static final long serialVersionUID = 1L;
40
+
41
+    public class StartActionEvent extends ActionEvent {
42
+
43
+        /**
44
+         * 
45
+         */
46
+        private static final long serialVersionUID = 4090710269781229078L;
47
+
48
+        protected static final String START_EVENT_COMMAND = "allInputFilled";
49
+
50
+        protected static final int START_EVENT_ID = 0x1;
51
+
52
+        private final List<Node> nodes;
53
+        private final Class<? extends AbstractAlgorithm<?>> algoClass;
54
+
55
+        private final ArcInspector arcFilter;
56
+
57
+        private final boolean graphicVisualization;
58
+        private final boolean textualVisualization;
59
+
60
+        public StartActionEvent(Class<? extends AbstractAlgorithm<?>> algoClass, List<Node> nodes,
61
+                ArcInspector arcFilter, boolean graphicVisualization,
62
+                boolean textualVisualization) {
63
+            super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND);
64
+            this.nodes = nodes;
65
+            this.algoClass = algoClass;
66
+            this.graphicVisualization = graphicVisualization;
67
+            this.textualVisualization = textualVisualization;
68
+            this.arcFilter = arcFilter;
69
+        }
70
+
71
+        /**
72
+         * @return Nodes associated with this event.
73
+         */
74
+        public List<Node> getNodes() {
75
+            return this.nodes;
76
+        }
77
+
78
+        /**
79
+         * @return Arc filter associated with this event.
80
+         */
81
+        public ArcInspector getArcFilter() {
82
+            return this.arcFilter;
83
+        }
84
+
85
+        /**
86
+         * @return Algorithm class associated with this event.
87
+         */
88
+        public Class<? extends AbstractAlgorithm<?>> getAlgorithmClass() {
89
+            return this.algoClass;
90
+        }
91
+
92
+        /**
93
+         * @return true if graphic visualization is enabled.
94
+         */
95
+        public boolean isGraphicVisualizationEnabled() {
96
+            return this.graphicVisualization;
97
+        }
98
+
99
+        /**
100
+         * @return true if textual visualization is enabled.
101
+         */
102
+        public boolean isTextualVisualizationEnabled() {
103
+            return this.textualVisualization;
104
+        }
105
+
106
+    };
107
+
108
+    // Input panels for node.
109
+    protected NodesInputPanel nodesInputPanel;
110
+
111
+    // Solution
112
+    protected SolutionPanel solutionPanel;
113
+
114
+    // Component that can be enabled/disabled.
115
+    private ArrayList<JComponent> components = new ArrayList<>();
116
+
117
+    // Graphic / Text checkbox observer
118
+    private final JCheckBox graphicObserverCheckbox, textualObserverCheckbox;
119
+
120
+    private JButton startAlgoButton;
121
+
122
+    // Start listeners
123
+    List<ActionListener> startActionListeners = new ArrayList<>();
124
+
125
+    /**
126
+     * Create a new AlgorithmPanel with the given parameters.
127
+     * 
128
+     * @param parent Parent component for this panel. Only use for centering
129
+     *        dialogs.
130
+     * @param baseAlgorithm Base algorithm for this algorithm panel.
131
+     * @param title Title of the panel.
132
+     * @param nodeNames Names of the input nodes.
133
+     * @param enableArcFilterSelection <code>true</code> to enable
134
+     *        {@link ArcInspector} selection.
135
+     * 
136
+     * @see ArcInspectorFactory
137
+     */
138
+    public AlgorithmPanel(Component parent, Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
139
+            String title, String[] nodeNames, boolean enableArcFilterSelection) {
140
+        super();
141
+        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
142
+
143
+        setBorder(new EmptyBorder(15, 15, 15, 15));
144
+
145
+        // Set title.
146
+        add(createTitleLabel(title));
147
+        add(Box.createVerticalStrut(8));
148
+
149
+        // Add algorithm selection
150
+        JComboBox<String> algoSelect = createAlgoritmSelectComboBox(baseAlgorithm);
151
+        if (algoSelect.getItemCount() > 1) {
152
+            add(algoSelect);
153
+            components.add(algoSelect);
154
+        }
155
+
156
+        // Add inputs for node.
157
+        this.nodesInputPanel = createNodesInputPanel(nodeNames);
158
+        add(this.nodesInputPanel);
159
+        components.add(this.nodesInputPanel);
160
+
161
+        JComboBox<ArcInspector> arcFilterSelect = new JComboBox<>(
162
+                ArcInspectorFactory.getAllFilters().toArray(new ArcInspector[0]));
163
+        arcFilterSelect.setBackground(Color.WHITE);
164
+
165
+        // Add mode selection
166
+        JPanel modeAndObserverPanel = new JPanel();
167
+        modeAndObserverPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
168
+        modeAndObserverPanel.setLayout(new GridBagLayout());
169
+
170
+        graphicObserverCheckbox = new JCheckBox("Graphic");
171
+        graphicObserverCheckbox.setSelected(true);
172
+        textualObserverCheckbox = new JCheckBox("Textual");
173
+
174
+        GridBagConstraints c = new GridBagConstraints();
175
+
176
+        c.fill = GridBagConstraints.HORIZONTAL;
177
+
178
+        c.gridy = 2;
179
+        c.gridx = 0;
180
+        c.weightx = 0;
181
+        modeAndObserverPanel.add(new JLabel("Visualization: "), c);
182
+        c.gridx = 1;
183
+        c.weightx = 1;
184
+        modeAndObserverPanel.add(graphicObserverCheckbox, c);
185
+        c.gridx = 2;
186
+        c.weightx = 1;
187
+        modeAndObserverPanel.add(textualObserverCheckbox, c);
188
+
189
+        if (enableArcFilterSelection) {
190
+            c.gridy = 1;
191
+            c.gridx = 0;
192
+            c.weightx = 0;
193
+            modeAndObserverPanel.add(new JLabel("Mode: "), c);
194
+            c.gridx = 1;
195
+            c.gridwidth = 2;
196
+            c.weightx = 1;
197
+            modeAndObserverPanel.add(arcFilterSelect, c);
198
+        }
199
+
200
+        components.add(arcFilterSelect);
201
+        components.add(textualObserverCheckbox);
202
+
203
+        add(modeAndObserverPanel);
204
+
205
+        solutionPanel = new SolutionPanel(parent);
206
+        solutionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
207
+        solutionPanel.setVisible(false);
208
+        add(Box.createVerticalStrut(10));
209
+        add(solutionPanel);
210
+
211
+        // Bottom panel
212
+        JPanel bottomPanel = new JPanel();
213
+        bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
214
+
215
+        startAlgoButton = new JButton("Start");
216
+        startAlgoButton.setEnabled(false);
217
+        startAlgoButton.addActionListener(new ActionListener() {
218
+            @Override
219
+            public void actionPerformed(ActionEvent e) {
220
+                for (ActionListener lis: startActionListeners) {
221
+                    lis.actionPerformed(new StartActionEvent(
222
+                            AlgorithmFactory.getAlgorithmClass(baseAlgorithm,
223
+                                    (String) algoSelect.getSelectedItem()),
224
+                            nodesInputPanel.getNodeForInputs(),
225
+                            (ArcInspector) arcFilterSelect.getSelectedItem(),
226
+                            graphicObserverCheckbox.isSelected(),
227
+                            textualObserverCheckbox.isSelected()));
228
+                }
229
+            }
230
+        });
231
+
232
+        JButton hideButton = new JButton("Hide");
233
+        hideButton.addActionListener(new ActionListener() {
234
+            @Override
235
+            public void actionPerformed(ActionEvent e) {
236
+                nodesInputPanel.setEnabled(false);
237
+                setVisible(false);
238
+            }
239
+        });
240
+
241
+        bottomPanel.add(startAlgoButton);
242
+        bottomPanel.add(Box.createHorizontalGlue());
243
+        bottomPanel.add(hideButton);
244
+
245
+        components.add(hideButton);
246
+
247
+        bottomPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
248
+
249
+        add(Box.createVerticalStrut(8));
250
+        add(bottomPanel);
251
+
252
+        nodesInputPanel.addInputChangedListener(new ActionListener() {
253
+            @Override
254
+            public void actionPerformed(ActionEvent e) {
255
+                InputChangedEvent evt = (InputChangedEvent) e;
256
+                startAlgoButton.setEnabled(allNotNull(evt.getNodes()));
257
+            }
258
+        });
259
+
260
+        addComponentListener(new ComponentAdapter() {
261
+
262
+            @Override
263
+            public void componentShown(ComponentEvent e) {
264
+                setEnabled(true);
265
+                nodesInputPanel.setVisible(true);
266
+            }
267
+
268
+            @Override
269
+            public void componentHidden(ComponentEvent e) {
270
+                setEnabled(false);
271
+                nodesInputPanel.setVisible(false);
272
+            }
273
+
274
+        });
275
+
276
+        setEnabled(false);
277
+    }
278
+
279
+    /**
280
+     * Create the title JLabel for this panel.
281
+     * 
282
+     * @param title Title for the label.
283
+     * 
284
+     * @return A new JLabel containing the given title with proper font.
285
+     */
286
+    protected JLabel createTitleLabel(String title) {
287
+        JLabel titleLabel = new JLabel(title);
288
+        titleLabel.setBackground(Color.RED);
289
+        titleLabel.setHorizontalAlignment(JLabel.LEFT);
290
+        titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
291
+        Font font = titleLabel.getFont();
292
+        font = font.deriveFont(Font.BOLD, 18);
293
+        titleLabel.setFont(font);
294
+        return titleLabel;
295
+    }
296
+
297
+    /**
298
+     * Create the combo box for the algorithm selection.
299
+     * 
300
+     * @param baseAlgorithm Base algorithm for which the select box should be
301
+     *        created.
302
+     * 
303
+     * @return A new JComboBox containing algorithms for the given base algorithm.
304
+     * 
305
+     * @see AlgorithmFactory
306
+     */
307
+    protected JComboBox<String> createAlgoritmSelectComboBox(
308
+            Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
309
+        JComboBox<String> algoSelect = new JComboBox<>(
310
+                AlgorithmFactory.getAlgorithmNames(baseAlgorithm).toArray(new String[0]));
311
+        algoSelect.setBackground(Color.WHITE);
312
+        algoSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
313
+        return algoSelect;
314
+
315
+    }
316
+
317
+    /**
318
+     * Create a node input panel with the given node input names.
319
+     * 
320
+     * @param nodeNames Field names for the inputs to create.
321
+     * 
322
+     * @return A new NodesInputPanel containing inputs for the given names.
323
+     */
324
+    protected NodesInputPanel createNodesInputPanel(String[] nodeNames) {
325
+        NodesInputPanel panel = new NodesInputPanel();
326
+        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
327
+        for (int i = 0; i < nodeNames.length; ++i) {
328
+            panel.addTextField(nodeNames[i] + ": ", ColorUtils.getColor(i));
329
+        }
330
+        panel.setEnabled(false);
331
+        return panel;
332
+    }
333
+
334
+    /**
335
+     * Check if the given list of nodes does not contain any <code>null</code>
336
+     * value.
337
+     * 
338
+     * @param nodes List of {@link Node} to check.
339
+     * 
340
+     * @return <code>true</code> if the list does not contain any <code>null</code>
341
+     *         value, <code>false</code> otherwise.
342
+     */
343
+    protected boolean allNotNull(List<Node> nodes) {
344
+        boolean allNotNull = true;
345
+        for (Node node: nodes) {
346
+            allNotNull = allNotNull && node != null;
347
+        }
348
+        return allNotNull;
349
+    }
350
+
351
+    @Override
352
+    public void setEnabled(boolean enabled) {
353
+        super.setEnabled(enabled);
354
+        nodesInputPanel.setEnabled(enabled);
355
+        solutionPanel.setEnabled(enabled);
356
+        for (JComponent component: components) {
357
+            component.setEnabled(enabled);
358
+        }
359
+        graphicObserverCheckbox.setEnabled(enabled);
360
+        enabled = enabled && allNotNull(this.nodesInputPanel.getNodeForInputs());
361
+        startAlgoButton.setEnabled(enabled);
362
+    }
363
+
364
+    /**
365
+     * Add a new start action listener to this class.
366
+     * 
367
+     * @param listener Listener to add.
368
+     */
369
+    public void addStartActionListener(ActionListener listener) {
370
+        this.startActionListeners.add(listener);
371
+    }
372
+
373
+    @Override
374
+    public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
375
+    }
376
+
377
+    @Override
378
+    public void onRedrawRequest() {
379
+    }
380
+
381
+}

+ 56
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/BlockingActionFactory.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Component;
4
+import java.awt.event.ActionEvent;
5
+import java.awt.event.ActionListener;
6
+import java.util.ArrayList;
7
+
8
+import javax.swing.JOptionPane;
9
+
10
+public class BlockingActionFactory {
11
+
12
+    // List of running actions.
13
+    private ArrayList<RunningAction> actions = new ArrayList<>();
14
+
15
+    // Parent component.
16
+    private Component parentComponent;
17
+
18
+    public BlockingActionFactory(Component parentComponent) {
19
+        this.parentComponent = parentComponent;
20
+    }
21
+
22
+    public void addAction(RunningAction action) {
23
+        actions.add(action);
24
+    }
25
+
26
+    public ActionListener createBlockingAction(ActionListener listener) {
27
+        return new ActionListener() {
28
+            @Override
29
+            public void actionPerformed(ActionEvent e) {
30
+                boolean accepted = true;
31
+
32
+                // Check if actions...
33
+                for (int i = 0; i < actions.size() && accepted; ++i) {
34
+                    RunningAction action = actions.get(i);
35
+                    // If action is running, ask user...
36
+                    if (action.isRunning()) {
37
+                        if (JOptionPane.showConfirmDialog(parentComponent, "Action {"
38
+                                + action.getInformation()
39
+                                + "} is running, do you want to stop it?") == JOptionPane.OK_OPTION) {
40
+                            action.interrupt();
41
+                        }
42
+                        else {
43
+                            accepted = false;
44
+                        }
45
+                    }
46
+                }
47
+
48
+                // If action is accepted, run it...
49
+                if (accepted) {
50
+                    listener.actionPerformed(e);
51
+                }
52
+            }
53
+        };
54
+    }
55
+
56
+}

+ 24
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/DrawingChangeListener.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import org.insa.graphs.gui.drawing.Drawing;
4
+
5
+public interface DrawingChangeListener {
6
+
7
+    /**
8
+     * Event fired when a new drawing is loaded.
9
+     * 
10
+     * @param oldDrawing Old drawing, may be null if no drawing exits prior to this
11
+     *        one.
12
+     * @param newDrawing New drawing.
13
+     */
14
+    public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing);
15
+
16
+    /**
17
+     * Event fired when a redraw request is emitted - This is typically emitted
18
+     * after a onDrawingLoaded event, but not always, and request that elements are
19
+     * drawn again on the new drawing.
20
+     * 
21
+     */
22
+    public void onRedrawRequest();
23
+
24
+}

+ 14
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/GraphChangeListener.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import org.insa.graphs.model.Graph;
4
+
5
+public interface GraphChangeListener {
6
+
7
+    /**
8
+     * Event fire when a new graph has been loaded.
9
+     * 
10
+     * @param graph The new graph.
11
+     */
12
+    public void newGraphLoaded(Graph graph);
13
+
14
+}

+ 123
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/GraphReaderProgressBar.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Component;
4
+
5
+import javax.swing.Box;
6
+import javax.swing.BoxLayout;
7
+import javax.swing.JDialog;
8
+import javax.swing.JFrame;
9
+import javax.swing.JLabel;
10
+import javax.swing.JPanel;
11
+import javax.swing.JProgressBar;
12
+import javax.swing.border.EmptyBorder;
13
+
14
+import org.insa.graphs.model.Arc;
15
+import org.insa.graphs.model.Node;
16
+import org.insa.graphs.model.RoadInformation;
17
+import org.insa.graphs.model.io.GraphReaderObserver;
18
+
19
+/**
20
+ * One-time use GraphReaderObserver that display progress in three different
21
+ * JProgressBar.
22
+ * 
23
+ * @author Mikael
24
+ *
25
+ */
26
+public class GraphReaderProgressBar extends JDialog implements GraphReaderObserver {
27
+
28
+    /**
29
+     * 
30
+     */
31
+    private static final long serialVersionUID = -1;
32
+
33
+    // Index...
34
+    private static final int NODE = 0, DESC = 1, ARC = 2;
35
+
36
+    // Progress bar
37
+    private final JProgressBar[] progressBars = new JProgressBar[3];
38
+
39
+    // Current element read, and modulo.
40
+    private int[] counters = new int[]{ 0, 0, 0 };
41
+    private int[] modulos = new int[3];
42
+
43
+    public GraphReaderProgressBar(JFrame owner) {
44
+        super(owner);
45
+        this.setVisible(false);
46
+        setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
47
+
48
+        final String[] infos = { "nodes", "road informations", "arcs" };
49
+
50
+        JPanel pane = new JPanel();
51
+        pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
52
+        pane.setBorder(new EmptyBorder(15, 15, 15, 15));
53
+        pane.add(Box.createVerticalGlue());
54
+
55
+        for (int i = 0; i < 3; ++i) {
56
+            JLabel label = new JLabel("Reading " + infos[i] + "... ");
57
+            label.setAlignmentX(Component.LEFT_ALIGNMENT);
58
+            progressBars[i] = new JProgressBar();
59
+            progressBars[i].setAlignmentX(Component.LEFT_ALIGNMENT);
60
+            pane.add(label);
61
+            pane.add(progressBars[i]);
62
+        }
63
+
64
+        pane.add(Box.createVerticalGlue());
65
+        setContentPane(pane);
66
+        pack();
67
+    }
68
+
69
+    @Override
70
+    public void notifyStartReading(String mapId) {
71
+        setTitle("Reading graph " + mapId + "... ");
72
+        setVisible(true);
73
+    }
74
+
75
+    @Override
76
+    public void notifyEndReading() {
77
+        setVisible(false);
78
+        dispose();
79
+    }
80
+
81
+    protected void initProgressBar(int index, int max) {
82
+        progressBars[index].setMaximum(max);
83
+        modulos[index] = Math.max(max / 100, 1);
84
+    }
85
+
86
+    protected void incCounter(int index) {
87
+        counters[index] += 1;
88
+        if (counters[index] % modulos[index] == 0) {
89
+            progressBars[index].setValue(counters[index]);
90
+        }
91
+    }
92
+
93
+    @Override
94
+    public void notifyStartReadingNodes(int nNodes) {
95
+        initProgressBar(NODE, nNodes);
96
+    }
97
+
98
+    @Override
99
+    public void notifyNewNodeRead(Node node) {
100
+        incCounter(NODE);
101
+    }
102
+
103
+    @Override
104
+    public void notifyStartReadingDescriptors(int nDesc) {
105
+        initProgressBar(DESC, nDesc);
106
+    }
107
+
108
+    @Override
109
+    public void notifyNewDescriptorRead(RoadInformation desc) {
110
+        incCounter(DESC);
111
+    }
112
+
113
+    @Override
114
+    public void notifyStartReadingArcs(int nArcs) {
115
+        initProgressBar(ARC, nArcs);
116
+    }
117
+
118
+    @Override
119
+    public void notifyNewArcRead(Arc arc) {
120
+        incCounter(ARC);
121
+    }
122
+
123
+}

+ 862
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.BorderLayout;
4
+import java.awt.Color;
5
+import java.awt.Component;
6
+import java.awt.Dimension;
7
+import java.awt.GridBagConstraints;
8
+import java.awt.GridBagLayout;
9
+import java.awt.event.ActionEvent;
10
+import java.awt.event.ActionListener;
11
+import java.awt.event.KeyEvent;
12
+import java.awt.event.WindowAdapter;
13
+import java.awt.event.WindowEvent;
14
+import java.io.BufferedInputStream;
15
+import java.io.DataInputStream;
16
+import java.io.File;
17
+import java.io.FileInputStream;
18
+import java.io.IOException;
19
+import java.io.PrintStream;
20
+import java.util.ArrayList;
21
+import java.util.List;
22
+
23
+import javax.swing.BorderFactory;
24
+import javax.swing.Box;
25
+import javax.swing.BoxLayout;
26
+import javax.swing.JButton;
27
+import javax.swing.JFileChooser;
28
+import javax.swing.JFrame;
29
+import javax.swing.JLabel;
30
+import javax.swing.JMenu;
31
+import javax.swing.JMenuBar;
32
+import javax.swing.JMenuItem;
33
+import javax.swing.JOptionPane;
34
+import javax.swing.JPanel;
35
+import javax.swing.JScrollPane;
36
+import javax.swing.JSplitPane;
37
+import javax.swing.JTextArea;
38
+import javax.swing.KeyStroke;
39
+import javax.swing.SwingConstants;
40
+import javax.swing.SwingUtilities;
41
+import javax.swing.Timer;
42
+import javax.swing.UIManager;
43
+import javax.swing.border.CompoundBorder;
44
+import javax.swing.border.EmptyBorder;
45
+
46
+import org.insa.graphs.algorithm.AbstractSolution;
47
+import org.insa.graphs.algorithm.AlgorithmFactory;
48
+import org.insa.graphs.algorithm.carpooling.CarPoolingAlgorithm;
49
+import org.insa.graphs.algorithm.packageswitch.PackageSwitchAlgorithm;
50
+import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
51
+import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
52
+import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
53
+import org.insa.graphs.algorithm.shortestpath.ShortestPathTextObserver;
54
+import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentTextObserver;
55
+import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
56
+import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsData;
57
+import org.insa.graphs.gui.AlgorithmPanel.StartActionEvent;
58
+import org.insa.graphs.gui.drawing.BasicGraphPalette;
59
+import org.insa.graphs.gui.drawing.BlackAndWhiteGraphPalette;
60
+import org.insa.graphs.gui.drawing.Drawing;
61
+import org.insa.graphs.gui.drawing.GraphPalette;
62
+import org.insa.graphs.gui.drawing.components.BasicDrawing;
63
+import org.insa.graphs.gui.drawing.components.MapViewDrawing;
64
+import org.insa.graphs.gui.observers.ShortestPathGraphicObserver;
65
+import org.insa.graphs.gui.observers.WeaklyConnectedComponentGraphicObserver;
66
+import org.insa.graphs.gui.utils.FileUtils;
67
+import org.insa.graphs.gui.utils.FileUtils.FolderType;
68
+import org.insa.graphs.model.Graph;
69
+import org.insa.graphs.model.Path;
70
+import org.insa.graphs.model.io.BinaryGraphReader;
71
+import org.insa.graphs.model.io.BinaryPathReader;
72
+import org.insa.graphs.model.io.GraphReader;
73
+import org.insa.graphs.model.io.MapMismatchException;
74
+
75
+public class MainWindow extends JFrame {
76
+
77
+    /**
78
+     * 
79
+     */
80
+    private static final long serialVersionUID = 1L;
81
+
82
+    /**
83
+     * 
84
+     */
85
+    private static final String WINDOW_TITLE = "BE Graphes INSA";
86
+
87
+    /**
88
+     * 
89
+     */
90
+    private static final int THREAD_TIMER_DELAY = 1000; // in milliseconds
91
+
92
+    // Current graph.
93
+    protected Graph graph;
94
+
95
+    // Path to the last opened graph file.
96
+    private String graphFilePath;
97
+
98
+    // Drawing and click adapter.
99
+    protected Drawing drawing;
100
+    private final MapViewDrawing mapViewDrawing;
101
+    private final BasicDrawing basicDrawing;
102
+
103
+    private final GraphPalette basicPalette, blackAndWhitePalette;
104
+    private GraphPalette currentPalette;
105
+
106
+    // Main panel.
107
+    private final JSplitPane mainPanel;
108
+
109
+    // Algorithm panels
110
+    private final List<AlgorithmPanel> algoPanels = new ArrayList<>();
111
+    private final AlgorithmPanel wccPanel, spPanel, cpPanel, psPanel;
112
+
113
+    // Path panel
114
+    private final PathsPanel pathPanel;
115
+
116
+    // List of items that cannot be used without a graph
117
+    private final ArrayList<JMenuItem> graphLockItems = new ArrayList<JMenuItem>();
118
+
119
+    // Label containing the map ID of the current graph.
120
+    private JLabel graphInfoPanel;
121
+
122
+    // Thread information
123
+    private Timer threadTimer;
124
+    private JPanel threadPanel;
125
+
126
+    // Log stream and print stream
127
+    private StreamCapturer logStream;
128
+
129
+    private PrintStream printStream;
130
+
131
+    // Current running thread
132
+    private ThreadWrapper currentThread;
133
+
134
+    // Factory
135
+    private BlockingActionFactory baf;
136
+
137
+    // Observers
138
+    private List<DrawingChangeListener> drawingChangeListeners = new ArrayList<>();
139
+    private List<GraphChangeListener> graphChangeListeneres = new ArrayList<>();
140
+
141
+    public MainWindow() {
142
+        super(WINDOW_TITLE);
143
+
144
+        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
145
+        setLayout(new BorderLayout());
146
+
147
+        setMinimumSize(new Dimension(800, 600));
148
+
149
+        // Create drawing and action listeners...
150
+        this.basicDrawing = new BasicDrawing();
151
+        this.mapViewDrawing = new MapViewDrawing();
152
+        this.drawing = basicDrawing;
153
+
154
+        // Createa palettes
155
+        this.basicPalette = new BasicGraphPalette();
156
+        this.blackAndWhitePalette = new BlackAndWhiteGraphPalette();
157
+        this.currentPalette = this.basicPalette;
158
+
159
+        wccPanel = new AlgorithmPanel(this, WeaklyConnectedComponentsAlgorithm.class,
160
+                "Weakly-Connected Components", new String[] {}, false);
161
+        wccPanel.addStartActionListener(new ActionListener() {
162
+            @Override
163
+            public void actionPerformed(ActionEvent e) {
164
+                StartActionEvent evt = (StartActionEvent) e;
165
+                WeaklyConnectedComponentsData data = new WeaklyConnectedComponentsData(graph);
166
+
167
+                WeaklyConnectedComponentsAlgorithm wccAlgorithm = null;
168
+                try {
169
+                    wccAlgorithm = (WeaklyConnectedComponentsAlgorithm) AlgorithmFactory
170
+                            .createAlgorithm(evt.getAlgorithmClass(), data);
171
+                }
172
+                catch (Exception e1) {
173
+                    JOptionPane.showMessageDialog(MainWindow.this,
174
+                            "An error occurred while creating the specified algorithm.",
175
+                            "Internal error: Algorithm instantiation failure",
176
+                            JOptionPane.ERROR_MESSAGE);
177
+                    e1.printStackTrace();
178
+                    return;
179
+                }
180
+
181
+                wccPanel.setEnabled(false);
182
+
183
+                if (evt.isGraphicVisualizationEnabled()) {
184
+                    wccAlgorithm.addObserver(new WeaklyConnectedComponentGraphicObserver(drawing));
185
+                }
186
+                if (evt.isTextualVisualizationEnabled()) {
187
+                    wccAlgorithm.addObserver(new WeaklyConnectedComponentTextObserver(printStream));
188
+                }
189
+
190
+                // We love Java...
191
+                final WeaklyConnectedComponentsAlgorithm copyAlgorithm = wccAlgorithm;
192
+                launchThread(new Runnable() {
193
+                    @Override
194
+                    public void run() {
195
+                        AbstractSolution solution = copyAlgorithm.run();
196
+                        wccPanel.solutionPanel.addSolution(solution, false);
197
+                        wccPanel.solutionPanel.setVisible(true);
198
+                        wccPanel.setEnabled(true);
199
+                    }
200
+                });
201
+            }
202
+        });
203
+
204
+        spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class, "Shortest-Path",
205
+                new String[] { "Origin", "Destination" }, true);
206
+        spPanel.addStartActionListener(new ActionListener() {
207
+            @Override
208
+            public void actionPerformed(ActionEvent e) {
209
+                StartActionEvent evt = (StartActionEvent) e;
210
+                ShortestPathData data = new ShortestPathData(graph, evt.getNodes().get(0),
211
+                        evt.getNodes().get(1), evt.getArcFilter());
212
+
213
+                ShortestPathAlgorithm spAlgorithm = null;
214
+                try {
215
+                    spAlgorithm = (ShortestPathAlgorithm) AlgorithmFactory
216
+                            .createAlgorithm(evt.getAlgorithmClass(), data);
217
+                }
218
+                catch (Exception e1) {
219
+                    JOptionPane.showMessageDialog(MainWindow.this,
220
+                            "An error occurred while creating the specified algorithm.",
221
+                            "Internal error: Algorithm instantiation failure",
222
+                            JOptionPane.ERROR_MESSAGE);
223
+                    e1.printStackTrace();
224
+                    return;
225
+                }
226
+
227
+                spPanel.setEnabled(false);
228
+
229
+                if (evt.isGraphicVisualizationEnabled()) {
230
+                    spAlgorithm.addObserver(new ShortestPathGraphicObserver(drawing));
231
+                }
232
+                if (evt.isTextualVisualizationEnabled()) {
233
+                    spAlgorithm.addObserver(new ShortestPathTextObserver(printStream));
234
+                }
235
+
236
+                final ShortestPathAlgorithm copyAlgorithm = spAlgorithm;
237
+                launchThread(new Runnable() {
238
+                    @Override
239
+                    public void run() {
240
+                        // Run the algorithm.
241
+                        ShortestPathSolution solution = copyAlgorithm.run();
242
+                        // Add the solution to the solution panel (but do not display
243
+                        // overlay).
244
+                        spPanel.solutionPanel.addSolution(solution, false);
245
+                        // If the solution is feasible, add the path to the path panel.
246
+                        if (solution.isFeasible()) {
247
+                            pathPanel.addPath(solution.getPath());
248
+                        }
249
+                        // Show the solution panel and enable the shortest-path panel.
250
+                        spPanel.solutionPanel.setVisible(true);
251
+                        spPanel.setEnabled(true);
252
+                    }
253
+                });
254
+            }
255
+        });
256
+
257
+        cpPanel = new AlgorithmPanel(this, CarPoolingAlgorithm.class, "Car-Pooling", new String[] {
258
+                "Origin Car", "Origin Pedestrian", "Destination Car", "Destination Pedestrian" },
259
+                true);
260
+
261
+        psPanel = new AlgorithmPanel(this, PackageSwitchAlgorithm.class, "Car-Pooling",
262
+                new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true);
263
+
264
+        // add algorithm panels
265
+        algoPanels.add(wccPanel);
266
+        algoPanels.add(spPanel);
267
+        algoPanels.add(cpPanel);
268
+        algoPanels.add(psPanel);
269
+
270
+        this.pathPanel = new PathsPanel(this);
271
+
272
+        // Add click listeners to both drawing.
273
+
274
+        for (AlgorithmPanel panel: algoPanels) {
275
+            this.basicDrawing.addDrawingClickListener(panel.nodesInputPanel);
276
+            this.mapViewDrawing.addDrawingClickListener(panel.nodesInputPanel);
277
+            this.graphChangeListeneres.add(panel.nodesInputPanel);
278
+            this.graphChangeListeneres.add(panel.solutionPanel);
279
+            this.drawingChangeListeners.add(panel.nodesInputPanel);
280
+            this.drawingChangeListeners.add(panel.solutionPanel);
281
+            this.drawingChangeListeners.add(panel);
282
+        }
283
+
284
+        this.graphChangeListeneres.add(pathPanel);
285
+        this.drawingChangeListeners.add(pathPanel);
286
+
287
+        // Create action factory.
288
+        this.currentThread = new ThreadWrapper(this);
289
+        this.baf = new BlockingActionFactory(this);
290
+        this.baf.addAction(currentThread);
291
+
292
+        // Click adapter
293
+        ActionListener openMapActionListener = new ActionListener() {
294
+            @Override
295
+            public void actionPerformed(ActionEvent e) {
296
+                JFileChooser chooser = FileUtils.createFileChooser(FolderType.Map);
297
+                if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
298
+                    graphFilePath = chooser.getSelectedFile().getAbsolutePath();
299
+
300
+                    // Note: Don't use a try-resources block since loadGraph is asynchronous.
301
+                    final DataInputStream stream;
302
+                    try {
303
+                        stream = new DataInputStream(new BufferedInputStream(
304
+                                new FileInputStream(chooser.getSelectedFile())));
305
+                    }
306
+                    catch (IOException e1) {
307
+                        JOptionPane.showMessageDialog(MainWindow.this,
308
+                                "Cannot open the selected file.");
309
+                        return;
310
+                    }
311
+                    loadGraph(new BinaryGraphReader(stream));
312
+                }
313
+            }
314
+        };
315
+
316
+        setJMenuBar(createMenuBar(openMapActionListener));
317
+
318
+        // Initial panel to show "Open Map... "
319
+        JPanel openPanel = new JPanel();
320
+        openPanel.setLayout(new BoxLayout(openPanel, BoxLayout.PAGE_AXIS));
321
+        JButton openButton = new JButton("Open Map... ");
322
+        openButton.setAlignmentX(Component.CENTER_ALIGNMENT);
323
+        openButton.addActionListener(openMapActionListener);
324
+        openButton.setFocusPainted(false);
325
+        openPanel.add(Box.createVerticalGlue());
326
+        openPanel.add(openButton);
327
+        openPanel.add(Box.createVerticalGlue());
328
+
329
+        addWindowListener(new WindowAdapter() {
330
+            public void windowClosing(WindowEvent e) {
331
+                int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
332
+                        "Are you sure you want to close the application?", "Exit Confirmation",
333
+                        JOptionPane.YES_NO_OPTION);
334
+
335
+                if (confirmed == JOptionPane.YES_OPTION) {
336
+                    dispose();
337
+                    System.exit(0);
338
+                }
339
+            }
340
+        });
341
+
342
+        // Create graph area
343
+        mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
344
+
345
+        JTextArea infoPanel = new JTextArea();
346
+        infoPanel.setMinimumSize(new Dimension(200, 50));
347
+        infoPanel.setBackground(Color.WHITE);
348
+        infoPanel.setLineWrap(true);
349
+        infoPanel.setEditable(false);
350
+        this.logStream = new StreamCapturer(infoPanel);
351
+        this.printStream = new PrintStream(this.logStream);
352
+
353
+        JPanel rightComponent = new JPanel();
354
+        rightComponent.setLayout(new GridBagLayout());
355
+
356
+        GridBagConstraints c = new GridBagConstraints();
357
+        c.gridx = 0;
358
+        c.gridy = 0;
359
+        c.fill = GridBagConstraints.HORIZONTAL;
360
+        rightComponent.add(pathPanel, c);
361
+
362
+        c.gridy = 1;
363
+        for (AlgorithmPanel panel: algoPanels) {
364
+            panel.setVisible(false);
365
+            rightComponent.add(panel, c);
366
+        }
367
+
368
+        c = new GridBagConstraints();
369
+        c.gridx = 0;
370
+        c.gridy = 2;
371
+        c.weightx = 1;
372
+        c.weighty = 1;
373
+        c.fill = GridBagConstraints.BOTH;
374
+        c.gridheight = GridBagConstraints.REMAINDER;
375
+        rightComponent.add(new JScrollPane(infoPanel), c);
376
+
377
+        mainPanel.setResizeWeight(0.8);
378
+        mainPanel.setDividerSize(5);
379
+
380
+        mainPanel.setBackground(Color.WHITE);
381
+        mainPanel.setLeftComponent(openPanel);
382
+        mainPanel.setRightComponent(rightComponent);
383
+        this.add(mainPanel, BorderLayout.CENTER);
384
+
385
+        // Top Panel
386
+        this.add(createStatusBar(), BorderLayout.SOUTH);
387
+
388
+        // Notify everythin
389
+        notifyDrawingLoaded(null, drawing);
390
+    }
391
+
392
+    /**
393
+     * @param runnable
394
+     * @param canInterrupt
395
+     */
396
+    private void launchThread(Runnable runnable, boolean canInterrupt) {
397
+        if (canInterrupt) {
398
+            currentThread.setThread(new Thread(new Runnable() {
399
+                @Override
400
+                public void run() {
401
+                    threadTimer.restart();
402
+                    threadPanel.setVisible(true);
403
+                    runnable.run();
404
+                    clearCurrentThread();
405
+                }
406
+            }));
407
+        }
408
+        else {
409
+            currentThread.setThread(new Thread(runnable));
410
+        }
411
+        currentThread.startThread();
412
+    }
413
+
414
+    private void launchThread(Runnable runnable) {
415
+        launchThread(runnable, true);
416
+    }
417
+
418
+    protected void clearCurrentThread() {
419
+        threadTimer.stop();
420
+        threadPanel.setVisible(false);
421
+        currentThread.setThread(null);
422
+        if (spPanel.isVisible()) {
423
+            spPanel.setEnabled(true);
424
+        }
425
+    }
426
+
427
+    /**
428
+     * Notify all listeners that a new graph has been loaded.
429
+     */
430
+    private void notifyNewGraphLoaded() {
431
+        for (GraphChangeListener listener: graphChangeListeneres) {
432
+            listener.newGraphLoaded(graph);
433
+        }
434
+    }
435
+
436
+    /**
437
+     * Notify all listeners that a new drawing has been set up.
438
+     * 
439
+     * @param oldDrawing
440
+     * @param newDrawing
441
+     */
442
+    private void notifyDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
443
+        for (DrawingChangeListener listener: drawingChangeListeners) {
444
+            listener.onDrawingLoaded(oldDrawing, newDrawing);
445
+        }
446
+    }
447
+
448
+    /**
449
+     * Notify all listeners that a redraw request is emitted.
450
+     */
451
+    private void notifyRedrawRequest() {
452
+        for (DrawingChangeListener listener: drawingChangeListeners) {
453
+            listener.onRedrawRequest();
454
+        }
455
+    }
456
+
457
+    /**
458
+     * Draw the stored graph on the drawing.
459
+     */
460
+    private void drawGraph(Class<? extends Drawing> newClass, GraphPalette palette) {
461
+
462
+        // Save old divider location
463
+        int oldLocation = mainPanel.getDividerLocation();
464
+
465
+        // Set drawing if not set
466
+        if (!(mainPanel.getLeftComponent() instanceof Drawing)) {
467
+            mainPanel.setLeftComponent((Component) this.drawing);
468
+            mainPanel.setDividerLocation(oldLocation);
469
+            // Need to re-validate or the drawing will not have the
470
+            // correct size prior to drawing, which can cause issue.
471
+            this.revalidate();
472
+        }
473
+
474
+        boolean isNewGraph = newClass == null;
475
+        boolean isMapView = (isNewGraph && drawing == mapViewDrawing)
476
+                || (!isNewGraph && newClass.equals(MapViewDrawing.class));
477
+
478
+        // We need to draw MapView, we have to check if the file exists.
479
+        File mfile = null;
480
+        if (isMapView) {
481
+            String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map"))
482
+                    + ".mapfg";
483
+            mfile = new File(mfpath);
484
+            if (!mfile.exists()) {
485
+                if (JOptionPane.showConfirmDialog(this,
486
+                        "The associated mapsforge (.mapfg) file has not been found, do you want to specify it manually?",
487
+                        "File not found",
488
+                        JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) {
489
+                    JFileChooser chooser = new JFileChooser(mfile.getParentFile());
490
+                    if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
491
+                        mfile = chooser.getSelectedFile();
492
+                    }
493
+                    else {
494
+                        mfile = null;
495
+                    }
496
+                }
497
+                else {
498
+                    mfile = null;
499
+                }
500
+            }
501
+        }
502
+
503
+        Runnable runnable = null;
504
+
505
+        if (isMapView && mfile != null) {
506
+            final File mfileFinal = mfile;
507
+            // It is a mapview drawing and the file was found, so:
508
+            // 1. We create the drawing if necessary.
509
+            if (drawing != mapViewDrawing) {
510
+                drawing.clear();
511
+                drawing = mapViewDrawing;
512
+                mainPanel.setLeftComponent(mapViewDrawing);
513
+                mainPanel.setDividerLocation(oldLocation);
514
+                notifyDrawingLoaded(basicDrawing, mapViewDrawing);
515
+                drawing.clear();
516
+                isNewGraph = true;
517
+                mainPanel.revalidate();
518
+            }
519
+            if (isNewGraph) {
520
+                drawing.clear();
521
+                runnable = new Runnable() {
522
+                    public void run() {
523
+                        ((MapViewDrawing) drawing).drawGraph(mfileFinal);
524
+                        notifyRedrawRequest();
525
+                    }
526
+                };
527
+            }
528
+
529
+        }
530
+        else if (!isMapView || (isMapView && mfile == null && isNewGraph)) {
531
+            if (drawing == mapViewDrawing) {
532
+                mapViewDrawing.clear();
533
+                drawing = basicDrawing;
534
+                mainPanel.setLeftComponent(basicDrawing);
535
+                mainPanel.setDividerLocation(oldLocation);
536
+                notifyDrawingLoaded(mapViewDrawing, basicDrawing);
537
+                isNewGraph = true;
538
+            }
539
+            if (isNewGraph || palette != this.currentPalette) {
540
+                this.currentPalette = palette;
541
+                drawing.clear();
542
+                runnable = new Runnable() {
543
+                    public void run() {
544
+                        drawing.drawGraph(graph, palette);
545
+                        notifyRedrawRequest();
546
+                    }
547
+                };
548
+            }
549
+        }
550
+
551
+        if (runnable != null) {
552
+            launchThread(runnable, false);
553
+        }
554
+        else {
555
+            drawing.clearOverlays();
556
+            notifyRedrawRequest();
557
+        }
558
+
559
+    }
560
+
561
+    /**
562
+     * @param newClass
563
+     */
564
+    private void drawGraph(Class<? extends Drawing> newClass) {
565
+        drawGraph(newClass, new BasicGraphPalette());
566
+    }
567
+
568
+    /**
569
+     * 
570
+     */
571
+    private void drawGraph() {
572
+        drawGraph(null, this.currentPalette);
573
+    }
574
+
575
+    private void loadGraph(GraphReader reader) {
576
+        launchThread(new Runnable() {
577
+            @Override
578
+            public void run() {
579
+                GraphReaderProgressBar progressBar = new GraphReaderProgressBar(MainWindow.this);
580
+                progressBar.setLocationRelativeTo(mainPanel.getLeftComponent());
581
+                reader.addObserver(progressBar);
582
+                try {
583
+                    graph = reader.read();
584
+                    reader.close();
585
+                }
586
+                catch (Exception exception) {
587
+                    progressBar.setVisible(false);
588
+                    progressBar.dispose();
589
+                    progressBar = null;
590
+                    JOptionPane.showMessageDialog(MainWindow.this,
591
+                            "<html><p>Unable to read graph from the selected file:</p><p>"
592
+                                    + exception.getMessage() + "</p>");
593
+                    exception.printStackTrace(System.out);
594
+                    return;
595
+                }
596
+
597
+                // In case of....
598
+                progressBar.setVisible(false);
599
+                progressBar.dispose();
600
+                progressBar = null;
601
+
602
+                String info = graph.getMapId();
603
+                if (graph.getMapName() != null && !graph.getMapName().isEmpty()) {
604
+                    // The \u200e character is the left-to-right mark, we need to avoid issue with
605
+                    // name that are right-to-left (e.g. arabic names).
606
+                    info += " - " + graph.getMapName() + "\u200e";
607
+                }
608
+                info += ", " + graph.size() + " nodes, " + graph.getGraphInformation().getArcCount()
609
+                        + " arcs.";
610
+                graphInfoPanel.setText(info);
611
+
612
+                drawGraph();
613
+
614
+                notifyNewGraphLoaded();
615
+
616
+                for (JMenuItem item: graphLockItems) {
617
+                    item.setEnabled(true);
618
+                }
619
+            }
620
+        }, false);
621
+    }
622
+
623
+    /**
624
+     * Show and enable the given AlgorithmPanel (and hide all others).
625
+     * 
626
+     * @param algorithmPanel
627
+     */
628
+    private void enableAlgorithmPanel(AlgorithmPanel algorithmPanel) {
629
+        int dividerLocation = mainPanel.getDividerLocation();
630
+        for (AlgorithmPanel panel: algoPanels) {
631
+            panel.setVisible(panel == algorithmPanel);
632
+        }
633
+        mainPanel.setDividerLocation(dividerLocation);
634
+    }
635
+
636
+    private JMenuBar createMenuBar(ActionListener openMapActionListener) {
637
+
638
+        // Open Map item...
639
+        JMenuItem openMapItem = new JMenuItem("Open Map... ", KeyEvent.VK_O);
640
+        openMapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
641
+        openMapItem.addActionListener(baf.createBlockingAction(openMapActionListener));
642
+
643
+        // Open Path item...
644
+        JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P);
645
+        openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK));
646
+        openPathItem.addActionListener(baf.createBlockingAction(new ActionListener() {
647
+
648
+            @Override
649
+            public void actionPerformed(ActionEvent e) {
650
+                JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathInput);
651
+                if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
652
+                    try (BinaryPathReader reader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(
653
+                            new FileInputStream(chooser.getSelectedFile()))))){
654
+                        Path path = reader.readPath(graph);
655
+                        pathPanel.addPath(path);
656
+                    }
657
+                    catch (MapMismatchException exception) {
658
+                        JOptionPane.showMessageDialog(MainWindow.this,
659
+                                "The selected file does not contain a path for the current graph.");
660
+                    }
661
+                    catch (IOException e1) {
662
+                        JOptionPane.showMessageDialog(MainWindow.this,
663
+                                "Cannot open the selected file.");
664
+                    }
665
+                    catch (Exception exception) {
666
+                        JOptionPane.showMessageDialog(MainWindow.this,
667
+                                "Unable to read path from the selected file.");
668
+                    }
669
+                }
670
+            }
671
+        }));
672
+        graphLockItems.add(openPathItem);
673
+
674
+        // Close item
675
+        JMenuItem closeItem = new JMenuItem("Quit", KeyEvent.VK_Q);
676
+        closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
677
+        closeItem.addActionListener(new ActionListener() {
678
+            @Override
679
+            public void actionPerformed(ActionEvent e) {
680
+                MainWindow.this.dispatchEvent(
681
+                        new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING));
682
+            }
683
+        });
684
+
685
+        // Build the first menu.
686
+        JMenu fileMenu = new JMenu("File");
687
+        fileMenu.add(openMapItem);
688
+        fileMenu.add(openPathItem);
689
+        fileMenu.addSeparator();
690
+        fileMenu.add(closeItem);
691
+
692
+        // Second menu
693
+        JMenuItem drawGraphItem = new JMenuItem("Redraw", KeyEvent.VK_R);
694
+        drawGraphItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
695
+        drawGraphItem.addActionListener(baf.createBlockingAction(new ActionListener() {
696
+            @Override
697
+            public void actionPerformed(ActionEvent e) {
698
+                drawGraph(BasicDrawing.class, basicPalette);
699
+            }
700
+        }));
701
+        graphLockItems.add(drawGraphItem);
702
+        JMenuItem drawGraphBWItem = new JMenuItem("Redraw (B&W)", KeyEvent.VK_B);
703
+        drawGraphBWItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.ALT_MASK));
704
+        drawGraphBWItem.addActionListener(baf.createBlockingAction(new ActionListener() {
705
+            @Override
706
+            public void actionPerformed(ActionEvent e) {
707
+                drawGraph(BasicDrawing.class, blackAndWhitePalette);
708
+            }
709
+        }));
710
+        graphLockItems.add(drawGraphBWItem);
711
+        JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M);
712
+        drawGraphMapsforgeItem
713
+                .setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK));
714
+        drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() {
715
+            @Override
716
+            public void actionPerformed(ActionEvent e) {
717
+                drawGraph(MapViewDrawing.class);
718
+            }
719
+        }));
720
+        graphLockItems.add(drawGraphMapsforgeItem);
721
+
722
+        JMenu graphMenu = new JMenu("Graph");
723
+        graphMenu.add(drawGraphItem);
724
+        graphMenu.add(drawGraphBWItem);
725
+        graphMenu.addSeparator();
726
+        graphMenu.add(drawGraphMapsforgeItem);
727
+
728
+        // Algo menu
729
+        JMenu algoMenu = new JMenu("Algorithms");
730
+
731
+        // Weakly connected components
732
+        JMenuItem wccItem = new JMenuItem("Weakly Connected Components");
733
+        wccItem.addActionListener(baf.createBlockingAction(new ActionListener() {
734
+            @Override
735
+            public void actionPerformed(ActionEvent e) {
736
+                enableAlgorithmPanel(wccPanel);
737
+            }
738
+        }));
739
+
740
+        // Shortest path
741
+        JMenuItem spItem = new JMenuItem("Shortest-Path");
742
+        spItem.addActionListener(baf.createBlockingAction(new ActionListener() {
743
+            @Override
744
+            public void actionPerformed(ActionEvent e) {
745
+                enableAlgorithmPanel(spPanel);
746
+            }
747
+        }));
748
+
749
+        // Car pooling
750
+        JMenuItem cpItem = new JMenuItem("Car Pooling");
751
+        cpItem.addActionListener(baf.createBlockingAction(new ActionListener() {
752
+            @Override
753
+            public void actionPerformed(ActionEvent e) {
754
+                enableAlgorithmPanel(cpPanel);
755
+            }
756
+        }));
757
+
758
+        // Car pooling
759
+        JMenuItem psItem = new JMenuItem("Package Switch");
760
+        psItem.addActionListener(baf.createBlockingAction(new ActionListener() {
761
+            @Override
762
+            public void actionPerformed(ActionEvent e) {
763
+                enableAlgorithmPanel(psPanel);
764
+            }
765
+        }));
766
+
767
+        graphLockItems.add(wccItem);
768
+        graphLockItems.add(spItem);
769
+        graphLockItems.add(cpItem);
770
+        graphLockItems.add(psItem);
771
+
772
+        algoMenu.add(wccItem);
773
+        algoMenu.addSeparator();
774
+        algoMenu.add(spItem);
775
+        algoMenu.add(cpItem);
776
+        algoMenu.add(psItem);
777
+
778
+        // Create the menu bar.
779
+        JMenuBar menuBar = new JMenuBar();
780
+
781
+        menuBar.add(fileMenu);
782
+        menuBar.add(graphMenu);
783
+        menuBar.add(algoMenu);
784
+
785
+        for (JMenuItem item: graphLockItems) {
786
+            item.setEnabled(false);
787
+        }
788
+
789
+        return menuBar;
790
+    }
791
+
792
+    private JPanel createStatusBar() {
793
+        // create the status bar panel and shove it down the bottom of the frame
794
+        JPanel statusPanel = new JPanel();
795
+        statusPanel.setBorder(
796
+                new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
797
+                        new EmptyBorder(0, 15, 0, 15)));
798
+        statusPanel.setPreferredSize(new Dimension(getWidth(), 38));
799
+        statusPanel.setLayout(new BorderLayout());
800
+
801
+        graphInfoPanel = new JLabel();
802
+        graphInfoPanel.setHorizontalAlignment(SwingConstants.LEFT);
803
+        statusPanel.add(graphInfoPanel, BorderLayout.WEST);
804
+
805
+        JLabel threadInfo = new JLabel("Thread running... ");
806
+        JLabel threadTimerLabel = new JLabel("00:00:00");
807
+        JButton threadButton = new JButton("Stop");
808
+        threadButton.addActionListener(new ActionListener() {
809
+
810
+            @Override
811
+            public void actionPerformed(ActionEvent e) {
812
+                if (currentThread.isRunning()) {
813
+                    int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
814
+                            "Are you sure you want to kill the running thread?",
815
+                            "Kill Confirmation", JOptionPane.YES_NO_OPTION);
816
+                    if (confirmed == JOptionPane.YES_OPTION) {
817
+                        currentThread.interrupt();
818
+                    }
819
+                }
820
+            }
821
+        });
822
+
823
+        threadTimer = new Timer(THREAD_TIMER_DELAY, new ActionListener() {
824
+            @Override
825
+            public void actionPerformed(ActionEvent e) {
826
+                long seconds = currentThread.getDuration().getSeconds();
827
+                threadTimerLabel.setText(String.format("%02d:%02d:%02d", seconds / 3600,
828
+                        seconds / 60 % 60, seconds % 60));
829
+            }
830
+        });
831
+        threadTimer.setInitialDelay(0);
832
+
833
+        threadPanel = new JPanel();
834
+        threadPanel.add(threadInfo);
835
+        threadPanel.add(threadTimerLabel);
836
+        threadPanel.add(threadButton);
837
+        threadPanel.setVisible(false);
838
+        statusPanel.add(threadPanel, BorderLayout.EAST);
839
+
840
+        return statusPanel;
841
+    }
842
+
843
+    public static void main(final String[] args) {
844
+
845
+        // Try to set system look and feel.
846
+        try {
847
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
848
+        }
849
+        catch (Exception e) {
850
+        }
851
+
852
+        SwingUtilities.invokeLater(new Runnable() {
853
+            @Override
854
+            public void run() {
855
+                MainWindow w = new MainWindow();
856
+                w.setExtendedState(JFrame.MAXIMIZED_BOTH);
857
+                w.setVisible(true);
858
+            }
859
+        });
860
+    }
861
+
862
+}

+ 424
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/NodesInputPanel.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Color;
4
+import java.awt.Font;
5
+import java.awt.GridBagConstraints;
6
+import java.awt.GridBagLayout;
7
+import java.awt.Insets;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+import java.util.ArrayList;
11
+import java.util.Collections;
12
+import java.util.IdentityHashMap;
13
+import java.util.List;
14
+import java.util.Map;
15
+
16
+import javax.swing.JButton;
17
+import javax.swing.JComponent;
18
+import javax.swing.JLabel;
19
+import javax.swing.JPanel;
20
+import javax.swing.JTextField;
21
+import javax.swing.event.DocumentEvent;
22
+import javax.swing.event.DocumentListener;
23
+
24
+import org.insa.graphs.gui.drawing.Drawing;
25
+import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
26
+import org.insa.graphs.gui.drawing.DrawingClickListener;
27
+import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
28
+import org.insa.graphs.model.Graph;
29
+import org.insa.graphs.model.Node;
30
+import org.insa.graphs.model.Point;
31
+
32
+public class NodesInputPanel extends JPanel
33
+        implements DrawingClickListener, DrawingChangeListener, GraphChangeListener {
34
+
35
+    /**
36
+     * 
37
+     */
38
+    private static final long serialVersionUID = 1L;
39
+
40
+    private static final Color DEFAULT_MARKER_COLOR = Color.BLUE;
41
+
42
+    /**
43
+     * Utility class that can be used to find a node from coordinates in a "fast"
44
+     * way.
45
+     *
46
+     */
47
+    private static class NodeFinder {
48
+
49
+        // Graph associated with this node finder.
50
+        private Graph graph;
51
+
52
+        /**
53
+         * @param graph
54
+         */
55
+        public NodeFinder(Graph graph) {
56
+            this.graph = graph;
57
+        }
58
+
59
+        /**
60
+         * @param point
61
+         * 
62
+         * @return the closest node to the given point, or null if no node is "close
63
+         *         enough".
64
+         */
65
+        public Node findClosestNode(Point point) {
66
+            Node minNode = null;
67
+            double minDis = Double.POSITIVE_INFINITY;
68
+            for (Node node: graph.getNodes()) {
69
+                double dlon = point.getLongitude() - node.getPoint().getLongitude();
70
+                double dlat = point.getLatitude() - node.getPoint().getLatitude();
71
+                double dis = dlon * dlon + dlat * dlat; // No need to square
72
+                if (dis < minDis) {
73
+                    minNode = node;
74
+                    minDis = dis;
75
+                }
76
+            }
77
+            return minNode;
78
+        }
79
+
80
+    }
81
+
82
+    /**
83
+     * Event data send when a node input has changed.
84
+     *
85
+     */
86
+    public class InputChangedEvent extends ActionEvent {
87
+
88
+        /**
89
+         * 
90
+         */
91
+        private static final long serialVersionUID = 3440024811352247731L;
92
+
93
+        protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled";
94
+
95
+        protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1;
96
+
97
+        // List of nodes
98
+        List<Node> nodes;
99
+
100
+        public InputChangedEvent(List<Node> nodes2) {
101
+            super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND);
102
+            this.nodes = nodes2;
103
+        }
104
+
105
+        List<Node> getNodes() {
106
+            return Collections.unmodifiableList(nodes);
107
+        }
108
+
109
+    };
110
+
111
+    // Node inputs and markers.
112
+    private final ArrayList<JTextField> nodeInputs = new ArrayList<>();
113
+    private final Map<JTextField, MarkerOverlay> markerTrackers = new IdentityHashMap<JTextField, MarkerOverlay>();
114
+
115
+    // Component that can be enabled/disabled.
116
+    private ArrayList<JComponent> components = new ArrayList<>();
117
+    private int inputToFillIndex;
118
+
119
+    // ActionListener called when all inputs are filled.
120
+    private ArrayList<ActionListener> inputChangeListeners = new ArrayList<>();
121
+
122
+    // Drawing and graph
123
+    private Drawing drawing;
124
+    private Graph graph;
125
+    private NodeFinder nodeFinder;
126
+
127
+    /**
128
+     * Create a new NodesInputPanel.
129
+     * 
130
+     */
131
+    public NodesInputPanel() {
132
+        super(new GridBagLayout());
133
+        initInputToFill();
134
+    }
135
+
136
+    /**
137
+     * Add an InputChanged listener to this panel. This listener will be notified by
138
+     * a {@link InputChangedEvent} each time an input in this panel change (click,
139
+     * clear, manual input).
140
+     * 
141
+     * @param listener Listener to add.
142
+     * 
143
+     * @see InputChangedEvent
144
+     */
145
+    public void addInputChangedListener(ActionListener listener) {
146
+        inputChangeListeners.add(listener);
147
+    }
148
+
149
+    @Override
150
+    public void setVisible(boolean visible) {
151
+        super.setVisible(visible);
152
+        for (JTextField input: nodeInputs) {
153
+            MarkerOverlay marker = markerTrackers.getOrDefault(input, null);
154
+            if (marker != null) {
155
+                marker.setVisible(visible && !input.getText().trim().isEmpty());
156
+            }
157
+        }
158
+    }
159
+
160
+    @Override
161
+    public void setEnabled(boolean enabled) {
162
+        for (JComponent component: components) {
163
+            component.setEnabled(enabled);
164
+        }
165
+        super.setEnabled(enabled);
166
+        if (enabled) {
167
+            // Enable: Check if there is an input to fill, otherwize find the next one.
168
+            if (getInputToFill() == null) {
169
+                nextInputToFill();
170
+            }
171
+        }
172
+        else {
173
+            // Disable, next input to fill = -1.
174
+            this.inputToFillIndex = -1;
175
+        }
176
+    }
177
+
178
+    public void clear() {
179
+        for (JTextField field: nodeInputs) {
180
+            field.setText("");
181
+            markerTrackers.put(field, null);
182
+        }
183
+        initInputToFill();
184
+    }
185
+
186
+    public void addTextField(String label) {
187
+        addTextField(label, DEFAULT_MARKER_COLOR);
188
+    }
189
+
190
+    public void addTextField(String label, Color markerColor) {
191
+
192
+        GridBagConstraints c = new GridBagConstraints();
193
+        c.insets = new Insets(3, 3, 3, 3);
194
+
195
+        JLabel jLabel = new JLabel(label);
196
+        jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD));
197
+        JTextField textField = new JTextField();
198
+        jLabel.setLabelFor(textField);
199
+
200
+        c.gridx = 0;
201
+        c.gridy = nodeInputs.size();
202
+        c.weightx = 0;
203
+        c.gridwidth = 1;
204
+        c.fill = GridBagConstraints.HORIZONTAL;
205
+        add(jLabel, c);
206
+
207
+        c.gridx = 1;
208
+        c.gridy = nodeInputs.size();
209
+        c.weightx = 1;
210
+        c.gridwidth = 1;
211
+        c.fill = GridBagConstraints.HORIZONTAL;
212
+        add(textField, c);
213
+
214
+        JButton clearButton = new JButton("Clear");
215
+        c.gridx = 2;
216
+        c.gridy = nodeInputs.size();
217
+        c.weightx = 0;
218
+        c.gridwidth = 1;
219
+        c.fill = GridBagConstraints.HORIZONTAL;
220
+        add(clearButton, c);
221
+
222
+        JButton clickButton = new JButton("Click");
223
+        c.gridx = 3;
224
+        c.gridy = nodeInputs.size();
225
+        c.weightx = 0;
226
+        c.gridwidth = 1;
227
+        c.fill = GridBagConstraints.HORIZONTAL;
228
+        add(clickButton, c);
229
+
230
+        // Did not find something easier that this... ?
231
+        textField.getDocument().addDocumentListener(new DocumentListener() {
232
+
233
+            @Override
234
+            public void changedUpdate(DocumentEvent e) {
235
+                insertUpdate(e);
236
+            }
237
+
238
+            @Override
239
+            public void removeUpdate(DocumentEvent e) {
240
+                insertUpdate(e);
241
+            }
242
+
243
+            @Override
244
+            public void insertUpdate(DocumentEvent e) {
245
+
246
+                // Draw marker if possible
247
+                Node curnode = getNodeForInput(textField);
248
+                MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null);
249
+                if (curnode != null) {
250
+                    if (tracker == null) {
251
+                        tracker = drawing.drawMarker(curnode.getPoint(), markerColor, Color.BLACK,
252
+                                AlphaMode.TRANSPARENT);
253
+                        markerTrackers.put(textField, tracker);
254
+                    }
255
+                    else {
256
+                        tracker.moveTo(curnode.getPoint());
257
+                    }
258
+                    tracker.setVisible(true);
259
+                }
260
+                else if (tracker != null) {
261
+                    tracker.setVisible(false);
262
+                    if (getInputToFill() == null) {
263
+                        nextInputToFill();
264
+                    }
265
+                }
266
+
267
+                // Create array of nodes
268
+                List<Node> nodes = getNodeForInputs();
269
+
270
+                // Trigger change event.
271
+                for (ActionListener lis: inputChangeListeners) {
272
+                    lis.actionPerformed(new InputChangedEvent(nodes));
273
+                }
274
+            }
275
+        });
276
+
277
+        clearButton.addActionListener(new ActionListener() {
278
+            @Override
279
+            public void actionPerformed(ActionEvent e) {
280
+                textField.setText("");
281
+                setInputToFill(textField);
282
+            }
283
+        });
284
+
285
+        clickButton.addActionListener(new ActionListener() {
286
+            @Override
287
+            public void actionPerformed(ActionEvent e) {
288
+                setInputToFill(textField);
289
+            }
290
+        });
291
+
292
+        nodeInputs.add(textField);
293
+        components.add(textField);
294
+        components.add(clearButton);
295
+        components.add(clickButton);
296
+    }
297
+
298
+    /**
299
+     * @return Current graph associated with this input panel.
300
+     */
301
+    protected Graph getGraph() {
302
+        return this.graph;
303
+    }
304
+
305
+    /**
306
+     * @return The node for the given text field, or null if the content of the text
307
+     *         field is invalid.
308
+     */
309
+    protected Node getNodeForInput(JTextField textfield) {
310
+        try {
311
+            Node node = graph.get(Integer.valueOf(textfield.getText().trim()));
312
+            return node;
313
+        }
314
+        catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
315
+            return null;
316
+        }
317
+    }
318
+
319
+    /**
320
+     * @return List of nodes associated with the input. Some nodes may be null if
321
+     *         their associated input is invalid.
322
+     */
323
+    public List<Node> getNodeForInputs() {
324
+        List<Node> nodes = new ArrayList<>(nodeInputs.size());
325
+        for (JTextField input: nodeInputs) {
326
+            nodes.add(getNodeForInput(input));
327
+        }
328
+        return nodes;
329
+    }
330
+
331
+    /**
332
+     * Get the next input that should be filled by a click, or null if none should
333
+     * be filled.
334
+     * 
335
+     * @return
336
+     */
337
+    protected JTextField getInputToFill() {
338
+        if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) {
339
+            return null;
340
+        }
341
+        return nodeInputs.get(inputToFillIndex);
342
+    }
343
+
344
+    /**
345
+     * Initialize the next input to fill.
346
+     */
347
+    protected void initInputToFill() {
348
+        inputToFillIndex = 0;
349
+    }
350
+
351
+    /**
352
+     * Set the next input to fill to the given text field.
353
+     * 
354
+     * @param input
355
+     */
356
+    protected void setInputToFill(JTextField input) {
357
+        inputToFillIndex = nodeInputs.indexOf(input);
358
+    }
359
+
360
+    /**
361
+     * Find the next input to fill, if any.
362
+     */
363
+    protected void nextInputToFill() {
364
+        boolean found = false;
365
+        if (inputToFillIndex == -1) {
366
+            inputToFillIndex = 0;
367
+        }
368
+        for (int i = 0; i < nodeInputs.size() && !found; ++i) {
369
+            int nextIndex = (i + inputToFillIndex) % nodeInputs.size();
370
+            JTextField input = nodeInputs.get(nextIndex);
371
+            if (input.getText().trim().isEmpty()) {
372
+                inputToFillIndex = nextIndex;
373
+                found = true;
374
+            }
375
+        }
376
+        if (!found) {
377
+            inputToFillIndex = -1;
378
+        }
379
+    }
380
+
381
+    @Override
382
+    public void mouseClicked(Point point) {
383
+        JTextField input = getInputToFill();
384
+        if (input != null) {
385
+            Node node = nodeFinder.findClosestNode(point);
386
+            input.setText(String.valueOf(node.getId()));
387
+            nextInputToFill();
388
+        }
389
+    }
390
+
391
+    @Override
392
+    public void newGraphLoaded(Graph graph) {
393
+        if (graph != this.graph) {
394
+            this.clear();
395
+            this.graph = graph;
396
+
397
+            nodeFinder = new NodeFinder(graph);
398
+
399
+            // Disable if previously disabled...
400
+            setEnabled(this.isEnabled());
401
+        }
402
+    }
403
+
404
+    @Override
405
+    public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
406
+        if (newDrawing != drawing) {
407
+            this.drawing = newDrawing;
408
+        }
409
+    }
410
+
411
+    @Override
412
+    public void onRedrawRequest() {
413
+        for (JTextField input: nodeInputs) {
414
+            MarkerOverlay tracker = markerTrackers.getOrDefault(input, null);
415
+            if (tracker != null) {
416
+                MarkerOverlay newMarker = this.drawing.drawMarker(tracker.getPoint(),
417
+                        tracker.getColor(), Color.BLACK, AlphaMode.TRANSPARENT);
418
+                markerTrackers.put(input, newMarker);
419
+                newMarker.setVisible(tracker.isVisible());
420
+                tracker.delete();
421
+            }
422
+        }
423
+    }
424
+}

+ 361
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/PathsPanel.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Color;
4
+import java.awt.Component;
5
+import java.awt.Dimension;
6
+import java.awt.Graphics;
7
+import java.awt.Image;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+import java.awt.event.MouseAdapter;
11
+import java.awt.event.MouseEvent;
12
+import java.io.BufferedOutputStream;
13
+import java.io.DataOutputStream;
14
+import java.io.File;
15
+import java.io.FileOutputStream;
16
+import java.io.IOException;
17
+
18
+import javax.imageio.ImageIO;
19
+import javax.swing.BorderFactory;
20
+import javax.swing.Box;
21
+import javax.swing.BoxLayout;
22
+import javax.swing.Icon;
23
+import javax.swing.ImageIcon;
24
+import javax.swing.JButton;
25
+import javax.swing.JCheckBox;
26
+import javax.swing.JColorChooser;
27
+import javax.swing.JFileChooser;
28
+import javax.swing.JLabel;
29
+import javax.swing.JOptionPane;
30
+import javax.swing.JPanel;
31
+import javax.swing.border.EmptyBorder;
32
+import javax.swing.event.ChangeEvent;
33
+import javax.swing.event.ChangeListener;
34
+
35
+import org.insa.graphs.gui.drawing.Drawing;
36
+import org.insa.graphs.gui.drawing.overlays.PathOverlay;
37
+import org.insa.graphs.gui.utils.ColorUtils;
38
+import org.insa.graphs.gui.utils.FileUtils;
39
+import org.insa.graphs.gui.utils.FileUtils.FolderType;
40
+import org.insa.graphs.model.Graph;
41
+import org.insa.graphs.model.Path;
42
+import org.insa.graphs.model.io.BinaryPathWriter;
43
+
44
+public class PathsPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
45
+
46
+    /**
47
+     * 
48
+     */
49
+    private static final long serialVersionUID = 1L;
50
+
51
+    private class PathPanel extends JPanel {
52
+
53
+        /**
54
+         * 
55
+         */
56
+        private static final long serialVersionUID = 1L;
57
+
58
+        /**
59
+         * Simple icon that represents a unicolor rectangle.
60
+         *
61
+         */
62
+        protected class ColorIcon implements Icon {
63
+
64
+            private Color color;
65
+            private int width, height;
66
+
67
+            public ColorIcon(Color color, int width, int height) {
68
+                this.width = width;
69
+                this.height = height;
70
+                this.color = color;
71
+            }
72
+
73
+            public void setColor(Color color) {
74
+                this.color = color;
75
+            }
76
+
77
+            @Override
78
+            public void paintIcon(Component c, Graphics g, int x, int y) {
79
+                g.setColor(this.color);
80
+                g.fillRect(x, y, getIconWidth(), getIconHeight());
81
+            }
82
+
83
+            @Override
84
+            public int getIconWidth() {
85
+                return this.width;
86
+            }
87
+
88
+            @Override
89
+            public int getIconHeight() {
90
+                return this.height;
91
+            }
92
+        }
93
+
94
+        // Solution
95
+        private final Path path;
96
+
97
+        // Path Overlay (not final due to redraw)
98
+        private PathOverlay overlay;
99
+
100
+        // Color button
101
+        private final JButton colorButton;
102
+
103
+        /**
104
+         * Create a new bundle with the given path and create a new overlay
105
+         * corresponding to the path.
106
+         * 
107
+         * @param path Path for this bundle, must not be null.
108
+         * 
109
+         * @throws IOException If a resource was not found.
110
+         * 
111
+         */
112
+        public PathPanel(Path path, Color color) throws IOException {
113
+            super();
114
+            setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
115
+            setBorder(BorderFactory.createCompoundBorder(
116
+                    BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY),
117
+                    new EmptyBorder(5, 0, 5, 0)));
118
+            this.path = path;
119
+            this.overlay = drawing.drawPath(this.path, color);
120
+
121
+            JCheckBox checkbox = new JCheckBox();
122
+            checkbox.setSelected(true);
123
+            checkbox.addActionListener(new ActionListener() {
124
+                @Override
125
+                public void actionPerformed(ActionEvent e) {
126
+                    overlay.setVisible(checkbox.isSelected());
127
+                }
128
+            });
129
+
130
+            JLabel infoPanel = new JLabel();
131
+            String info = "";
132
+
133
+            // Display length
134
+            float length = path.getLength();
135
+            if (length < 2000) {
136
+                info += String.format("Length = %.1f meters", length);
137
+            }
138
+            else {
139
+                info += String.format("Length = %.3f kilometers", length / 1000.);
140
+            }
141
+
142
+            // Display time
143
+            info += ", Duration=";
144
+            double time = path.getMinimumTravelTime();
145
+            int hours = (int) (time / 3600);
146
+            int minutes = (int) (time / 60) % 60;
147
+            int seconds = ((int) time) % 60;
148
+            if (hours > 0) {
149
+                info += String.format("%d hours, ", hours);
150
+            }
151
+            if (minutes > 0) {
152
+                info += String.format("%d minutes, ", minutes);
153
+            }
154
+            info += String.format("%d seconds.", seconds);
155
+            infoPanel.setText("<html>" + toString() + "<br/>" + info + "</html>");
156
+            infoPanel.addMouseListener(new MouseAdapter() {
157
+                @Override
158
+                public void mouseClicked(MouseEvent e) {
159
+                    checkbox.setSelected(!checkbox.isSelected());
160
+                    overlay.setVisible(checkbox.isSelected());
161
+                }
162
+            });
163
+
164
+            Dimension size = new Dimension(24, 24);
165
+
166
+            ColorIcon icon = new ColorIcon(overlay.getColor(), 14, 14);
167
+            colorButton = new JButton(icon);
168
+            colorButton.setFocusable(false);
169
+            colorButton.setFocusPainted(false);
170
+            colorButton.setMinimumSize(size);
171
+            colorButton.setPreferredSize(size);
172
+            colorButton.setMaximumSize(size);
173
+
174
+            colorButton.setToolTipText("Pick a color");
175
+
176
+            colorButton.addActionListener(new ActionListener() {
177
+                @Override
178
+                public void actionPerformed(ActionEvent e) {
179
+                    final Color originalColor = overlay.getColor();
180
+                    JColorChooser chooser = new JColorChooser(overlay.getColor());
181
+                    chooser.getSelectionModel().addChangeListener(new ChangeListener() {
182
+                        @Override
183
+                        public void stateChanged(ChangeEvent e) {
184
+                            icon.setColor(chooser.getSelectionModel().getSelectedColor());
185
+                            colorButton.repaint();
186
+                            overlay.setColor(chooser.getSelectionModel().getSelectedColor());
187
+                            overlay.redraw();
188
+                        }
189
+                    });
190
+
191
+                    JColorChooser.createDialog(getTopLevelAncestor(), "Pick a new color", true,
192
+                            chooser, new ActionListener() {
193
+                                @Override
194
+                                public void actionPerformed(ActionEvent e) {
195
+                                    icon.setColor(chooser.getSelectionModel().getSelectedColor());
196
+                                    colorButton.repaint();
197
+                                    overlay.setColor(
198
+                                            chooser.getSelectionModel().getSelectedColor());
199
+                                    overlay.redraw();
200
+                                }
201
+                            }, new ActionListener() {
202
+                                @Override
203
+                                public void actionPerformed(ActionEvent e) {
204
+                                    icon.setColor(originalColor);
205
+                                    colorButton.repaint();
206
+                                    overlay.setColor(originalColor);
207
+                                    overlay.redraw();
208
+                                }
209
+                            }).setVisible(true);
210
+                    ;
211
+
212
+                }
213
+            });
214
+
215
+            Image saveImg = ImageIO.read(getClass().getResourceAsStream("/save-icon.png"))
216
+                    .getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
217
+            JButton saveButton = new JButton(new ImageIcon(saveImg));
218
+            saveButton.setFocusPainted(false);
219
+            saveButton.setFocusable(false);
220
+            saveButton.setMinimumSize(size);
221
+            saveButton.setPreferredSize(size);
222
+            saveButton.setMaximumSize(size);
223
+            saveButton.addActionListener(new ActionListener() {
224
+                @Override
225
+                public void actionPerformed(ActionEvent e) {
226
+                    String filepath = String.format("path_%s_%d_%d.path",
227
+                            path.getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", ""),
228
+                            path.getOrigin().getId(), path.getDestination().getId());
229
+                    JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathOutput,
230
+                            filepath);
231
+
232
+                    if (chooser
233
+                            .showSaveDialog(getTopLevelAncestor()) == JFileChooser.APPROVE_OPTION) {
234
+                        File file = chooser.getSelectedFile();
235
+                        try (BinaryPathWriter writer = new BinaryPathWriter(new DataOutputStream(
236
+                                    new BufferedOutputStream(new FileOutputStream(file))))) {
237
+                            writer.writePath(path);
238
+                        }
239
+                        catch (IOException e1) {
240
+                            JOptionPane.showMessageDialog(getTopLevelAncestor(),
241
+                                    "Unable to write path to the selected file.");
242
+                            e1.printStackTrace();
243
+                        }
244
+                    }
245
+                }
246
+            });
247
+
248
+            Image newimg = ImageIO.read(getClass().getResourceAsStream("/delete-icon.png"))
249
+                    .getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
250
+            JButton deleteButton = new JButton(new ImageIcon(newimg));
251
+            deleteButton.setFocusPainted(false);
252
+            deleteButton.setFocusable(false);
253
+            deleteButton.setMinimumSize(size);
254
+            deleteButton.setPreferredSize(size);
255
+            deleteButton.setMaximumSize(size);
256
+            deleteButton.addActionListener(new ActionListener() {
257
+                @Override
258
+                public void actionPerformed(ActionEvent e) {
259
+                    overlay.delete();
260
+                    PathsPanel.this.removePath(PathPanel.this);
261
+                }
262
+            });
263
+
264
+            add(checkbox);
265
+            add(Box.createHorizontalStrut(5));
266
+            add(infoPanel);
267
+            add(Box.createHorizontalGlue());
268
+            add(colorButton);
269
+            add(saveButton);
270
+            add(deleteButton);
271
+
272
+        }
273
+
274
+        /**
275
+         * Re-draw the current overlay (if any) on the new drawing.
276
+         * 
277
+         */
278
+        public void updateOverlay() {
279
+            PathOverlay oldOverlay = this.overlay;
280
+            this.overlay = drawing.drawPath(path);
281
+            this.overlay.setColor(oldOverlay.getColor());
282
+            ((ColorIcon) this.colorButton.getIcon()).setColor(this.overlay.getColor());
283
+            this.colorButton.repaint();
284
+            this.overlay.setVisible(oldOverlay.isVisible());
285
+            oldOverlay.delete();
286
+        }
287
+
288
+        /*
289
+         * (non-Javadoc)
290
+         * 
291
+         * @see java.lang.Object#toString()
292
+         */
293
+        public String toString() {
294
+            return "Path from #" + path.getOrigin().getId() + " to #"
295
+                    + path.getDestination().getId();
296
+        }
297
+
298
+    }
299
+
300
+    // Solution
301
+    private Drawing drawing;
302
+
303
+    public PathsPanel(Component parent) {
304
+        super();
305
+        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
306
+        setBorder(new EmptyBorder(15, 15, 15, 15));
307
+
308
+        // Default hidden
309
+        this.setVisible(false);
310
+
311
+    }
312
+
313
+    public void addPath(Path path) {
314
+        try {
315
+            this.add(new PathPanel(path, ColorUtils.getColor(this.getComponentCount())));
316
+            this.setVisible(true);
317
+            this.revalidate();
318
+            this.repaint();
319
+        }
320
+        catch (Exception e) {
321
+            e.printStackTrace();
322
+        }
323
+    }
324
+
325
+    protected void removePath(PathPanel panel) {
326
+        PathsPanel.this.remove(panel);
327
+        PathsPanel.this.revalidate();
328
+        PathsPanel.this.repaint();
329
+        if (this.getComponentCount() == 0) {
330
+            this.setVisible(false);
331
+        }
332
+    }
333
+
334
+    @Override
335
+    public void newGraphLoaded(Graph graph) {
336
+        for (Component c: this.getComponents()) {
337
+            if (c instanceof PathPanel) {
338
+                ((PathPanel) c).overlay.delete();
339
+            }
340
+        }
341
+        this.removeAll();
342
+        this.setVisible(false);
343
+    }
344
+
345
+    @Override
346
+    public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
347
+        if (newDrawing != drawing) {
348
+            drawing = newDrawing;
349
+        }
350
+    }
351
+
352
+    @Override
353
+    public void onRedrawRequest() {
354
+        for (Component c: this.getComponents()) {
355
+            if (c instanceof PathPanel) {
356
+                ((PathPanel) c).updateOverlay();
357
+            }
358
+        }
359
+    }
360
+
361
+}

+ 33
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/RunningAction.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.time.Duration;
4
+import java.time.Instant;
5
+
6
+public interface RunningAction {
7
+
8
+    /**
9
+     * @return true if this action is running.
10
+     */
11
+    public boolean isRunning();
12
+
13
+    /**
14
+     * Interrupt this action.
15
+     */
16
+    public void interrupt();
17
+
18
+    /**
19
+     * @return Starting time of this action.
20
+     */
21
+    public Instant getStartingTime();
22
+
23
+    /**
24
+     * @return Current duration of this action.
25
+     */
26
+    public Duration getDuration();
27
+
28
+    /**
29
+     * @return Information for this action.
30
+     */
31
+    public String getInformation();
32
+
33
+}

+ 289
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/SolutionPanel.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.awt.Color;
4
+import java.awt.Component;
5
+import java.awt.event.ActionEvent;
6
+import java.awt.event.ActionListener;
7
+import java.lang.reflect.InvocationTargetException;
8
+import java.lang.reflect.Method;
9
+import java.util.ArrayList;
10
+import java.util.List;
11
+
12
+import javax.swing.BorderFactory;
13
+import javax.swing.Box;
14
+import javax.swing.BoxLayout;
15
+import javax.swing.JButton;
16
+import javax.swing.JComboBox;
17
+import javax.swing.JLabel;
18
+import javax.swing.JPanel;
19
+import javax.swing.border.CompoundBorder;
20
+import javax.swing.border.EmptyBorder;
21
+
22
+import org.insa.graphs.algorithm.AbstractInputData;
23
+import org.insa.graphs.algorithm.AbstractSolution;
24
+import org.insa.graphs.gui.drawing.Drawing;
25
+import org.insa.graphs.gui.drawing.overlays.PathOverlay;
26
+import org.insa.graphs.model.Graph;
27
+import org.insa.graphs.model.Path;
28
+
29
+public class SolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
30
+
31
+    /**
32
+     * 
33
+     */
34
+    private static final long serialVersionUID = 1L;
35
+
36
+    private class SolutionBundle {
37
+
38
+        // Solution
39
+        private final AbstractSolution solution;
40
+
41
+        // Path Overlay (not final due to redraw)
42
+        private List<PathOverlay> overlays = new ArrayList<>();
43
+
44
+        /**
45
+         * Create a new bundle with the given solution and create a new overlay
46
+         * corresponding to the solution (if the solution is feasible).
47
+         * 
48
+         * @param solution Solution for this bundle, must not be null.
49
+         * 
50
+         */
51
+        public SolutionBundle(AbstractSolution solution, boolean createOverlays) {
52
+            this.solution = solution;
53
+            if (createOverlays) {
54
+                this.overlays = createOverlaysFromSolution();
55
+            }
56
+        }
57
+
58
+        /**
59
+         * @return Solution associated with this bundle.
60
+         */
61
+        public AbstractSolution getSolution() {
62
+            return this.solution;
63
+        }
64
+
65
+        /**
66
+         * @return Data assocaited with this bundle.
67
+         */
68
+        public AbstractInputData getData() {
69
+            return this.solution.getInputData();
70
+        }
71
+
72
+        /**
73
+         * @return Overlays associated with this bundle, or null.
74
+         */
75
+        public List<PathOverlay> getOverlays() {
76
+            return this.overlays;
77
+        }
78
+
79
+        /**
80
+         * @return true if this bundle has overlays.
81
+         */
82
+        public boolean hasOverlays() {
83
+            return !this.overlays.isEmpty();
84
+        }
85
+
86
+        /**
87
+         * Re-draw the current overlay (if any) on the new drawing.
88
+         * 
89
+         */
90
+        public void updateOverlays() {
91
+            if (this.overlays.isEmpty()) {
92
+                return; // This bundle has no overlay.
93
+            }
94
+            List<PathOverlay> oldOverlays = this.overlays;
95
+            this.overlays = createOverlaysFromSolution();
96
+            for (int i = 0; i < oldOverlays.size(); ++i) {
97
+                oldOverlays.get(i).delete();
98
+            }
99
+        }
100
+
101
+        private List<PathOverlay> createOverlaysFromSolution() {
102
+            List<PathOverlay> overlays = new ArrayList<>();
103
+            if (solution.isFeasible()) {
104
+                Method[] methods = this.solution.getClass().getDeclaredMethods();
105
+                for (Method method: methods) {
106
+                    if (method.getReturnType().equals(Path.class)
107
+                            && method.getParameterCount() == 0) {
108
+                        try {
109
+                            Path path = (Path) method.invoke(this.solution);
110
+                            overlays.add(drawing.drawPath(path));
111
+                        }
112
+                        catch (IllegalAccessException | IllegalArgumentException
113
+                                | InvocationTargetException e) {
114
+                            // This has been check before, so should never happen...
115
+                            e.printStackTrace();
116
+                        }
117
+                    }
118
+                }
119
+            }
120
+            return overlays;
121
+        }
122
+
123
+        /*
124
+         * (non-Javadoc)
125
+         * 
126
+         * @see java.lang.Object#toString()
127
+         */
128
+        public String toString() {
129
+            return getData().toString();
130
+        }
131
+
132
+    }
133
+
134
+    // Solution
135
+    private Drawing drawing;
136
+
137
+    // Solution selector
138
+    private final JComboBox<SolutionBundle> solutionSelect;
139
+
140
+    private final JLabel informationPanel;
141
+
142
+    // Current bundle
143
+    private SolutionBundle currentBundle = null;
144
+
145
+    public SolutionPanel(Component parent) {
146
+        super();
147
+        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
148
+        setBorder(new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY),
149
+                new EmptyBorder(10, 0, 10, 0)));
150
+
151
+        solutionSelect = new JComboBox<>();
152
+        solutionSelect.setBackground(Color.WHITE);
153
+        solutionSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
154
+        add(solutionSelect);
155
+
156
+        informationPanel = new JLabel();
157
+        informationPanel.setOpaque(true);
158
+        informationPanel.setFocusable(false);
159
+        // informationPanel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
160
+        informationPanel.setHorizontalAlignment(JLabel.LEFT);
161
+
162
+        add(Box.createVerticalStrut(8));
163
+        add(informationPanel);
164
+
165
+        JButton clearButton = new JButton("Hide");
166
+        clearButton.addActionListener(new ActionListener() {
167
+
168
+            @Override
169
+            public void actionPerformed(ActionEvent e) {
170
+                for (PathOverlay overlay: currentBundle.getOverlays()) {
171
+                    if (overlay.isVisible()) {
172
+                        overlay.setVisible(false);
173
+                        clearButton.setText("Show");
174
+                    }
175
+                    else {
176
+                        overlay.setVisible(true);
177
+                        clearButton.setText("Hide");
178
+                    }
179
+                }
180
+            }
181
+        });
182
+
183
+        JPanel buttonPanel = new JPanel();
184
+        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
185
+        buttonPanel.add(Box.createHorizontalGlue());
186
+        buttonPanel.add(clearButton);
187
+        buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
188
+
189
+        add(Box.createVerticalStrut(4));
190
+        add(buttonPanel);
191
+
192
+        solutionSelect.addActionListener(new ActionListener() {
193
+            public void actionPerformed(ActionEvent e) {
194
+
195
+                if (currentBundle != null) {
196
+                    for (PathOverlay overlay: currentBundle.getOverlays()) {
197
+                        overlay.setVisible(false);
198
+                    }
199
+                }
200
+
201
+                SolutionBundle bundle = (SolutionBundle) solutionSelect.getSelectedItem();
202
+
203
+                if (bundle != null) {
204
+
205
+                    updateInformationLabel(bundle);
206
+                    buttonPanel
207
+                            .setVisible(bundle.getSolution().isFeasible() && bundle.hasOverlays());
208
+                    clearButton.setText(bundle.getSolution().isFeasible() ? "Hide" : "Show");
209
+
210
+                    for (PathOverlay overlay: bundle.getOverlays()) {
211
+                        overlay.setVisible(true);
212
+                    }
213
+                }
214
+
215
+                currentBundle = bundle;
216
+            }
217
+        });
218
+
219
+    }
220
+
221
+    public void addSolution(AbstractSolution solution) {
222
+        addSolution(solution, true);
223
+    }
224
+
225
+    /**
226
+     * Add the given solution to the panel.
227
+     * 
228
+     * @param solution the solution to add to the panel
229
+     * @param createOverlays Whether or not overlay should be created for this
230
+     *        solution.
231
+     */
232
+    public void addSolution(AbstractSolution solution, boolean createOverlays) {
233
+        SolutionBundle bundle = new SolutionBundle(solution, createOverlays);
234
+        solutionSelect.addItem(bundle);
235
+        solutionSelect.setSelectedItem(bundle);
236
+    }
237
+
238
+    protected void updateInformationLabel(SolutionBundle bundle) {
239
+        informationPanel.setText(bundle.getSolution().toString());
240
+        revalidate();
241
+        repaint();
242
+    }
243
+
244
+    @Override
245
+    public void setEnabled(boolean enabled) {
246
+        super.setEnabled(enabled);
247
+        solutionSelect.setEnabled(enabled);
248
+
249
+        if (enabled) {
250
+            // Trigger event
251
+            solutionSelect.setSelectedItem(currentBundle);
252
+        }
253
+        else {
254
+            SolutionBundle bundle = (SolutionBundle) this.solutionSelect.getSelectedItem();
255
+            if (bundle != null) {
256
+                for (PathOverlay overlay: bundle.getOverlays()) {
257
+                    overlay.setVisible(false);
258
+                }
259
+            }
260
+        }
261
+    }
262
+
263
+    @Override
264
+    public void newGraphLoaded(Graph graph) {
265
+        for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
266
+            for (PathOverlay overlay: this.solutionSelect.getItemAt(i).getOverlays()) {
267
+                overlay.delete();
268
+            }
269
+        }
270
+        this.solutionSelect.removeAllItems();
271
+        this.currentBundle = null;
272
+        this.setVisible(false);
273
+    }
274
+
275
+    @Override
276
+    public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
277
+        if (newDrawing != drawing) {
278
+            drawing = newDrawing;
279
+        }
280
+    }
281
+
282
+    @Override
283
+    public void onRedrawRequest() {
284
+        for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
285
+            this.solutionSelect.getItemAt(i).updateOverlays();
286
+        }
287
+    }
288
+
289
+}

+ 54
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/StreamCapturer.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.io.IOException;
4
+import java.io.OutputStream;
5
+
6
+import javax.swing.JTextArea;
7
+
8
+public class StreamCapturer extends OutputStream {
9
+
10
+    private StringBuilder buffer;
11
+    private String prefix = null;
12
+    private JTextArea output;
13
+
14
+    /**
15
+     * @param output Output JTextArea to which this stream should print.
16
+     * @param prefix Prefix to add to each line of this output.
17
+     */
18
+    public StreamCapturer(JTextArea output, String prefix) {
19
+        this.prefix = prefix;
20
+        buffer = new StringBuilder(128);
21
+        this.output = output;
22
+    }
23
+
24
+    /**
25
+     * Create a new StreamCapturer without prefix.
26
+     * 
27
+     * @param output Output JTextArea to which this stream should print.
28
+     */
29
+    public StreamCapturer(JTextArea output) {
30
+        this(output, null);
31
+    }
32
+
33
+    @Override
34
+    public void write(int b) throws IOException {
35
+        char c = (char) b;
36
+        String value = Character.toString(c);
37
+        buffer.append(value);
38
+        if (value.equals("\n")) {
39
+            output.append(getPrefix() + buffer.toString());
40
+            output.setCaretPosition(output.getText().length());
41
+            buffer.delete(0, buffer.length());
42
+        }
43
+    }
44
+
45
+    /**
46
+     * @return Formatted prefix, or empty string if no prefix is set.
47
+     */
48
+    public String getPrefix() {
49
+        if (this.prefix == null) {
50
+            return "";
51
+        }
52
+        return "[" + prefix + "] ";
53
+    }
54
+}

+ 62
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/ThreadWrapper.java View File

1
+package org.insa.graphs.gui;
2
+
3
+import java.time.Duration;
4
+import java.time.Instant;
5
+
6
+public class ThreadWrapper implements RunningAction {
7
+
8
+    // Thread hold by this wrapper.
9
+    private Thread thread;
10
+
11
+    // Starting time of the thread.
12
+    Instant startingTime;
13
+
14
+    // MainWindow
15
+    private MainWindow mainWindow;
16
+
17
+    public ThreadWrapper(MainWindow mainWindow) {
18
+        this.thread = null;
19
+        this.mainWindow = mainWindow;
20
+    }
21
+
22
+    public void setThread(Thread thread) {
23
+        this.thread = thread;
24
+    }
25
+
26
+    public void startThread() {
27
+        this.startingTime = Instant.now();
28
+        this.thread.start();
29
+    }
30
+
31
+    public Thread getThread() {
32
+        return this.thread;
33
+    }
34
+
35
+    @Override
36
+    public boolean isRunning() {
37
+        return thread != null && thread.isAlive();
38
+    }
39
+
40
+    @SuppressWarnings("deprecation")
41
+    @Override
42
+    public void interrupt() {
43
+        thread.stop();
44
+        this.mainWindow.clearCurrentThread();
45
+    }
46
+
47
+    @Override
48
+    public Instant getStartingTime() {
49
+        return startingTime;
50
+    }
51
+
52
+    @Override
53
+    public Duration getDuration() {
54
+        return Duration.between(getStartingTime(), Instant.now());
55
+    }
56
+
57
+    @Override
58
+    public String getInformation() {
59
+        return getClass().getName();
60
+    }
61
+
62
+}

+ 82
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/BasicGraphPalette.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.model.Arc;
6
+import org.insa.graphs.model.RoadInformation.RoadType;
7
+
8
+public class BasicGraphPalette implements GraphPalette {
9
+
10
+    // Color types for arc.
11
+    private static final Color MOTORWAY_COLOR = Color.RED;
12
+    private static final Color BIG_ROAD_COLOR = new Color(255, 105, 0);
13
+    private static final Color SMALL_ROAD_COLOR = new Color(255, 200, 0);
14
+    private static final Color COASTLINE_COLOR = Color.BLUE;
15
+
16
+    @Override
17
+    public Color getColorForArc(Arc arc) {
18
+        RoadType type = arc.getRoadInformation().getType();
19
+        switch (type) {
20
+        case MOTORWAY:
21
+            return MOTORWAY_COLOR;
22
+        case TRUNK:
23
+        case PRIMARY:
24
+        case SECONDARY:
25
+        case MOTORWAY_LINK:
26
+        case TRUNK_LINK:
27
+        case PRIMARY_LINK:
28
+            return BIG_ROAD_COLOR;
29
+        case SECONDARY_LINK:
30
+        case TERTIARY:
31
+        case RESIDENTIAL:
32
+        case UNCLASSIFIED:
33
+        case LIVING_STREET:
34
+        case SERVICE:
35
+        case ROUNDABOUT:
36
+        case PEDESTRIAN:
37
+        case CYCLEWAY:
38
+        case TRACK:
39
+            return SMALL_ROAD_COLOR;
40
+        case COASTLINE:
41
+            return COASTLINE_COLOR;
42
+        }
43
+
44
+        return Color.BLACK;
45
+    }
46
+
47
+    @Override
48
+    public int getWidthForArc(Arc arc) {
49
+        RoadType type = arc.getRoadInformation().getType();
50
+        int width = 1;
51
+        switch (type) {
52
+        case MOTORWAY:
53
+            width = 2;
54
+            break;
55
+        case TRUNK:
56
+        case PRIMARY:
57
+        case SECONDARY:
58
+        case MOTORWAY_LINK:
59
+        case TRUNK_LINK:
60
+        case PRIMARY_LINK:
61
+            width = 1;
62
+            break;
63
+        case SECONDARY_LINK:
64
+        case TERTIARY:
65
+        case RESIDENTIAL:
66
+        case UNCLASSIFIED:
67
+        case LIVING_STREET:
68
+        case SERVICE:
69
+        case ROUNDABOUT:
70
+        case PEDESTRIAN:
71
+        case CYCLEWAY:
72
+        case TRACK:
73
+            width = 1;
74
+            break;
75
+        case COASTLINE:
76
+            width = 4;
77
+            break;
78
+        }
79
+        return width;
80
+    }
81
+
82
+}

+ 19
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/BlackAndWhiteGraphPalette.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.model.Arc;
6
+
7
+public class BlackAndWhiteGraphPalette extends BasicGraphPalette {
8
+
9
+    // Road colors (index
10
+    private final static Color[] ROAD_COLOR_FROM_WIDTH = { null, new Color(140, 140, 140),
11
+            new Color(80, 80, 80), new Color(40, 40, 40), new Color(30, 30, 30) };
12
+
13
+    @Override
14
+    public Color getColorForArc(Arc arc) {
15
+        int width = getWidthForArc(arc);
16
+        return ROAD_COLOR_FROM_WIDTH[width];
17
+    }
18
+
19
+}

+ 157
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/Drawing.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
6
+import org.insa.graphs.gui.drawing.overlays.PathOverlay;
7
+import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
8
+import org.insa.graphs.model.Graph;
9
+import org.insa.graphs.model.Path;
10
+import org.insa.graphs.model.Point;
11
+
12
+public interface Drawing {
13
+
14
+    /**
15
+     * Available fill mode for the creation of markers, see the documentation of
16
+     * each value for more details.
17
+     */
18
+    enum AlphaMode {
19
+
20
+        /**
21
+         * Do not use the original transparency of the inner part to fill it.
22
+         */
23
+        OPAQUE,
24
+
25
+        /**
26
+         * Use the original transparency of the inner part to fill it.
27
+         */
28
+        TRANSPARENT
29
+    }
30
+
31
+    /**
32
+     * Add a listener to click to this drawing.
33
+     * 
34
+     * @param listener DrawingClickListener to add to this Drawing.
35
+     */
36
+    public void addDrawingClickListener(DrawingClickListener listener);
37
+
38
+    /**
39
+     * Remove the given listener from the drawing.
40
+     * 
41
+     * @param listener DrawingClickListener to remove from this Drawing.
42
+     */
43
+    public void removeDrawingClickListener(DrawingClickListener listener);
44
+
45
+    /**
46
+     * Clear the drawing (overlays and underlying graph/map).
47
+     */
48
+    public void clear();
49
+
50
+    /**
51
+     * Remove overlays from the drawing (do not remove the underlying graph/map).
52
+     */
53
+    public void clearOverlays();
54
+
55
+    /**
56
+     * Draw a marker at the given position using the given colors and according to
57
+     * the given mode.
58
+     * 
59
+     * @param point Position of the marker to draw.
60
+     * @param outer Color for the outer part of the marker to draw.
61
+     * @param inner Color for the inner part of the marker to draw.
62
+     * @param mode Mode for filling the inner par of the marker.
63
+     * 
64
+     * @return A MarkerOverlay instance representing the newly drawn marker.
65
+     */
66
+    public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode);
67
+
68
+    /**
69
+     * Create a new PointSetOverlay that can be used to add overlay points to this
70
+     * drawing.
71
+     * 
72
+     * PointSetOverlay are heavy memory resources, do not use one for each point!
73
+     * 
74
+     * @return A new PointSetOverlay for this drawing.
75
+     */
76
+    public PointSetOverlay createPointSetOverlay();
77
+
78
+    /**
79
+     * Create a new PointSetOverlay with the given initial width and color that can
80
+     * be used to add overlay points to this drawing.
81
+     * 
82
+     * PointSetOverlay are heavy memory resources, do not use one for each point!
83
+     * 
84
+     * @param width Initial width of points in the overlay.
85
+     * @param color Initial width of points in the overlay.
86
+     * 
87
+     * @return A new PointSetOverlay for this drawing.
88
+     */
89
+    public PointSetOverlay createPointSetOverlay(int width, Color color);
90
+
91
+    /**
92
+     * Draw the given graph using the given palette.
93
+     * 
94
+     * @param graph Graph to draw.
95
+     * @param palette Palette to use to draw the graph.
96
+     * 
97
+     * @see BasicGraphPalette
98
+     * @see BlackAndWhiteGraphPalette
99
+     */
100
+    public void drawGraph(Graph graph, GraphPalette palette);
101
+
102
+    /**
103
+     * Draw the given graph using a default palette specific to the implementation.
104
+     * 
105
+     * @param graph Graph to draw.
106
+     */
107
+    public void drawGraph(Graph graph);
108
+
109
+    /**
110
+     * Draw a path using the given color.
111
+     * 
112
+     * @param path Path to draw.
113
+     * @param color Color of the path to draw.
114
+     * @param markers true to show origin and destination markers.
115
+     * 
116
+     * @return A PathOverlay instance representing the newly drawn path.
117
+     */
118
+    public PathOverlay drawPath(Path path, Color color, boolean markers);
119
+
120
+    /**
121
+     * Draw a path with both origin and destination markers using the given color.
122
+     * 
123
+     * @param path Path to draw.
124
+     * @param color Color of the path to draw.
125
+     * 
126
+     * @return A PathOverlay instance representing the newly drawn path.
127
+     * 
128
+     * @see Drawing#drawPath(Path, Color, boolean)
129
+     */
130
+    public PathOverlay drawPath(Path path, Color color);
131
+
132
+    /**
133
+     * Draw a path using a default color specific to the implementation
134
+     * 
135
+     * @param path Path to draw.
136
+     * @param markers true to show origin and destination markers.
137
+     * 
138
+     * @return A PathOverlay instance representing the newly drawn path.
139
+     * 
140
+     * @see Drawing#drawPath(Path, Color, boolean)
141
+     */
142
+    public PathOverlay drawPath(Path path, boolean markers);
143
+
144
+    /**
145
+     * Draw a path with both origin and destination markers using a default color
146
+     * specific to the implementation
147
+     * 
148
+     * 
149
+     * @param path Path to draw.
150
+     * 
151
+     * @return A PathOverlay instance representing the newly drawn path.
152
+     * 
153
+     * @see Drawing#drawPath(Path, Color, boolean)
154
+     */
155
+    public PathOverlay drawPath(Path path);
156
+
157
+}

+ 14
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/DrawingClickListener.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import org.insa.graphs.model.Point;
4
+
5
+public interface DrawingClickListener {
6
+
7
+    /**
8
+     * Event triggered when a click is made on the map.
9
+     * 
10
+     * @param point Position (on the map) of the mouse click.
11
+     */
12
+    public void mouseClicked(Point point);
13
+
14
+}

+ 23
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/GraphPalette.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.model.Arc;
6
+
7
+public interface GraphPalette {
8
+
9
+    /**
10
+     * @param arc Arc for which color should be retrieved.
11
+     * 
12
+     * @return Color associated with the given arc.
13
+     */
14
+    public Color getColorForArc(Arc arc);
15
+
16
+    /**
17
+     * @param arc Arc for which width should be retrieved.
18
+     * 
19
+     * @return Width associated with the given arc.
20
+     */
21
+    public int getWidthForArc(Arc arc);
22
+
23
+}

+ 116
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/MercatorProjection.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import java.awt.Dimension;
4
+
5
+import org.insa.graphs.model.GraphStatistics.BoundingBox;
6
+
7
+public class MercatorProjection implements Projection {
8
+
9
+    public static final double MAX_LATITUDE = 82;
10
+
11
+    public static final double MIN_LATITUDE = -MAX_LATITUDE;
12
+
13
+    // From Wikipedia... for the above max/min latitude.
14
+    private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746;
15
+
16
+    private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE);
17
+    private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE);
18
+
19
+    // Bounding box
20
+    private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
21
+
22
+    // Projection of min and max latitude.
23
+    private final double minLatitudeProj, maxLatitudeProj;
24
+
25
+    // Dimension of the image
26
+    private final double width, height;
27
+
28
+    /**
29
+     * Create a new MercatorProjection corresponding to the given BoundingBox and
30
+     * maxSize.
31
+     * 
32
+     * @param boundingBox Box for this projection.
33
+     * @param maxSize Maximum size of any side (width / height) of the image to
34
+     *        which this projection should draw.
35
+     */
36
+    public MercatorProjection(BoundingBox boundingBox, int maxSize) {
37
+        // Find minimum/maximum longitude and latitude.
38
+        this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
39
+        this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
40
+        this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
41
+        this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
42
+
43
+        // Compute projection
44
+        this.minLatitudeProj = projectY(this.minLatitude);
45
+        this.maxLatitudeProj = projectY(this.maxLatitude);
46
+
47
+        Dimension imageDimension = computeImageSize(maxSize);
48
+        this.width = imageDimension.getWidth();
49
+        this.height = imageDimension.getHeight();
50
+    }
51
+
52
+    /**
53
+     * Compute the projection (without scaling) of the given latitude.
54
+     * 
55
+     * @param latitude Latitude to project.
56
+     * 
57
+     * @return Projection of the given latitude (without scaling).
58
+     */
59
+    private static double projectY(double latitude) {
60
+        double sinLatitude = Math.sin(latitude * Math.PI / 180.0);
61
+        return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2;
62
+    }
63
+
64
+    /**
65
+     * Compute the dimension required for drawing a projection of the given box on
66
+     * an image, ensuring that none of the side of image is greater than maxSize.
67
+     * 
68
+     * @param maxSize Maximum side of any side of the image.
69
+     * 
70
+     * @return Dimension corresponding to the preferred size for the image.
71
+     */
72
+    protected Dimension computeImageSize(int maxSize) {
73
+        double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0;
74
+        double propHeight = (this.maxLatitudeProj - this.minLatitudeProj)
75
+                / (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT;
76
+
77
+        return propWidth < propHeight
78
+                ? new Dimension((int) (maxSize * propWidth / propHeight), maxSize)
79
+                : new Dimension(maxSize, (int) (maxSize * propHeight / propWidth));
80
+    }
81
+
82
+    @Override
83
+    public double getImageWidth() {
84
+        return this.width;
85
+    }
86
+
87
+    @Override
88
+    public double getImageHeight() {
89
+        return this.height;
90
+    }
91
+
92
+    @Override
93
+    public int latitudeToPixelY(float latitude) {
94
+        return (int) ((this.maxLatitudeProj - projectY(latitude))
95
+                / (this.maxLatitudeProj - this.minLatitudeProj) * this.height);
96
+    }
97
+
98
+    @Override
99
+    public int longitudeToPixelX(float longitude) {
100
+        return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude));
101
+    }
102
+
103
+    @Override
104
+    public float pixelYToLatitude(double py) {
105
+        float y = (float) (this.maxLatitudeProj
106
+                - (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj));
107
+        return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI);
108
+    }
109
+
110
+    @Override
111
+    public float pixelXToLongitude(double px) {
112
+        return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude)
113
+                + this.minLongitude);
114
+    }
115
+
116
+}

+ 68
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/PlateCarreProjection.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+import org.insa.graphs.model.GraphStatistics.BoundingBox;
4
+
5
+public class PlateCarreProjection implements Projection {
6
+
7
+    // Bounding box
8
+    private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
9
+
10
+    // Dimension of the image
11
+    private final double width, height;
12
+
13
+    /**
14
+     * Create a new PlateCarreProjection corresponding to the given BoundingBox and
15
+     * maxSize.
16
+     * 
17
+     * @param boundingBox Box for this projection.
18
+     * @param maxSize Maximum size of any side (width / height) of the image to
19
+     *        which this projection should draw.
20
+     */
21
+    public PlateCarreProjection(BoundingBox boundingBox, int maxSize) {
22
+        // Find minimum/maximum longitude and latitude.
23
+        this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
24
+        this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
25
+        this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
26
+        this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
27
+
28
+        float diffLon = maxLongitude - minLongitude, diffLat = maxLatitude - minLatitude;
29
+
30
+        this.width = diffLon < diffLat ? (int) (maxSize * diffLon / diffLat) : maxSize;
31
+        this.height = diffLon < diffLat ? maxSize : (int) (maxSize * diffLat / diffLon);
32
+    }
33
+
34
+    @Override
35
+    public double getImageWidth() {
36
+        return this.width;
37
+    }
38
+
39
+    @Override
40
+    public double getImageHeight() {
41
+        return this.height;
42
+    }
43
+
44
+    @Override
45
+    public int latitudeToPixelY(float latitude) {
46
+        return (int) (this.height * (this.maxLatitude - latitude)
47
+                / (this.maxLatitude - this.minLatitude));
48
+    }
49
+
50
+    @Override
51
+    public int longitudeToPixelX(float longitude) {
52
+        return (int) (this.width * (longitude - this.minLongitude)
53
+                / (this.maxLongitude - this.minLongitude));
54
+    }
55
+
56
+    @Override
57
+    public float pixelYToLatitude(double py) {
58
+        return (float) (this.maxLatitude
59
+                - py / this.height * (this.maxLatitude - this.minLatitude));
60
+    }
61
+
62
+    @Override
63
+    public float pixelXToLongitude(double px) {
64
+        return (float) (px / this.width * (this.maxLongitude - this.minLongitude)
65
+                + this.minLongitude);
66
+    }
67
+
68
+}

+ 51
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/Projection.java View File

1
+package org.insa.graphs.gui.drawing;
2
+
3
+public interface Projection {
4
+
5
+    /**
6
+     * @return Image width for this projection to work properly.
7
+     */
8
+    public double getImageWidth();
9
+
10
+    /**
11
+     * @return Image weight for this projection to work properly.
12
+     */
13
+    public double getImageHeight();
14
+
15
+    /**
16
+     * Project the given latitude on the image.
17
+     * 
18
+     * @param latitude Latitude to project.
19
+     * 
20
+     * @return Projected position of the latitude on the image.
21
+     */
22
+    public int latitudeToPixelY(float latitude);
23
+
24
+    /**
25
+     * Project the given longitude on the image.
26
+     * 
27
+     * @param longitude Longitude to project.
28
+     * 
29
+     * @return Projected position of the longitude on the image.
30
+     */
31
+    public int longitudeToPixelX(float longitude);
32
+
33
+    /**
34
+     * Retrieve the latitude associated to the given projected point.
35
+     * 
36
+     * @param py Projected y-position for which latitude should be retrieved.
37
+     * 
38
+     * @return The original latitude of the point.
39
+     */
40
+    public float pixelYToLatitude(double py);
41
+
42
+    /**
43
+     * Retrieve the longitude associated to the given projected point.
44
+     * 
45
+     * @param px Projected x-position for which longitude should be retrieved.
46
+     * 
47
+     * @return The original longitude of the point.
48
+     */
49
+    public float pixelXToLongitude(double px);
50
+
51
+}

+ 747
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/BasicDrawing.java View File

1
+package org.insa.graphs.gui.drawing.components;
2
+
3
+import java.awt.BasicStroke;
4
+import java.awt.Color;
5
+import java.awt.Graphics;
6
+import java.awt.Graphics2D;
7
+import java.awt.Image;
8
+import java.awt.event.ActionEvent;
9
+import java.awt.event.ActionListener;
10
+import java.awt.event.MouseAdapter;
11
+import java.awt.event.MouseEvent;
12
+import java.awt.geom.AffineTransform;
13
+import java.awt.geom.NoninvertibleTransformException;
14
+import java.awt.geom.Point2D;
15
+import java.awt.image.BufferedImage;
16
+import java.io.IOException;
17
+import java.util.ArrayList;
18
+import java.util.Iterator;
19
+import java.util.List;
20
+
21
+import javax.swing.JPanel;
22
+
23
+import org.insa.graphs.gui.drawing.BasicGraphPalette;
24
+import org.insa.graphs.gui.drawing.Drawing;
25
+import org.insa.graphs.gui.drawing.DrawingClickListener;
26
+import org.insa.graphs.gui.drawing.GraphPalette;
27
+import org.insa.graphs.gui.drawing.MercatorProjection;
28
+import org.insa.graphs.gui.drawing.PlateCarreProjection;
29
+import org.insa.graphs.gui.drawing.Projection;
30
+import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
31
+import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
32
+import org.insa.graphs.gui.drawing.overlays.Overlay;
33
+import org.insa.graphs.gui.drawing.overlays.PathOverlay;
34
+import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
35
+import org.insa.graphs.model.Arc;
36
+import org.insa.graphs.model.Graph;
37
+import org.insa.graphs.model.Node;
38
+import org.insa.graphs.model.Path;
39
+import org.insa.graphs.model.Point;
40
+import org.insa.graphs.model.GraphStatistics.BoundingBox;
41
+
42
+/**
43
+ *
44
+ */
45
+public class BasicDrawing extends JPanel implements Drawing {
46
+
47
+    /**
48
+     * 
49
+     */
50
+    private static final long serialVersionUID = 96779785877771827L;
51
+
52
+    private abstract class BasicOverlay implements Overlay {
53
+
54
+        // Visible?
55
+        protected boolean visible;
56
+
57
+        // Color
58
+        protected Color color;
59
+
60
+        public BasicOverlay(Color color) {
61
+            this.visible = true;
62
+            this.color = color;
63
+        }
64
+
65
+        /**
66
+         * @return The Z level of this overlay (>= 1).
67
+         */
68
+        public abstract int getZLevel();
69
+
70
+        @Override
71
+        public void setColor(Color color) {
72
+            this.color = color;
73
+        }
74
+
75
+        @Override
76
+        public Color getColor() {
77
+            return this.color;
78
+        }
79
+
80
+        @Override
81
+        public void setVisible(boolean visible) {
82
+            this.visible = visible;
83
+            BasicDrawing.this.repaint();
84
+        }
85
+
86
+        @Override
87
+        public boolean isVisible() {
88
+            return this.visible;
89
+        }
90
+
91
+        @Override
92
+        public void delete() {
93
+            BasicDrawing.this.overlays.remove(this);
94
+        }
95
+
96
+        /**
97
+         * Draw the given overlay.
98
+         */
99
+        public void draw(Graphics2D g) {
100
+            if (this.visible) {
101
+                drawImpl(g);
102
+            }
103
+        }
104
+
105
+        public abstract void drawImpl(Graphics2D g);
106
+
107
+        public void redraw() {
108
+            BasicDrawing.this.repaint();
109
+        }
110
+
111
+    };
112
+
113
+    private class BasicMarkerOverlay extends BasicOverlay implements MarkerOverlay {
114
+
115
+        // Marker width and height
116
+        public static final int MARKER_WIDTH = 30, MARKER_HEIGHT = 60;
117
+
118
+        // Point of the marker.
119
+        private Point point;
120
+
121
+        // Image to draw
122
+        private Image image;
123
+
124
+        // Inner color and fill mode.
125
+        private Color innerColor;
126
+        private final AlphaMode alphaMode;
127
+
128
+        public BasicMarkerOverlay(Point point, Color color, Color inner, AlphaMode alphaMode) {
129
+            super(color);
130
+            this.point = point;
131
+            this.image = MarkerUtils.getMarkerForColor(color, inner, alphaMode);
132
+            this.innerColor = inner;
133
+            this.alphaMode = alphaMode;
134
+        }
135
+
136
+        public int getZLevel() {
137
+            return 3;
138
+        }
139
+
140
+        @Override
141
+        public Point getPoint() {
142
+            return point;
143
+        }
144
+
145
+        @Override
146
+        public void setColor(Color color) {
147
+            this.innerColor = this.innerColor.equals(this.color) ? color : innerColor;
148
+            super.setColor(color);
149
+            this.image = MarkerUtils.getMarkerForColor(color, this.innerColor, alphaMode);
150
+        }
151
+
152
+        @Override
153
+        public void moveTo(Point point) {
154
+            this.point = point;
155
+            BasicDrawing.this.repaint();
156
+        }
157
+
158
+        @Override
159
+        public void drawImpl(Graphics2D graphics) {
160
+
161
+            int px = projection.longitudeToPixelX(getPoint().getLongitude());
162
+            int py = projection.latitudeToPixelY(getPoint().getLatitude());
163
+
164
+            graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
165
+                    MARKER_HEIGHT, BasicDrawing.this);
166
+        }
167
+
168
+    };
169
+
170
+    private class BasicPathOverlay extends BasicOverlay implements PathOverlay {
171
+
172
+        // List of points
173
+        private final List<Point> points;
174
+
175
+        // Origin / Destination markers.
176
+        private BasicMarkerOverlay origin, destination;
177
+
178
+        public BasicPathOverlay(List<Point> points, Color color, BasicMarkerOverlay origin,
179
+                BasicMarkerOverlay destination) {
180
+            super(color);
181
+            this.points = points;
182
+            this.origin = origin;
183
+            this.destination = destination;
184
+            this.color = color;
185
+        }
186
+
187
+        public int getZLevel() {
188
+            return 2;
189
+        }
190
+
191
+        @Override
192
+        public void setColor(Color color) {
193
+            super.setColor(color);
194
+            this.origin.setColor(color);
195
+            this.destination.setColor(color);
196
+        }
197
+
198
+        @Override
199
+        public void drawImpl(Graphics2D graphics) {
200
+
201
+            if (!points.isEmpty()) {
202
+
203
+                graphics.setStroke(new BasicStroke(2));
204
+                graphics.setColor(getColor());
205
+
206
+                Iterator<Point> itPoint = points.iterator();
207
+                Point prev = itPoint.next();
208
+
209
+                while (itPoint.hasNext()) {
210
+                    Point curr = itPoint.next();
211
+
212
+                    int x1 = projection.longitudeToPixelX(prev.getLongitude());
213
+                    int x2 = projection.longitudeToPixelX(curr.getLongitude());
214
+                    int y1 = projection.latitudeToPixelY(prev.getLatitude());
215
+                    int y2 = projection.latitudeToPixelY(curr.getLatitude());
216
+
217
+                    graphics.drawLine(x1, y1, x2, y2);
218
+
219
+                    prev = curr;
220
+                }
221
+
222
+            }
223
+
224
+            if (this.origin != null) {
225
+                this.origin.draw(graphics);
226
+            }
227
+            if (this.destination != null) {
228
+                this.destination.draw(graphics);
229
+            }
230
+        }
231
+
232
+    };
233
+
234
+    private class BasicPointSetOverlay extends BasicOverlay implements PointSetOverlay {
235
+
236
+        // Default point width
237
+        private static final int DEFAULT_POINT_WIDTH = 5;
238
+
239
+        // Image for path / points
240
+        private final BufferedImage image;
241
+        private final Graphics2D graphics;
242
+
243
+        private int width = DEFAULT_POINT_WIDTH;
244
+
245
+        public BasicPointSetOverlay() {
246
+            super(Color.BLACK);
247
+            this.image = new BufferedImage(BasicDrawing.this.width, BasicDrawing.this.height,
248
+                    BufferedImage.TYPE_4BYTE_ABGR);
249
+            this.graphics = image.createGraphics();
250
+            this.graphics.setBackground(new Color(0, 0, 0, 0));
251
+        }
252
+
253
+        public int getZLevel() {
254
+            return 1;
255
+        }
256
+
257
+        @Override
258
+        public void setColor(Color color) {
259
+            super.setColor(color);
260
+            this.graphics.setColor(color);
261
+        }
262
+
263
+        @Override
264
+        public void setWidth(int width) {
265
+            this.width = Math.max(2, width);
266
+        }
267
+
268
+        @Override
269
+        public void setWidthAndColor(int width, Color color) {
270
+            setWidth(width);
271
+            setColor(color);
272
+        }
273
+
274
+        @Override
275
+        public void addPoint(Point point) {
276
+            int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
277
+            int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
278
+            this.graphics.fillOval(x, y, this.width, this.width);
279
+            BasicDrawing.this.repaint();
280
+        }
281
+
282
+        @Override
283
+        public void addPoint(Point point, int width) {
284
+            setWidth(width);
285
+            addPoint(point);
286
+        }
287
+
288
+        @Override
289
+        public void addPoint(Point point, Color color) {
290
+            setColor(color);
291
+            addPoint(point);
292
+        }
293
+
294
+        @Override
295
+        public void addPoint(Point point, int width, Color color) {
296
+            setWidth(width);
297
+            setColor(color);
298
+            addPoint(point);
299
+        }
300
+
301
+        @Override
302
+        public void drawImpl(Graphics2D g) {
303
+            g.drawImage(this.image, 0, 0, BasicDrawing.this);
304
+        }
305
+
306
+    }
307
+
308
+    /**
309
+     * Class encapsulating a set of overlays.
310
+     *
311
+     */
312
+    private class BasicOverlays {
313
+
314
+        // List of overlays.
315
+        private ArrayList<ArrayList<BasicOverlay>> overlays = new ArrayList<>();
316
+
317
+        public synchronized void draw(Graphics2D g) {
318
+            // Clear overlays.
319
+            for (ArrayList<BasicOverlay> arr: this.overlays) {
320
+                for (BasicOverlay overlay: arr) {
321
+                    overlay.draw(g);
322
+                }
323
+            }
324
+        }
325
+
326
+        public synchronized void remove(BasicOverlay overlay) {
327
+            overlays.get(overlay.getZLevel() - 1).remove(overlay);
328
+            BasicDrawing.this.repaint();
329
+        }
330
+
331
+        public void clear() {
332
+            clear(true);
333
+        }
334
+
335
+        public void clear(boolean repaint) {
336
+            // Clear overlays.
337
+            for (ArrayList<BasicOverlay> arr: this.overlays) {
338
+                arr.clear();
339
+            }
340
+            // Repaint if requested.
341
+            if (repaint) {
342
+                BasicDrawing.this.repaint();
343
+            }
344
+        }
345
+
346
+        public BasicOverlay add(BasicOverlay marker) {
347
+            return add(marker, true);
348
+        }
349
+
350
+        public synchronized BasicOverlay add(BasicOverlay overlay, boolean repaint) {
351
+
352
+            // Check if we have a level for this...
353
+            for (int i = overlays.size(); i < overlay.getZLevel(); ++i) {
354
+                overlays.add(new ArrayList<>());
355
+            }
356
+
357
+            // Add overlay to the given list.
358
+            overlays.get(overlay.getZLevel() - 1).add(overlay);
359
+
360
+            // Repaint if requested.
361
+            if (repaint) {
362
+                BasicDrawing.this.repaint();
363
+            }
364
+
365
+            return overlay;
366
+        }
367
+
368
+    };
369
+
370
+    // Default path color.
371
+    public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
372
+
373
+    // Default palette.
374
+    public static final GraphPalette DEFAULT_PALETTE = new BasicGraphPalette();
375
+
376
+    // Maximum width for the drawing (in pixels).
377
+    private static final int MAXIMUM_DRAWING_WIDTH = 2000;
378
+
379
+    private Projection projection;
380
+
381
+    // Width and height of the image
382
+    private int width, height;
383
+
384
+    // Zoom controls
385
+    private MapZoomControls zoomControls;
386
+    private ZoomAndPanListener zoomAndPanListener;
387
+
388
+    //
389
+    private Image graphImage = null;
390
+    private Graphics2D graphGraphics = null;
391
+
392
+    // List of image for markers
393
+    private BasicOverlays overlays = new BasicOverlays();
394
+
395
+    // Mapping DrawingClickListener -> MouseEventListener
396
+    private List<DrawingClickListener> drawingClickListeners = new ArrayList<>();
397
+
398
+    /**
399
+     * Create a new BasicDrawing.
400
+     * 
401
+     */
402
+    public BasicDrawing() {
403
+        setLayout(null);
404
+        this.setBackground(new Color(245, 245, 245));
405
+        this.zoomAndPanListener = new ZoomAndPanListener(this,
406
+                ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2);
407
+
408
+        // Try...
409
+        try {
410
+            this.zoomControls = new MapZoomControls(this, 0,
411
+                    ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20);
412
+            this.zoomControls.addZoomInListener(new ActionListener() {
413
+                @Override
414
+                public void actionPerformed(ActionEvent e) {
415
+                    zoomAndPanListener.zoomIn();
416
+                }
417
+            });
418
+            this.zoomControls.addZoomOutListener(new ActionListener() {
419
+                @Override
420
+                public void actionPerformed(ActionEvent e) {
421
+                    zoomAndPanListener.zoomOut();
422
+                }
423
+            });
424
+        }
425
+        catch (IOException e) {
426
+            e.printStackTrace();
427
+        }
428
+
429
+        this.addMouseListener(new MouseAdapter() {
430
+
431
+            @Override
432
+            public void mouseClicked(MouseEvent evt) {
433
+                if (zoomControls.contains(evt.getPoint())) {
434
+                    return;
435
+                }
436
+                Point lonlat = null;
437
+                try {
438
+                    lonlat = getLongitudeLatitude(evt);
439
+                }
440
+                catch (NoninvertibleTransformException e) {
441
+                    return;
442
+                }
443
+                for (DrawingClickListener listener: drawingClickListeners) {
444
+                    listener.mouseClicked(lonlat);
445
+                }
446
+            }
447
+
448
+        });
449
+    }
450
+
451
+    @Override
452
+    public void paintComponent(Graphics g1) {
453
+        super.paintComponent(g1);
454
+        Graphics2D g = (Graphics2D) g1;
455
+        AffineTransform sTransform = g.getTransform();
456
+        g.setColor(this.getBackground());
457
+        g.fillRect(0, 0, getWidth(), getHeight());
458
+        g.setTransform(zoomAndPanListener.getCoordTransform());
459
+
460
+        if (graphImage != null) {
461
+            // Draw graph
462
+            g.drawImage(graphImage, 0, 0, this);
463
+        }
464
+
465
+        // Draw markers
466
+        this.overlays.draw(g);
467
+
468
+        g.setTransform(sTransform);
469
+        if (this.zoomControls != null) {
470
+            this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel());
471
+            this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20,
472
+                    this.getHeight() - this.zoomControls.getHeight() - 10, this);
473
+        }
474
+
475
+    }
476
+
477
+    /*
478
+     * (non-Javadoc)
479
+     * 
480
+     * @see org.insa.graphics.drawing.Drawing#clear()
481
+     */
482
+    @Override
483
+    public void clear() {
484
+        if (this.graphGraphics != null) {
485
+            this.graphGraphics.clearRect(0, 0, this.width, this.height);
486
+        }
487
+        this.overlays.clear(false);
488
+        this.repaint();
489
+    }
490
+
491
+    /*
492
+     * (non-Javadoc)
493
+     * 
494
+     * @see org.insa.graphics.drawing.Drawing#clearOverlays()
495
+     */
496
+    @Override
497
+    public void clearOverlays() {
498
+        this.overlays.clear();
499
+    }
500
+
501
+    /**
502
+     * @return The current ZoomAndPanListener associated with this drawing.
503
+     */
504
+    public ZoomAndPanListener getZoomAndPanListener() {
505
+        return this.zoomAndPanListener;
506
+    }
507
+
508
+    /**
509
+     * Return the longitude and latitude corresponding to the given position of the
510
+     * MouseEvent.
511
+     * 
512
+     * @param event MouseEvent from which longitude/latitude should be retrieved.
513
+     * 
514
+     * @return Point representing the projection of the MouseEvent position in the
515
+     *         graph/map.
516
+     * 
517
+     * @throws NoninvertibleTransformException if the actual transformation is
518
+     *         invalid.
519
+     */
520
+    protected Point getLongitudeLatitude(MouseEvent event) throws NoninvertibleTransformException {
521
+        // Get the point using the inverse transform of the Zoom/Pan object, this gives
522
+        // us
523
+        // a point within the drawing box (between [0, 0] and [width, height]).
524
+        Point2D ptDst = this.zoomAndPanListener.getCoordTransform()
525
+                .inverseTransform(event.getPoint(), null);
526
+
527
+        // Inverse the "projection" on x/y to get longitude and latitude.
528
+        return new Point(projection.pixelXToLongitude(ptDst.getX()),
529
+                projection.pixelYToLatitude(ptDst.getY()));
530
+    }
531
+
532
+    /*
533
+     * (non-Javadoc)
534
+     * 
535
+     * @see
536
+     * org.insa.graphics.drawing.Drawing#addDrawingClickListener(org.insa.graphics.
537
+     * drawing.DrawingClickListener)
538
+     */
539
+    @Override
540
+    public void addDrawingClickListener(DrawingClickListener listener) {
541
+        this.drawingClickListeners.add(listener);
542
+    }
543
+
544
+    /*
545
+     * (non-Javadoc)
546
+     * 
547
+     * @see org.insa.graphics.drawing.Drawing#removeDrawingClickListener(org.insa.
548
+     * graphics.drawing.DrawingClickListener)
549
+     */
550
+    @Override
551
+    public void removeDrawingClickListener(DrawingClickListener listener) {
552
+        this.drawingClickListeners.remove(listener);
553
+    }
554
+
555
+    public BasicMarkerOverlay createMarker(Point point, Color outer, Color inner, AlphaMode mode) {
556
+        return new BasicMarkerOverlay(point, outer, inner, mode);
557
+    }
558
+
559
+    @Override
560
+    public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
561
+        return (MarkerOverlay) this.overlays.add(createMarker(point, outer, inner, mode));
562
+    }
563
+
564
+    @Override
565
+    public PointSetOverlay createPointSetOverlay() {
566
+        return (PointSetOverlay) this.overlays.add(new BasicPointSetOverlay(), false);
567
+    }
568
+
569
+    @Override
570
+    public PointSetOverlay createPointSetOverlay(int width, Color color) {
571
+        PointSetOverlay ps = createPointSetOverlay();
572
+        ps.setWidthAndColor(width, color);
573
+        return ps;
574
+    }
575
+
576
+    /**
577
+     * Draw the given arc.
578
+     * 
579
+     * @param arc Arc to draw.
580
+     * @param palette Palette to use to retrieve color and width for arc, or null to
581
+     *        use current settings.
582
+     */
583
+    protected void drawArc(Arc arc, GraphPalette palette, boolean repaint) {
584
+        List<Point> pts = arc.getPoints();
585
+        if (!pts.isEmpty()) {
586
+            if (palette != null) {
587
+                this.graphGraphics.setColor(palette.getColorForArc(arc));
588
+                this.graphGraphics.setStroke(new BasicStroke(palette.getWidthForArc(arc)));
589
+            }
590
+            Iterator<Point> it1 = pts.iterator();
591
+            Point prev = it1.next();
592
+            while (it1.hasNext()) {
593
+                Point curr = it1.next();
594
+
595
+                int x1 = projection.longitudeToPixelX(prev.getLongitude());
596
+                int x2 = projection.longitudeToPixelX(curr.getLongitude());
597
+                int y1 = projection.latitudeToPixelY(prev.getLatitude());
598
+                int y2 = projection.latitudeToPixelY(curr.getLatitude());
599
+
600
+                graphGraphics.drawLine(x1, y1, x2, y2);
601
+                prev = curr;
602
+            }
603
+        }
604
+        if (repaint) {
605
+            this.repaint();
606
+        }
607
+    }
608
+
609
+    /**
610
+     * Initialize the drawing for the given graph.
611
+     * 
612
+     * @param graph
613
+     */
614
+    protected void initialize(Graph graph) {
615
+
616
+        // Clear everything.
617
+        this.clear();
618
+
619
+        BoundingBox box = graph.getGraphInformation().getBoundingBox();
620
+
621
+        // Find minimum/maximum longitude and latitude.
622
+        float minLon = box.getTopLeftPoint().getLongitude(),
623
+                maxLon = box.getBottomRightPoint().getLongitude(),
624
+                minLat = box.getBottomRightPoint().getLatitude(),
625
+                maxLat = box.getTopLeftPoint().getLatitude();
626
+
627
+        // Add a little delta to avoid drawing on the edge...
628
+        float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
629
+        float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
630
+
631
+        // Create the projection and retrieve width and height for the box.
632
+        BoundingBox extendedBox = box.extend(deltaLon, deltaLat, deltaLon, deltaLat);
633
+
634
+        // Special projection for non-realistic maps...
635
+        if (graph.getMapId().startsWith("0x")) {
636
+            projection = new PlateCarreProjection(extendedBox, MAXIMUM_DRAWING_WIDTH / 4);
637
+        }
638
+        else {
639
+            projection = new MercatorProjection(extendedBox, MAXIMUM_DRAWING_WIDTH);
640
+        }
641
+        this.width = (int) projection.getImageWidth();
642
+        this.height = (int) projection.getImageHeight();
643
+
644
+        // Create the image
645
+        BufferedImage img = new BufferedImage(this.width, this.height,
646
+                BufferedImage.TYPE_3BYTE_BGR);
647
+        this.graphImage = img;
648
+        this.graphGraphics = img.createGraphics();
649
+        this.graphGraphics.setBackground(this.getBackground());
650
+        this.graphGraphics.clearRect(0, 0, this.width, this.height);
651
+
652
+        // Set the zoom and pan listener
653
+
654
+        double scale = 1 / Math.max(this.width / (double) this.getWidth(),
655
+                this.height / (double) this.getHeight());
656
+
657
+        this.zoomAndPanListener.setCoordTransform(this.graphGraphics.getTransform());
658
+        this.zoomAndPanListener.getCoordTransform().translate(
659
+                (this.getWidth() - this.width * scale) / 2,
660
+                (this.getHeight() - this.height * scale) / 2);
661
+        this.zoomAndPanListener.getCoordTransform().scale(scale, scale);
662
+        this.zoomAndPanListener.setZoomLevel(0);
663
+        this.zoomControls.setZoomLevel(0);
664
+
665
+        // Repaint
666
+        this.repaint();
667
+    }
668
+
669
+    @Override
670
+    public void drawGraph(Graph graph, GraphPalette palette) {
671
+        int repaintModulo = Math.max(1, graph.size() / 100);
672
+
673
+        // Initialize the buffered image
674
+
675
+        this.initialize(graph);
676
+
677
+        // Remove zoom and pan listener
678
+        this.removeMouseListener(zoomAndPanListener);
679
+        this.removeMouseMotionListener(zoomAndPanListener);
680
+        this.removeMouseWheelListener(zoomAndPanListener);
681
+
682
+        for (Node node: graph.getNodes()) {
683
+            for (Arc arc: node.getSuccessors()) {
684
+                // Draw arcs only if there are one-way arcs or if origin is lower than
685
+                // destination, avoid drawing two-ways arc twice.
686
+                if (arc.getRoadInformation().isOneWay()
687
+                        || arc.getOrigin().compareTo(arc.getDestination()) < 0) {
688
+                    drawArc(arc, palette, false);
689
+                }
690
+            }
691
+            if (node.getId() % repaintModulo == 0) {
692
+                this.repaint();
693
+            }
694
+        }
695
+        this.repaint();
696
+
697
+        // Re-add zoom and pan listener
698
+        this.addMouseListener(zoomAndPanListener);
699
+        this.addMouseMotionListener(zoomAndPanListener);
700
+        this.addMouseWheelListener(zoomAndPanListener);
701
+    }
702
+
703
+    @Override
704
+    public void drawGraph(Graph graph) {
705
+        drawGraph(graph, DEFAULT_PALETTE);
706
+    }
707
+
708
+    @Override
709
+    public PathOverlay drawPath(Path path, Color color, boolean markers) {
710
+        List<Point> points = new ArrayList<Point>();
711
+        if (!path.isEmpty()) {
712
+            points.add(path.getOrigin().getPoint());
713
+            for (Arc arc: path.getArcs()) {
714
+                Iterator<Point> itPoint = arc.getPoints().iterator();
715
+                // Discard origin each time
716
+                itPoint.next();
717
+                while (itPoint.hasNext()) {
718
+                    points.add(itPoint.next());
719
+                }
720
+            }
721
+        }
722
+        BasicMarkerOverlay origin = null, destination = null;
723
+        if (markers && !path.isEmpty()) {
724
+            origin = createMarker(path.getOrigin().getPoint(), color, color, AlphaMode.TRANSPARENT);
725
+            destination = createMarker(path.getDestination().getPoint(), color, color,
726
+                    AlphaMode.TRANSPARENT);
727
+        }
728
+        return (PathOverlay) this.overlays
729
+                .add(new BasicPathOverlay(points, color, origin, destination));
730
+    }
731
+
732
+    @Override
733
+    public PathOverlay drawPath(Path path, Color color) {
734
+        return drawPath(path, color, true);
735
+    }
736
+
737
+    @Override
738
+    public PathOverlay drawPath(Path path) {
739
+        return drawPath(path, DEFAULT_PATH_COLOR);
740
+    }
741
+
742
+    @Override
743
+    public PathOverlay drawPath(Path path, boolean markers) {
744
+        return drawPath(path, DEFAULT_PATH_COLOR, markers);
745
+    }
746
+
747
+}

+ 521
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/MapViewDrawing.java View File

1
+package org.insa.graphs.gui.drawing.components;
2
+
3
+import java.awt.Color;
4
+import java.awt.Graphics;
5
+import java.awt.Graphics2D;
6
+import java.awt.Image;
7
+import java.awt.event.ActionEvent;
8
+import java.awt.event.ActionListener;
9
+import java.io.File;
10
+import java.io.IOException;
11
+import java.util.ArrayList;
12
+import java.util.List;
13
+import java.util.UUID;
14
+import java.util.stream.Collectors;
15
+
16
+import org.insa.graphs.gui.drawing.Drawing;
17
+import org.insa.graphs.gui.drawing.DrawingClickListener;
18
+import org.insa.graphs.gui.drawing.GraphPalette;
19
+import org.insa.graphs.gui.drawing.overlays.MarkerAutoScaling;
20
+import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
21
+import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
22
+import org.insa.graphs.gui.drawing.overlays.Overlay;
23
+import org.insa.graphs.gui.drawing.overlays.PathOverlay;
24
+import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
25
+import org.insa.graphs.gui.drawing.overlays.PolylineAutoScaling;
26
+import org.insa.graphs.model.Arc;
27
+import org.insa.graphs.model.Graph;
28
+import org.insa.graphs.model.Path;
29
+import org.insa.graphs.model.Point;
30
+import org.mapsforge.core.graphics.GraphicFactory;
31
+import org.mapsforge.core.model.BoundingBox;
32
+import org.mapsforge.core.model.LatLong;
33
+import org.mapsforge.core.model.MapPosition;
34
+import org.mapsforge.core.util.LatLongUtils;
35
+import org.mapsforge.core.util.Parameters;
36
+import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
37
+import org.mapsforge.map.awt.util.AwtUtil;
38
+import org.mapsforge.map.awt.view.MapView;
39
+import org.mapsforge.map.datastore.MapDataStore;
40
+import org.mapsforge.map.layer.Layer;
41
+import org.mapsforge.map.layer.Layers;
42
+import org.mapsforge.map.layer.cache.TileCache;
43
+import org.mapsforge.map.layer.hills.HillsRenderConfig;
44
+import org.mapsforge.map.layer.overlay.Marker;
45
+import org.mapsforge.map.layer.overlay.Polygon;
46
+import org.mapsforge.map.layer.renderer.TileRendererLayer;
47
+import org.mapsforge.map.model.DisplayModel;
48
+import org.mapsforge.map.model.IMapViewPosition;
49
+import org.mapsforge.map.model.Model;
50
+import org.mapsforge.map.reader.MapFile;
51
+import org.mapsforge.map.rendertheme.InternalRenderTheme;
52
+
53
+/**
54
+ *
55
+ */
56
+public class MapViewDrawing extends MapView implements Drawing {
57
+
58
+    /**
59
+     * 
60
+     */
61
+    private static final long serialVersionUID = 8606967833704938092L;
62
+
63
+    /**
64
+     * Base Overlay for MapViewDrawing overlays.
65
+     *
66
+     */
67
+    private abstract class MapViewOverlay implements Overlay {
68
+
69
+        // Marker associated.
70
+        protected Layer[] layers;
71
+
72
+        // Current color
73
+        protected Color color;
74
+
75
+        public MapViewOverlay(Layer[] layers, Color color) {
76
+            this.layers = layers;
77
+            for (Layer layer: this.layers) {
78
+                MapViewDrawing.this.getLayerManager().getLayers().add(layer);
79
+            }
80
+            this.color = color;
81
+        }
82
+
83
+        @Override
84
+        public void setColor(Color color) {
85
+            this.color = color;
86
+        }
87
+
88
+        @Override
89
+        public Color getColor() {
90
+            return this.color;
91
+        }
92
+
93
+        @Override
94
+        public void setVisible(boolean visible) {
95
+            for (Layer layer: layers) {
96
+                layer.setVisible(visible);
97
+            }
98
+        }
99
+
100
+        @Override
101
+        public boolean isVisible() {
102
+            if (this.layers.length == 0) {
103
+                return true;
104
+            }
105
+            return this.layers[0].isVisible();
106
+        }
107
+
108
+        @Override
109
+        public void delete() {
110
+            Layers mlayers = MapViewDrawing.this.getLayerManager().getLayers();
111
+            for (Layer layer: layers) {
112
+                mlayers.remove(layer);
113
+            }
114
+        }
115
+
116
+        @Override
117
+        public void redraw() {
118
+            MapViewDrawing.this.getLayerManager().redrawLayers();
119
+        }
120
+    };
121
+
122
+    /**
123
+     * MarkerOverlay for MapViewDrawing.
124
+     *
125
+     */
126
+    private class MapViewMarkerOverlay extends MapViewOverlay implements MarkerOverlay {
127
+
128
+        private final AlphaMode alphaMode;
129
+        private Color innerColor;
130
+
131
+        public MapViewMarkerOverlay(Marker marker, Color outer, Color innerColor,
132
+                AlphaMode alphaMode) {
133
+            super(new Layer[] { marker }, outer);
134
+            this.innerColor = innerColor;
135
+            this.alphaMode = alphaMode;
136
+        }
137
+
138
+        @Override
139
+        public Point getPoint() {
140
+            Marker marker = (Marker) super.layers[0];
141
+            return new Point((float) marker.getLatLong().getLongitude(),
142
+                    (float) marker.getLatLong().getLatitude());
143
+        }
144
+
145
+        @Override
146
+        public void setColor(Color outer) {
147
+            this.innerColor = this.innerColor.equals(this.color) ? outer : this.innerColor;
148
+            super.setColor(color);
149
+            MarkerAutoScaling marker = (MarkerAutoScaling) super.layers[0];
150
+            marker.setImage(MarkerUtils.getMarkerForColor(color, this.innerColor, this.alphaMode));
151
+        }
152
+
153
+        @Override
154
+        public void moveTo(Point point) {
155
+            MarkerAutoScaling marker = (MarkerAutoScaling) this.layers[0];
156
+            this.delete();
157
+            marker = new MarkerAutoScaling(convertPoint(point), marker.getImage());
158
+            this.layers[0] = marker;
159
+            MapViewDrawing.this.getLayerManager().getLayers().add(marker);
160
+        }
161
+
162
+    };
163
+
164
+    /**
165
+     * PathOverlay for MapViewDrawing.
166
+     *
167
+     */
168
+    private class MapViewPathOverlay extends MapViewOverlay implements PathOverlay {
169
+
170
+        public MapViewPathOverlay(PolylineAutoScaling path, MarkerAutoScaling origin,
171
+                MarkerAutoScaling destination) {
172
+            super(new Layer[] { path, origin, destination }, path.getColor());
173
+        }
174
+
175
+        public MapViewPathOverlay(PolylineAutoScaling path) {
176
+            super(new Layer[] { path }, path.getColor());
177
+        }
178
+
179
+        @Override
180
+        public void setColor(Color color) {
181
+            super.setColor(color);
182
+            ((PolylineAutoScaling) this.layers[0]).setColor(color);
183
+            ((MarkerAutoScaling) this.layers[1])
184
+                    .setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
185
+            ((MarkerAutoScaling) this.layers[2])
186
+                    .setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
187
+        }
188
+
189
+    }
190
+
191
+    /**
192
+     * PointSetOverlay for MapViewDrawing - Not currently implemented.
193
+     *
194
+     */
195
+    private class MapViewPointSetOverlay extends MapViewOverlay implements PointSetOverlay {
196
+
197
+        private List<Point> points = new ArrayList<>();
198
+        private final Polygon polygon;
199
+
200
+        private List<Point> convexHull(List<Point> p) {
201
+            if (p.isEmpty()) {
202
+                return new ArrayList<>();
203
+            }
204
+            p.sort((p1, p2) -> Float.compare(p1.getLongitude(), p2.getLongitude()));
205
+            List<Point> h = new ArrayList<>();
206
+
207
+            // lower hull
208
+            for (Point pt: p) {
209
+                while (h.size() >= 2 && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
210
+                    h.remove(h.size() - 1);
211
+                }
212
+                h.add(pt);
213
+            }
214
+
215
+            // upper hull
216
+            int t = h.size() + 1;
217
+            for (int i = p.size() - 1; i >= 0; i--) {
218
+                Point pt = p.get(i);
219
+                while (h.size() >= t && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
220
+                    h.remove(h.size() - 1);
221
+                }
222
+                h.add(pt);
223
+            }
224
+
225
+            h.remove(h.size() - 1);
226
+            return h;
227
+        }
228
+
229
+        // ccw returns true if the three points make a counter-clockwise turn
230
+        private boolean ccw(Point a, Point b, Point c) {
231
+            return ((b.getLongitude() - a.getLongitude())
232
+                    * (c.getLatitude() - a.getLatitude())) > ((b.getLatitude() - a.getLatitude())
233
+                            * (c.getLongitude() - a.getLongitude()));
234
+        }
235
+
236
+        public MapViewPointSetOverlay() {
237
+            super(new Layer[] { new Polygon(GRAPHIC_FACTORY.createPaint(), null, GRAPHIC_FACTORY) },
238
+                    Color.BLACK);
239
+            polygon = (Polygon) this.layers[0];
240
+        }
241
+
242
+        @Override
243
+        public void setColor(Color color) {
244
+            super.setColor(color);
245
+            polygon.getPaintFill().setColor(GRAPHIC_FACTORY.createColor(100, color.getRed(),
246
+                    color.getGreen(), color.getBlue()));
247
+        }
248
+
249
+        @Override
250
+        public void setWidth(int width) {
251
+        }
252
+
253
+        @Override
254
+        public void setWidthAndColor(int width, Color color) {
255
+            setWidth(width);
256
+            setColor(color);
257
+        }
258
+
259
+        @Override
260
+        public void addPoint(Point point) {
261
+            points.add(point);
262
+            this.points = convexHull(points);
263
+            polygon.setPoints(this.points.stream().map(MapViewDrawing.this::convertPoint)
264
+                    .collect(Collectors.toList()));
265
+            polygon.requestRedraw();
266
+        }
267
+
268
+        @Override
269
+        public void addPoint(Point point, int width) {
270
+            setWidth(width);
271
+            addPoint(point);
272
+        }
273
+
274
+        @Override
275
+        public void addPoint(Point point, Color color) {
276
+            setColor(color);
277
+            addPoint(point);
278
+        }
279
+
280
+        @Override
281
+        public void addPoint(Point point, int width, Color color) {
282
+            setWidth(width);
283
+            setColor(color);
284
+            addPoint(point);
285
+        }
286
+
287
+    };
288
+
289
+    // Default path color.
290
+    public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
291
+
292
+    // Graphic factory.
293
+    private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
294
+
295
+    // Default tile size.
296
+    private static final int DEFAULT_TILE_SIZE = 512;
297
+
298
+    // List of listeners.
299
+    private ArrayList<DrawingClickListener> drawingClickListeners = new ArrayList<>();
300
+
301
+    // Tile size
302
+    private int tileSize;
303
+
304
+    // Zoom controls
305
+    private MapZoomControls zoomControls;
306
+
307
+    public MapViewDrawing() {
308
+        super();
309
+        Parameters.NUMBER_OF_THREADS = 2;
310
+        Parameters.SQUARE_FRAME_BUFFER = false;
311
+
312
+        getMapScaleBar().setVisible(true);
313
+        DisplayModel model = getModel().displayModel;
314
+        this.tileSize = DEFAULT_TILE_SIZE;
315
+        model.setFixedTileSize(this.tileSize);
316
+
317
+        this.setZoomLevelMin((byte) 0);
318
+        this.setZoomLevelMax((byte) 20);
319
+
320
+        // Try...
321
+        try {
322
+            this.zoomControls = new MapZoomControls(this, 0, 0, 20);
323
+            this.zoomControls.addZoomInListener(new ActionListener() {
324
+                @Override
325
+                public void actionPerformed(ActionEvent e) {
326
+                    getModel().mapViewPosition.zoomIn();
327
+                }
328
+            });
329
+            this.zoomControls.addZoomOutListener(new ActionListener() {
330
+                @Override
331
+                public void actionPerformed(ActionEvent e) {
332
+                    getModel().mapViewPosition.zoomOut();
333
+                }
334
+            });
335
+        }
336
+        catch (IOException e) {
337
+            e.printStackTrace();
338
+        }
339
+    }
340
+
341
+    /*
342
+     * (non-Javadoc)
343
+     * 
344
+     * @see org.mapsforge.map.awt.view.MapView#paint(java.awt.Graphics)
345
+     */
346
+    @Override
347
+    public void paint(Graphics graphics) {
348
+        super.paint(graphics);
349
+        if (this.zoomControls != null) {
350
+            this.zoomControls.setZoomLevel(this.getModel().mapViewPosition.getZoomLevel());
351
+            this.zoomControls.draw((Graphics2D) graphics,
352
+                    getWidth() - this.zoomControls.getWidth() - 20,
353
+                    this.getHeight() - this.zoomControls.getHeight() - 10, this);
354
+        }
355
+
356
+    }
357
+
358
+    /*
359
+     * (non-Javadoc)
360
+     * 
361
+     * @see org.insa.graphics.drawing.Drawing#clear()
362
+     */
363
+    @Override
364
+    public void clear() {
365
+        getLayerManager().getLayers().clear();
366
+        repaint();
367
+    }
368
+
369
+    /*
370
+     * (non-Javadoc)
371
+     * 
372
+     * @see org.insa.graphics.drawing.Drawing#clearOverlays()
373
+     */
374
+    @Override
375
+    public void clearOverlays() {
376
+        Layers layers = getLayerManager().getLayers();
377
+        for (Layer layer: layers) {
378
+            if (layer instanceof PolylineAutoScaling || layer instanceof MarkerAutoScaling) {
379
+                getLayerManager().getLayers().remove(layer, false);
380
+            }
381
+        }
382
+        repaint();
383
+    }
384
+
385
+    protected LatLong convertPoint(Point point) {
386
+        return new LatLong(point.getLatitude(), point.getLongitude());
387
+    }
388
+
389
+    private TileRendererLayer createTileRendererLayer(TileCache tileCache,
390
+            MapDataStore mapDataStore, IMapViewPosition mapViewPosition,
391
+            HillsRenderConfig hillsRenderConfig) {
392
+        TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapDataStore,
393
+                mapViewPosition, false, true, false, GRAPHIC_FACTORY, hillsRenderConfig) {
394
+            @Override
395
+            public boolean onTap(LatLong tapLatLong, org.mapsforge.core.model.Point layerXY,
396
+                    org.mapsforge.core.model.Point tapXY) {
397
+                if (zoomControls.contains(new java.awt.Point((int) tapXY.x, (int) tapXY.y))) {
398
+                    return false;
399
+                }
400
+                Point pt = new Point((float) tapLatLong.getLongitude(),
401
+                        (float) tapLatLong.getLatitude());
402
+                for (DrawingClickListener listener: MapViewDrawing.this.drawingClickListeners) {
403
+                    listener.mouseClicked(pt);
404
+                }
405
+                return true;
406
+            }
407
+        };
408
+        tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.DEFAULT);
409
+        return tileRendererLayer;
410
+    }
411
+
412
+    @Override
413
+    public void addDrawingClickListener(DrawingClickListener listener) {
414
+        this.drawingClickListeners.add(listener);
415
+    }
416
+
417
+    @Override
418
+    public void removeDrawingClickListener(DrawingClickListener listener) {
419
+        this.drawingClickListeners.remove(listener);
420
+    }
421
+
422
+    protected MarkerAutoScaling createMarker(Point point, Color outer, Color inner,
423
+            AlphaMode mode) {
424
+        Image image = MarkerUtils.getMarkerForColor(outer, inner, mode);
425
+        return new MarkerAutoScaling(convertPoint(point), image);
426
+    }
427
+
428
+    @Override
429
+    public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
430
+        return new MapViewMarkerOverlay(createMarker(point, outer, inner, mode), outer, inner,
431
+                mode);
432
+    }
433
+
434
+    @Override
435
+    public PointSetOverlay createPointSetOverlay() {
436
+        return new MapViewPointSetOverlay();
437
+    }
438
+
439
+    @Override
440
+    public PointSetOverlay createPointSetOverlay(int width, Color color) {
441
+        PointSetOverlay ps = new MapViewPointSetOverlay();
442
+        ps.setWidthAndColor(width, color);
443
+        return ps;
444
+    }
445
+
446
+    public void drawGraph(File file) {
447
+
448
+        // Tile cache
449
+        TileCache tileCache = AwtUtil.createTileCache(tileSize,
450
+                getModel().frameBufferModel.getOverdrawFactor(), 1024,
451
+                new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()));
452
+
453
+        // Layers
454
+        Layers layers = getLayerManager().getLayers();
455
+
456
+        MapDataStore mapDataStore = new MapFile(file);
457
+        TileRendererLayer tileRendererLayer = createTileRendererLayer(tileCache, mapDataStore,
458
+                getModel().mapViewPosition, null);
459
+        layers.add(tileRendererLayer);
460
+        BoundingBox boundingBox = mapDataStore.boundingBox();
461
+
462
+        final Model model = getModel();
463
+        if (model.mapViewPosition.getZoomLevel() == 0
464
+                || !boundingBox.contains(model.mapViewPosition.getCenter())) {
465
+            byte zoomLevel = LatLongUtils.zoomForBounds(model.mapViewDimension.getDimension(),
466
+                    boundingBox, model.displayModel.getTileSize());
467
+            model.mapViewPosition
468
+                    .setMapPosition(new MapPosition(boundingBox.getCenterPoint(), zoomLevel));
469
+            zoomControls.setZoomLevel(zoomLevel);
470
+        }
471
+
472
+    }
473
+
474
+    @Override
475
+    public void drawGraph(Graph graph, GraphPalette palette) {
476
+        throw new RuntimeException("Not implemented, use drawGraph(File).");
477
+    }
478
+
479
+    @Override
480
+    public void drawGraph(Graph graph) {
481
+        throw new RuntimeException("Not implemented, use drawGraph(File).");
482
+    }
483
+
484
+    @Override
485
+    public PathOverlay drawPath(Path path, Color color, boolean markers) {
486
+        PolylineAutoScaling line = new PolylineAutoScaling(1, color);
487
+        ArrayList<Point> points = new ArrayList<>(path.getArcs().size() * 4);
488
+        for (Arc arc: path.getArcs()) {
489
+            points.addAll(arc.getPoints());
490
+        }
491
+        line.addAll(points);
492
+        PathOverlay overlay = null;
493
+        if (markers) {
494
+            MarkerAutoScaling origin = createMarker(path.getOrigin().getPoint(), color, color,
495
+                    AlphaMode.TRANSPARENT),
496
+                    destination = createMarker(path.getDestination().getPoint(), color, color,
497
+                            AlphaMode.TRANSPARENT);
498
+            overlay = new MapViewPathOverlay(line, origin, destination);
499
+        }
500
+        else {
501
+            overlay = new MapViewPathOverlay(line);
502
+        }
503
+        return overlay;
504
+    }
505
+
506
+    @Override
507
+    public PathOverlay drawPath(Path path, Color color) {
508
+        return drawPath(path, color, true);
509
+    }
510
+
511
+    @Override
512
+    public PathOverlay drawPath(Path path) {
513
+        return drawPath(path, DEFAULT_PATH_COLOR, true);
514
+    }
515
+
516
+    @Override
517
+    public PathOverlay drawPath(Path path, boolean markers) {
518
+        return drawPath(path, DEFAULT_PATH_COLOR, markers);
519
+    }
520
+
521
+}

+ 209
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/MapZoomControls.java View File

1
+package org.insa.graphs.gui.drawing.components;
2
+
3
+import java.awt.BasicStroke;
4
+import java.awt.Color;
5
+import java.awt.Component;
6
+import java.awt.Cursor;
7
+import java.awt.Graphics2D;
8
+import java.awt.Image;
9
+import java.awt.Point;
10
+import java.awt.Rectangle;
11
+import java.awt.event.ActionEvent;
12
+import java.awt.event.ActionListener;
13
+import java.awt.event.MouseAdapter;
14
+import java.awt.event.MouseEvent;
15
+import java.awt.image.ImageObserver;
16
+import java.io.IOException;
17
+import java.util.ArrayList;
18
+import java.util.List;
19
+
20
+import javax.imageio.ImageIO;
21
+
22
+public class MapZoomControls {
23
+
24
+    // Default ID for action events
25
+    private static final int ZOOM_IN_ACTION_ID = 0x1;
26
+    private static final String ZOOM_IN_ACTION_NAME = "ZoomIn";
27
+
28
+    private static final int ZOOM_OUT_ACTION_ID = 0x2;
29
+    private static final String ZOOM_OUT_ACTION_NAME = "ZoomOut";
30
+
31
+    // Height
32
+    private static final int DEFAULT_HEIGHT = 20;
33
+
34
+    // Default spacing between mark
35
+    private static final int DEFAULT_SPACING = 4;
36
+
37
+    // Zoom ticks ratio from height (not the current one)
38
+    private static final double ZOOM_TICK_HEIGHT_RATIO = 0.3;
39
+
40
+    // Zoom ticks color
41
+    private static final Color ZOOM_TICK_COLOR = Color.GRAY;
42
+
43
+    // Current zoom ticks ratio from height
44
+    private static final double CURRENT_ZOOM_TICK_HEIGHT_RATIO = 0.8;
45
+
46
+    // Zoom ticks color
47
+    private static final Color CURRENT_ZOOM_TICK_COLOR = new Color(25, 25, 25);
48
+
49
+    // Use half mark or not
50
+    private boolean halfMark = true;
51
+
52
+    private int currentLevel = 0;
53
+    private final int minLevel, maxLevel;
54
+
55
+    // Zoom in/out image and their rectangles.
56
+    private final Image zoomIn, zoomOut;
57
+    private final Rectangle zoomInRect = new Rectangle(0, 0, 0, 0),
58
+            zoomOutRect = new Rectangle(0, 0, 0, 0);
59
+
60
+    // List of listeners
61
+    private final List<ActionListener> zoomInListeners = new ArrayList<>();
62
+    private final List<ActionListener> zoomOutListeners = new ArrayList<>();
63
+
64
+    public MapZoomControls(Component component, final int defaultZoom, final int minZoom,
65
+            final int maxZoom) throws IOException {
66
+
67
+        zoomIn = ImageIO.read(getClass().getResourceAsStream("/zoomIn.png"))
68
+                .getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
69
+        zoomOut = ImageIO.read(getClass().getResourceAsStream("/zoomOut.png"))
70
+                .getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
71
+
72
+        this.currentLevel = defaultZoom;
73
+        this.minLevel = minZoom;
74
+        this.maxLevel = maxZoom;
75
+
76
+        component.addMouseMotionListener(new MouseAdapter() {
77
+            @Override
78
+            public void mouseMoved(MouseEvent e) {
79
+                if (zoomInRect.contains(e.getPoint()) || zoomOutRect.contains(e.getPoint())) {
80
+                    component.setCursor(new Cursor(Cursor.HAND_CURSOR));
81
+                }
82
+                else {
83
+                    component.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
84
+                }
85
+            }
86
+        });
87
+
88
+        component.addMouseListener(new MouseAdapter() {
89
+            @Override
90
+            public void mouseClicked(MouseEvent e) {
91
+                if (zoomInRect.contains(e.getPoint()) && currentLevel < maxLevel) {
92
+                    currentLevel += 1;
93
+                    for (ActionListener al: zoomInListeners) {
94
+                        al.actionPerformed(
95
+                                new ActionEvent(this, ZOOM_IN_ACTION_ID, ZOOM_IN_ACTION_NAME));
96
+                    }
97
+                }
98
+                else if (zoomOutRect.contains(e.getPoint()) && currentLevel > minLevel) {
99
+                    currentLevel -= 1;
100
+                    for (ActionListener al: zoomOutListeners) {
101
+                        al.actionPerformed(
102
+                                new ActionEvent(this, ZOOM_OUT_ACTION_ID, ZOOM_OUT_ACTION_NAME));
103
+                    }
104
+                }
105
+                component.repaint();
106
+            }
107
+        });
108
+    }
109
+
110
+    /**
111
+     * Add a zoom-in listener.
112
+     * 
113
+     * @param listener Zoom-in listener to add to this MapZoomControls instance.
114
+     */
115
+    public void addZoomInListener(ActionListener listener) {
116
+        this.zoomInListeners.add(listener);
117
+    }
118
+
119
+    /**
120
+     * Add a zoom-out listener.
121
+     * 
122
+     * @param listener Zoom-out listener to add to this MapZoomControls instance.
123
+     */
124
+    public void addZoomOutListener(ActionListener listener) {
125
+        this.zoomOutListeners.add(listener);
126
+    }
127
+
128
+    /**
129
+     * @return the current zoom level.
130
+     */
131
+    public int getZoomLevel() {
132
+        return this.currentLevel;
133
+    }
134
+
135
+    /**
136
+     * Set the current zoom level without requesting a redraw.
137
+     * 
138
+     * @param level Zoom level to set.
139
+     */
140
+    public void setZoomLevel(int level) {
141
+        this.currentLevel = level;
142
+    }
143
+
144
+    /**
145
+     * @return Height of this "component" when drawn.
146
+     */
147
+    public int getHeight() {
148
+        return DEFAULT_HEIGHT;
149
+    }
150
+
151
+    /**
152
+     * @return Width of this "component" when drawn.
153
+     */
154
+    public int getWidth() {
155
+        return DEFAULT_HEIGHT + 2 + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2
156
+                + DEFAULT_HEIGHT;
157
+    }
158
+
159
+    /**
160
+     * Check if a point is contained inside an element of this zoom controls, useful
161
+     * to avoid spurious click listeners.
162
+     * 
163
+     * @param point Point to check.
164
+     * 
165
+     * @return true if the given point correspond to an element of this zoom
166
+     *         controls.
167
+     */
168
+    public boolean contains(Point point) {
169
+        return zoomInRect.contains(point) || zoomOutRect.contains(point);
170
+    }
171
+
172
+    protected void draw(Graphics2D g, int xoffset, int yoffset, ImageObserver observer) {
173
+
174
+        int height = getHeight();
175
+
176
+        // Draw icon
177
+        g.drawImage(zoomOut, xoffset, yoffset, observer);
178
+        zoomOutRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
179
+
180
+        g.setStroke(new BasicStroke(1));
181
+
182
+        // Draw ticks
183
+        xoffset += DEFAULT_HEIGHT + 2;
184
+        g.setColor(ZOOM_TICK_COLOR);
185
+        g.drawLine(xoffset, yoffset + height / 2,
186
+                xoffset + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1,
187
+                yoffset + height / 2);
188
+        for (int i = 0; i <= (this.maxLevel - this.minLevel); i += halfMark ? 2 : 1) {
189
+            g.drawLine(xoffset + i * DEFAULT_SPACING,
190
+                    yoffset + (int) (height * (1 - ZOOM_TICK_HEIGHT_RATIO) / 2),
191
+                    xoffset + i * DEFAULT_SPACING,
192
+                    yoffset + (int) (height * (1 + ZOOM_TICK_HEIGHT_RATIO) / 2));
193
+        }
194
+
195
+        // Draw current ticks
196
+        g.setColor(CURRENT_ZOOM_TICK_COLOR);
197
+        g.drawLine(xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
198
+                yoffset + (int) (height * (1 - CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2),
199
+                xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
200
+                yoffset + (int) (height * (1 + CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2));
201
+
202
+        xoffset += (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2;
203
+
204
+        g.drawImage(zoomIn, xoffset, yoffset, observer);
205
+        zoomInRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
206
+
207
+    }
208
+
209
+}

+ 177
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/components/ZoomAndPanListener.java View File

1
+package org.insa.graphs.gui.drawing.components;
2
+
3
+import java.awt.Component;
4
+import java.awt.Point;
5
+import java.awt.event.MouseEvent;
6
+import java.awt.event.MouseListener;
7
+import java.awt.event.MouseMotionListener;
8
+import java.awt.event.MouseWheelEvent;
9
+import java.awt.event.MouseWheelListener;
10
+import java.awt.geom.AffineTransform;
11
+import java.awt.geom.NoninvertibleTransformException;
12
+import java.awt.geom.Point2D;
13
+
14
+public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener {
15
+    public static final int DEFAULT_MIN_ZOOM_LEVEL = -20;
16
+    public static final int DEFAULT_MAX_ZOOM_LEVEL = 10;
17
+    public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2;
18
+
19
+    private Component targetComponent;
20
+
21
+    private int zoomLevel = 0;
22
+    private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL;
23
+    private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL;
24
+    private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR;
25
+
26
+    private Point dragStartScreen;
27
+    private Point dragEndScreen;
28
+    private AffineTransform coordTransform = new AffineTransform();
29
+
30
+    public ZoomAndPanListener(Component targetComponent) {
31
+        this.targetComponent = targetComponent;
32
+    }
33
+
34
+    public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel,
35
+            double zoomMultiplicationFactor) {
36
+        this.targetComponent = targetComponent;
37
+        this.minZoomLevel = minZoomLevel;
38
+        this.maxZoomLevel = maxZoomLevel;
39
+        this.zoomMultiplicationFactor = zoomMultiplicationFactor;
40
+    }
41
+
42
+    public void translate(double dx, double dy) {
43
+        coordTransform.translate(dx, dy);
44
+        targetComponent.repaint();
45
+    }
46
+
47
+    public void mouseClicked(MouseEvent e) {
48
+    }
49
+
50
+    public void mousePressed(MouseEvent e) {
51
+        dragStartScreen = e.getPoint();
52
+        dragEndScreen = null;
53
+    }
54
+
55
+    public void mouseReleased(MouseEvent e) {
56
+    }
57
+
58
+    public void mouseEntered(MouseEvent e) {
59
+    }
60
+
61
+    public void mouseExited(MouseEvent e) {
62
+    }
63
+
64
+    public void mouseMoved(MouseEvent e) {
65
+    }
66
+
67
+    public void mouseDragged(MouseEvent e) {
68
+        moveCamera(e);
69
+    }
70
+
71
+    public void mouseWheelMoved(MouseWheelEvent e) {
72
+        zoomCamera(e);
73
+    }
74
+
75
+    private void moveCamera(MouseEvent e) {
76
+        try {
77
+            dragEndScreen = e.getPoint();
78
+            Point2D.Float dragStart = transformPoint(dragStartScreen);
79
+            Point2D.Float dragEnd = transformPoint(dragEndScreen);
80
+            double dx = dragEnd.getX() - dragStart.getX();
81
+            double dy = dragEnd.getY() - dragStart.getY();
82
+            coordTransform.translate(dx, dy);
83
+            dragStartScreen = dragEndScreen;
84
+            dragEndScreen = null;
85
+            targetComponent.repaint();
86
+        }
87
+        catch (NoninvertibleTransformException ex) {
88
+            ex.printStackTrace();
89
+        }
90
+    }
91
+
92
+    private void zoomCamera(MouseWheelEvent e) {
93
+        try {
94
+            int wheelRotation = e.getWheelRotation();
95
+            Point p = e.getPoint();
96
+            if (wheelRotation < 0) {
97
+                if (zoomLevel < maxZoomLevel) {
98
+                    zoomLevel++;
99
+                    Point2D p1 = transformPoint(p);
100
+                    coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
101
+                    Point2D p2 = transformPoint(p);
102
+                    coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
103
+                    targetComponent.repaint();
104
+                }
105
+            }
106
+            else {
107
+                if (zoomLevel > minZoomLevel) {
108
+                    zoomLevel--;
109
+                    Point2D p1 = transformPoint(p);
110
+                    coordTransform.scale(1 / zoomMultiplicationFactor,
111
+                            1 / zoomMultiplicationFactor);
112
+                    Point2D p2 = transformPoint(p);
113
+                    coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
114
+                    targetComponent.repaint();
115
+                }
116
+            }
117
+        }
118
+        catch (NoninvertibleTransformException ex) {
119
+            ex.printStackTrace();
120
+        }
121
+    }
122
+
123
+    private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
124
+
125
+        AffineTransform inverse = coordTransform.createInverse();
126
+
127
+        Point2D.Float p2 = new Point2D.Float();
128
+        inverse.transform(p1, p2);
129
+        return p2;
130
+    }
131
+
132
+    public int getZoomLevel() {
133
+        return zoomLevel;
134
+    }
135
+
136
+    public void setZoomLevel(int zoomLevel) {
137
+        this.zoomLevel = zoomLevel;
138
+    }
139
+
140
+    public void zoomIn() {
141
+        try {
142
+            Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
143
+            zoomLevel++;
144
+            Point2D p1 = transformPoint(p);
145
+            coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
146
+            Point2D p2 = transformPoint(p);
147
+            coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
148
+            targetComponent.repaint();
149
+        }
150
+        catch (NoninvertibleTransformException ex) {
151
+            ex.printStackTrace();
152
+        }
153
+    }
154
+
155
+    public void zoomOut() {
156
+        try {
157
+            Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
158
+            zoomLevel--;
159
+            Point2D p1 = transformPoint(p);
160
+            coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
161
+            Point2D p2 = transformPoint(p);
162
+            coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
163
+            targetComponent.repaint();
164
+        }
165
+        catch (NoninvertibleTransformException ex) {
166
+            ex.printStackTrace();
167
+        }
168
+    }
169
+
170
+    public AffineTransform getCoordTransform() {
171
+        return coordTransform;
172
+    }
173
+
174
+    public void setCoordTransform(AffineTransform coordTransform) {
175
+        this.coordTransform = coordTransform;
176
+    }
177
+}

+ 73
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerAutoScaling.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Graphics2D;
4
+import java.awt.Image;
5
+import java.awt.image.BufferedImage;
6
+
7
+import org.mapsforge.core.graphics.Canvas;
8
+import org.mapsforge.core.model.BoundingBox;
9
+import org.mapsforge.core.model.LatLong;
10
+import org.mapsforge.core.model.Point;
11
+import org.mapsforge.map.awt.graphics.AwtBitmap;
12
+import org.mapsforge.map.layer.overlay.Marker;
13
+
14
+/**
15
+ * Class extending the default Mapsforge's {@link Marker} with auto-scaling.
16
+ * 
17
+ * Mapsforge's Markers do not scale with zoom level, this class aims at
18
+ * correcting this. Internally, this image stores an {@link Image} instance and
19
+ * scale it when a redraw is requested.
20
+ * 
21
+ * @see MarkerUtils#getMarkerForColor(java.awt.Color, java.awt.Color,
22
+ *      org.insa.graphics.drawing.Drawing.AlphaMode)
23
+ * @see PaintUtils#getStrokeWidth(int, byte)
24
+ */
25
+public class MarkerAutoScaling extends Marker {
26
+
27
+    // Original image.
28
+    private Image image;
29
+
30
+    /**
31
+     * Create a new MarkerAutoScaling at the given position with the given image.
32
+     * 
33
+     * @param latLong Initial position of the marker.
34
+     * @param image Image for this marker.
35
+     */
36
+    public MarkerAutoScaling(LatLong latLong, Image image) {
37
+        super(latLong, null, 0, 0);
38
+        this.image = image;
39
+    }
40
+
41
+    /**
42
+     * Set a new image for this marker overlay
43
+     * 
44
+     * @param image New image to set.
45
+     */
46
+    public void setImage(Image image) {
47
+        this.image = image;
48
+    }
49
+
50
+    /**
51
+     * @return Current image (marker) of this overlay.
52
+     */
53
+    public Image getImage() {
54
+        return image;
55
+    }
56
+
57
+    @Override
58
+    public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
59
+            Point topLeftPoint) {
60
+        int width = (int) PaintUtils.getStrokeWidth(8, zoomLevel),
61
+                height = (int) PaintUtils.getStrokeWidth(16, zoomLevel);
62
+        BufferedImage bfd = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
63
+        Graphics2D g = bfd.createGraphics();
64
+        g.drawImage(
65
+                this.image.getScaledInstance(bfd.getWidth(), bfd.getHeight(), Image.SCALE_SMOOTH),
66
+                0, 0, null);
67
+        setBitmap(new AwtBitmap(bfd));
68
+
69
+        setVerticalOffset(-height / 2);
70
+        super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
71
+        g.dispose();
72
+    }
73
+}

+ 19
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerOverlay.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import org.insa.graphs.model.Point;
4
+
5
+public interface MarkerOverlay extends Overlay {
6
+
7
+    /**
8
+     * @return The current position of this marker.
9
+     */
10
+    public Point getPoint();
11
+
12
+    /**
13
+     * Move this marker to the specified location.
14
+     * 
15
+     * @param point New position for the marker.
16
+     */
17
+    public void moveTo(Point point);
18
+
19
+}

+ 97
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/MarkerUtils.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Color;
4
+import java.awt.Image;
5
+import java.awt.image.BufferedImage;
6
+import java.io.DataInputStream;
7
+
8
+import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
9
+
10
+public class MarkerUtils {
11
+
12
+    /**
13
+     * Create an image to represent a marker using the given color for the outer and
14
+     * inner part, and the given mode for the inner part.
15
+     * 
16
+     * @param outer Outer color of the marker.
17
+     * @param inner Inner color of the marker.
18
+     * @param mode Mode to use to fill the inner part of the marker.
19
+     * 
20
+     * @return An image representing a marker.
21
+     */
22
+    public static Image getMarkerForColor(Color outer, Color inner, AlphaMode mode) {
23
+        // create image
24
+        int[][] mask = readMarkerMask();
25
+        BufferedImage image = new BufferedImage(mask[0].length, mask.length,
26
+                BufferedImage.TYPE_4BYTE_ABGR);
27
+
28
+        // Color[] map = getColorMapping(color);
29
+        int outerRGB = outer.getRGB() & 0x00ffffff;
30
+        for (int i = 0; i < image.getHeight(); ++i) {
31
+            for (int j = 0; j < image.getWidth(); ++j) {
32
+
33
+                // If we are in the "inner" part of the marker...
34
+                if (i >= MIN_Y_CENTER && i < MAX_Y_CENTER && j >= MIN_X_CENTER && j < MAX_X_CENTER
35
+                        && mask[i][j] != MAXIMUM_INNER_MASK_VALUE) {
36
+                    // Don't ask... https://stackoverflow.com/a/29321264/2666289
37
+                    // Basically, this compute a "middle" color between outer and inner depending on
38
+                    // the current mask value.
39
+                    double t = 1 - (mask[i][j] - MINIMUM_INNER_MASK_VALUE)
40
+                            / (double) (MAXIMUM_INNER_MASK_VALUE - MINIMUM_INNER_MASK_VALUE);
41
+                    int r = (int) Math.sqrt((1 - t) * outer.getRed() * outer.getRed()
42
+                            + t * inner.getRed() * inner.getRed());
43
+                    int g = (int) Math.sqrt((1 - t) * outer.getGreen() * outer.getGreen()
44
+                            + t * inner.getGreen() * inner.getGreen());
45
+                    int b = (int) Math.sqrt((1 - t) * outer.getBlue() * outer.getBlue()
46
+                            + t * inner.getBlue() * inner.getBlue());
47
+                    int a = mode == AlphaMode.OPAQUE ? MAXIMUM_INNER_MASK_VALUE : mask[i][j];
48
+                    image.setRGB(j, i, (a << 24) | (r << 16) | (g << 8) | b);
49
+                }
50
+                // Otherwize, just fill with the outer color and set the alpha value properly.
51
+                else {
52
+                    image.setRGB(j, i, outerRGB | (mask[i][j] << 24));
53
+                }
54
+            }
55
+        }
56
+
57
+        return image;
58
+    }
59
+
60
+    // Mask cache
61
+    private static int[][] MASK_CACHE = null;
62
+
63
+    // Hand-made... These defines the "center" of the marker, that can be filled
64
+    // with a different color.
65
+    private static final int MIN_X_CENTER = 40, MAX_X_CENTER = 101, MIN_Y_CENTER = 40,
66
+            MAX_Y_CENTER = 100;
67
+    private static final int MINIMUM_INNER_MASK_VALUE = 116, MAXIMUM_INNER_MASK_VALUE = 249;
68
+
69
+    /**
70
+     * @return Retrieve the mask from the mask file or from the cache.
71
+     */
72
+    private static int[][] readMarkerMask() {
73
+        if (MASK_CACHE == null) {
74
+            try {
75
+                DataInputStream dis = new DataInputStream(
76
+                        MarkerUtils.class.getResourceAsStream("/marker_mask.bin"));
77
+
78
+                int nrows = dis.readInt();
79
+                int ncols = dis.readInt();
80
+
81
+                MASK_CACHE = new int[nrows][ncols];
82
+                for (int i = 0; i < nrows; ++i) {
83
+                    for (int j = 0; j < ncols; ++j) {
84
+                        MASK_CACHE[i][j] = dis.readUnsignedByte();
85
+                    }
86
+                }
87
+                dis.close();
88
+            }
89
+            catch (Exception e) {
90
+                e.printStackTrace();
91
+                MASK_CACHE = null;
92
+            }
93
+        }
94
+        return MASK_CACHE;
95
+    }
96
+
97
+}

+ 42
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/Overlay.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Color;
4
+
5
+public interface Overlay {
6
+
7
+    /**
8
+     * Set the color of this overlay.
9
+     * 
10
+     * @param color New color for the overlay.
11
+     */
12
+    public void setColor(Color color);
13
+
14
+    /**
15
+     * @return The current color of this overlay.
16
+     */
17
+    public Color getColor();
18
+
19
+    /**
20
+     * Show or hide this marker - A marker should be visible when created.
21
+     * 
22
+     * @param visible true to show the marker, false to hide.
23
+     */
24
+    public void setVisible(boolean visible);
25
+
26
+    /**
27
+     * @return true if this overlay is visible.
28
+     */
29
+    public boolean isVisible();
30
+
31
+    /**
32
+     * Delete this marker.
33
+     */
34
+    public void delete();
35
+
36
+    /**
37
+     * Request a redraw of this overlay - This can start a full redraw of the inner
38
+     * drawing.
39
+     */
40
+    public void redraw();
41
+
42
+}

+ 65
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PaintUtils.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Color;
4
+
5
+import org.mapsforge.core.graphics.GraphicFactory;
6
+import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
7
+import org.mapsforge.map.layer.overlay.Marker;
8
+import org.mapsforge.map.layer.overlay.Polyline;
9
+
10
+public class PaintUtils {
11
+
12
+    // Graphic factory.
13
+    private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
14
+
15
+    /**
16
+     * Convert the given AWT color to a mapsforge compatible color.
17
+     * 
18
+     * @param color AWT color to convert.
19
+     * 
20
+     * @return Integer value representing a corresponding mapsforge color.
21
+     */
22
+    public static int convertColor(Color color) {
23
+        return GRAPHIC_FACTORY.createColor(color.getAlpha(), color.getRed(), color.getGreen(),
24
+                color.getBlue());
25
+    }
26
+
27
+    /**
28
+     * Convert the given mapsforge color to an AWT Color.
29
+     * 
30
+     * @param color Integer value representing a mapsforge color.
31
+     * 
32
+     * @return AWT color corresponding to the given value.
33
+     */
34
+    public static Color convertColor(int color) {
35
+        return new Color(color, true);
36
+    }
37
+
38
+    /**
39
+     * Compute an updated value for the given width at the given zoom level. This
40
+     * function can be used to automatically scale {@link Polyline} or
41
+     * {@link Marker} when zooming (which is not done by default in Mapsforge).
42
+     * 
43
+     * @param width Original width to convert.
44
+     * @param zoomLevel Zoom level for which the width should be computed.
45
+     * 
46
+     * @return Actual width at the given zoom level.
47
+     */
48
+    public static float getStrokeWidth(int width, byte zoomLevel) {
49
+        float mul = 1;
50
+        if (zoomLevel < 6) {
51
+            mul = 1;
52
+        }
53
+        else if (zoomLevel < 10) {
54
+            mul += (zoomLevel - 5) * 0.5;
55
+        }
56
+        else if (zoomLevel < 13) {
57
+            mul = 3.5f;
58
+        }
59
+        else {
60
+            mul += 2 * (zoomLevel - 8) / 3;
61
+        }
62
+        return width * mul;
63
+    }
64
+
65
+}

+ 5
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PathOverlay.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+public interface PathOverlay extends Overlay {
4
+
5
+}

+ 70
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PointSetOverlay.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.model.Point;
6
+
7
+public interface PointSetOverlay extends Overlay {
8
+
9
+    /**
10
+     * Set the width of this overlay for future addPoint().
11
+     * 
12
+     * @param width New default width for this overlay.
13
+     */
14
+    public void setWidth(int width);
15
+
16
+    /**
17
+     * Set color and width for this overlay for future addPoint().
18
+     * 
19
+     * @param width New default width for this overlay.
20
+     * @param color New default color for this overlay.
21
+     */
22
+    public void setWidthAndColor(int width, Color color);
23
+
24
+    /**
25
+     * Add a new point using the current width and color.
26
+     * 
27
+     * @param point Position of the point to add.
28
+     * 
29
+     * @see #setWidth(int)
30
+     * @see #setColor(Color)
31
+     */
32
+    public void addPoint(Point point);
33
+
34
+    /**
35
+     * Set the current width and then add a new point.
36
+     * 
37
+     * @param point Position of the point to add.
38
+     * @param width New default width for this overlay.
39
+     * 
40
+     * @see #setWidth(int)
41
+     * @see PointSetOverlay#addPoint(Point)
42
+     */
43
+    public void addPoint(Point point, int width);
44
+
45
+    /**
46
+     * Set the current color and then add a new point.
47
+     * 
48
+     * @param point Position of the point to add.
49
+     * @param color New default color for this overlay.
50
+     * 
51
+     * @see #setColor(Color)
52
+     * @see PointSetOverlay#addPoint(Point)
53
+     */
54
+    public void addPoint(Point point, Color color);
55
+
56
+    /**
57
+     * Add a new point at the given location, with the given color and width, and
58
+     * update the current width and color.
59
+     * 
60
+     * @param point Position of the point to add.
61
+     * @param width New default width for this overlay.
62
+     * @param color New default color for this overlay.
63
+     * 
64
+     * @see #setWidth(int)
65
+     * @see #setColor(Color)
66
+     * @see PointSetOverlay#addPoint(Point)
67
+     */
68
+    public void addPoint(Point point, int width, Color color);
69
+
70
+}

+ 92
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/drawing/overlays/PolylineAutoScaling.java View File

1
+package org.insa.graphs.gui.drawing.overlays;
2
+
3
+import java.awt.Color;
4
+import java.util.ArrayList;
5
+import java.util.Collection;
6
+
7
+import org.insa.graphs.model.Point;
8
+import org.mapsforge.core.graphics.Canvas;
9
+import org.mapsforge.core.graphics.GraphicFactory;
10
+import org.mapsforge.core.graphics.Style;
11
+import org.mapsforge.core.model.BoundingBox;
12
+import org.mapsforge.core.model.LatLong;
13
+import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
14
+import org.mapsforge.map.layer.overlay.Polyline;
15
+
16
+/**
17
+ * Class extending the default Mapsforge's {@link Polyline} with auto-scaling.
18
+ * 
19
+ * Mapsforge's Polylines do not scale with zoom level, this class aims at
20
+ * correcting this. When a redraw is requested, the width of the line is
21
+ * recomputed for the current zoom level.
22
+ * 
23
+ * @see PaintUtils#getStrokeWidth(int, byte)
24
+ */
25
+public class PolylineAutoScaling extends Polyline {
26
+
27
+    // Graphic factory.
28
+    private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
29
+
30
+    // Original width of the polyline.
31
+    private final int width;
32
+
33
+    /**
34
+     * Create a new PolylineAutoScaling with the given width and color.
35
+     * 
36
+     * @param width Original width of the line (independent of the zoom level).
37
+     * @param color Color of the line.
38
+     * 
39
+     * @see PaintUtils#getStrokeWidth(int, byte)
40
+     */
41
+    public PolylineAutoScaling(int width, Color color) {
42
+        super(GRAPHIC_FACTORY.createPaint(), GRAPHIC_FACTORY);
43
+        getPaintStroke().setColor(PaintUtils.convertColor(color));
44
+        getPaintStroke().setStyle(Style.STROKE);
45
+        this.width = width;
46
+    }
47
+
48
+    /**
49
+     * Set the color for this polyline.
50
+     * 
51
+     * @param color New color for this polyline.
52
+     */
53
+    public void setColor(Color color) {
54
+        getPaintStroke().setColor(PaintUtils.convertColor(color));
55
+    }
56
+
57
+    /**
58
+     * @return Color of this polyline.
59
+     */
60
+    public Color getColor() {
61
+        return PaintUtils.convertColor(getPaintStroke().getColor());
62
+    }
63
+
64
+    /**
65
+     * @param point Point to add to this line.
66
+     */
67
+    public void add(Point point) {
68
+        getLatLongs().add(new LatLong(point.getLatitude(), point.getLongitude()));
69
+    }
70
+
71
+    /**
72
+     * @param points Points to add to this line.
73
+     */
74
+    public void addAll(Collection<? extends Point> points) {
75
+        ArrayList<LatLong> latlongs = new ArrayList<>(points.size());
76
+        for (Point point: points) {
77
+            latlongs.add(new LatLong(point.getLatitude(), point.getLongitude()));
78
+        }
79
+        getLatLongs().addAll(latlongs);
80
+    }
81
+
82
+    @Override
83
+    public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
84
+            org.mapsforge.core.model.Point topLeftPoint) {
85
+
86
+        // Update paint stroke with width for level
87
+        this.getPaintStroke().setStrokeWidth(PaintUtils.getStrokeWidth(width, zoomLevel));
88
+
89
+        super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
90
+    }
91
+
92
+}

+ 42
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/observers/ShortestPathGraphicObserver.java View File

1
+package org.insa.graphs.gui.observers;
2
+
3
+import java.awt.Color;
4
+
5
+import org.insa.graphs.algorithm.shortestpath.ShortestPathObserver;
6
+import org.insa.graphs.gui.drawing.Drawing;
7
+import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
8
+import org.insa.graphs.model.Node;
9
+
10
+public class ShortestPathGraphicObserver implements ShortestPathObserver {
11
+
12
+    // Drawing and Graph drawing
13
+    protected Drawing drawing;
14
+    protected PointSetOverlay psOverlay1, psOverlay2;
15
+
16
+    public ShortestPathGraphicObserver(Drawing drawing) {
17
+        this.drawing = drawing;
18
+        psOverlay1 = drawing.createPointSetOverlay(1, Color.CYAN);
19
+        psOverlay2 = drawing.createPointSetOverlay(1, Color.BLUE);
20
+    }
21
+
22
+    @Override
23
+    public void notifyOriginProcessed(Node node) {
24
+        // drawing.drawMarker(node.getPoint(), Color.RED);
25
+    }
26
+
27
+    @Override
28
+    public void notifyNodeReached(Node node) {
29
+        psOverlay1.addPoint(node.getPoint());
30
+    }
31
+
32
+    @Override
33
+    public void notifyNodeMarked(Node node) {
34
+        psOverlay2.addPoint(node.getPoint());
35
+    }
36
+
37
+    @Override
38
+    public void notifyDestinationReached(Node node) {
39
+        // drawing.drawMarker(node.getPoint(), Color.RED);
40
+    }
41
+
42
+}

+ 42
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/observers/WeaklyConnectedComponentGraphicObserver.java View File

1
+package org.insa.graphs.gui.observers;
2
+
3
+import java.awt.Color;
4
+import java.util.ArrayList;
5
+
6
+import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentObserver;
7
+import org.insa.graphs.gui.drawing.Drawing;
8
+import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
9
+import org.insa.graphs.model.Node;
10
+
11
+public class WeaklyConnectedComponentGraphicObserver implements WeaklyConnectedComponentObserver {
12
+
13
+    private static final Color[] COLORS = { Color.BLUE, Color.ORANGE, Color.GREEN, Color.YELLOW,
14
+            Color.RED };
15
+
16
+    // Drawing + Graph drawing
17
+    private PointSetOverlay grPoints;
18
+
19
+    // Current index color
20
+    private int cindex = 0;
21
+
22
+    public WeaklyConnectedComponentGraphicObserver(Drawing drawing) {
23
+        this.grPoints = drawing.createPointSetOverlay();
24
+        this.grPoints.setWidth(1);
25
+    }
26
+
27
+    @Override
28
+    public void notifyStartComponent(Node curNode) {
29
+        this.grPoints.setColor(COLORS[cindex]);
30
+    }
31
+
32
+    @Override
33
+    public void notifyNewNodeInComponent(Node node) {
34
+        this.grPoints.addPoint(node.getPoint());
35
+    }
36
+
37
+    @Override
38
+    public void notifyEndComponent(ArrayList<Node> nodes) {
39
+        cindex = (cindex + 1) % COLORS.length;
40
+    }
41
+
42
+}

+ 78
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/simple/Launch.java View File

1
+package org.insa.graphs.gui.simple;
2
+
3
+import java.awt.BorderLayout;
4
+import java.awt.Dimension;
5
+import java.io.BufferedInputStream;
6
+import java.io.DataInputStream;
7
+import java.io.FileInputStream;
8
+
9
+import javax.swing.JFrame;
10
+import javax.swing.SwingUtilities;
11
+
12
+import org.insa.graphs.gui.drawing.Drawing;
13
+import org.insa.graphs.gui.drawing.components.BasicDrawing;
14
+import org.insa.graphs.model.Graph;
15
+import org.insa.graphs.model.Path;
16
+import org.insa.graphs.model.io.BinaryGraphReader;
17
+import org.insa.graphs.model.io.BinaryPathReader;
18
+import org.insa.graphs.model.io.GraphReader;
19
+import org.insa.graphs.model.io.PathReader;
20
+
21
+
22
+public class Launch {
23
+
24
+    /**
25
+     * Create a new Drawing inside a JFrame an return it.
26
+     * 
27
+     * @return The created drawing.
28
+     * 
29
+     * @throws Exception if something wrong happens when creating the graph.
30
+     */
31
+    public static Drawing createDrawing() throws Exception {
32
+        BasicDrawing basicDrawing = new BasicDrawing();
33
+        SwingUtilities.invokeAndWait(new Runnable() {
34
+            @Override
35
+            public void run() {
36
+                JFrame frame = new JFrame("BE Graphes - Launch");
37
+                frame.setLayout(new BorderLayout());
38
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
39
+                frame.setVisible(true);
40
+                frame.setSize(new Dimension(800, 600));
41
+                frame.setContentPane(basicDrawing);
42
+                frame.validate();
43
+            }
44
+        });
45
+        return basicDrawing;
46
+    }
47
+
48
+    public static void main(String[] args) throws Exception {
49
+
50
+        // Visit these directory to see the list of available files on Commetud.
51
+        final String mapName = "./Maps/haute-garonne.mapgr";
52
+        final String pathName = "./Paths/path_fr31_insa_aeroport_length.path";
53
+
54
+        // Create a graph reader.
55
+        final GraphReader reader = new BinaryGraphReader(
56
+                new DataInputStream(new BufferedInputStream(new FileInputStream(mapName))));
57
+
58
+        // DONE: Read the graph.
59
+        final Graph graph = reader.read();
60
+
61
+        // Create the drawing:
62
+        final Drawing drawing = createDrawing();
63
+
64
+        // DONE: Draw the graph on the drawing.
65
+        drawing.drawGraph(graph);
66
+
67
+        // DONE: Create a PathReader.
68
+        final PathReader pathReader = new BinaryPathReader(
69
+                new DataInputStream(new BufferedInputStream(new FileInputStream(pathName))));
70
+
71
+        // DONE: Read the path.
72
+        final Path path = pathReader.readPath(graph);
73
+
74
+        // DONE: Draw the path.
75
+        drawing.drawPath(path);
76
+    }
77
+
78
+}

+ 21
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/utils/ColorUtils.java View File

1
+package org.insa.graphs.gui.utils;
2
+
3
+import java.awt.Color;
4
+
5
+public class ColorUtils {
6
+
7
+    private static final Color[] COLORS = { // List of available colors
8
+            new Color(57, 172, 115), // Forest (Green)
9
+            new Color(246, 67, 63), // Red
10
+            new Color(110, 56, 172), // Purple
11
+            new Color(53, 191, 179), // Cyan
12
+            new Color(219, 136, 48), // Orange / Brown
13
+            new Color(110, 110, 110), // Gray
14
+            new Color(56, 104, 172) // Blue
15
+    };
16
+
17
+    public static Color getColor(int i) {
18
+        return COLORS[i % COLORS.length];
19
+    }
20
+
21
+}

+ 151
- 0
be-graphes-gui/src/main/java/org/insa/graphs/gui/utils/FileUtils.java View File

1
+package org.insa.graphs.gui.utils;
2
+
3
+import java.awt.event.ActionEvent;
4
+import java.awt.event.ActionListener;
5
+import java.io.File;
6
+import java.util.EnumMap;
7
+import java.util.Map;
8
+import java.util.prefs.Preferences;
9
+
10
+import javax.swing.JFileChooser;
11
+import javax.swing.filechooser.FileFilter;
12
+import javax.swing.filechooser.FileNameExtensionFilter;
13
+
14
+public class FileUtils {
15
+
16
+    // Preferences
17
+    private static Preferences preferences = Preferences.userRoot().node(FileUtils.class.getName());
18
+
19
+    /**
20
+     * Type of folder with associated preferred folder and path filters.
21
+     *
22
+     */
23
+    public enum FolderType {
24
+
25
+        /**
26
+         * Folder type for graph files input (*.mapgr).
27
+         */
28
+        Map,
29
+
30
+        /**
31
+         * Folder type for path inputs (*.path).
32
+         */
33
+        PathInput,
34
+
35
+        /**
36
+         * Folder type for path outputs (*.path).
37
+         */
38
+        PathOutput
39
+    }
40
+
41
+    private static class PreferencesEntry {
42
+        public String key;
43
+        public String value;
44
+
45
+        public PreferencesEntry(String key, String value) {
46
+            this.key = key;
47
+            this.value = value;
48
+        }
49
+    }
50
+
51
+    // Map folder type -> PreferencesEntry
52
+    private static final Map<FolderType, PreferencesEntry> folderToEntry = new EnumMap<>(
53
+            FolderType.class);
54
+
55
+    // Map folder type -> File Filter
56
+    private static final Map<FolderType, FileFilter> folderToFilter = new EnumMap<>(
57
+            FolderType.class);
58
+
59
+    static {
60
+        // Populate folderToEntry
61
+        folderToEntry.put(FolderType.Map, new PreferencesEntry("DefaultMapFolder",
62
+                "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps"));
63
+        folderToEntry.put(FolderType.PathInput, new PreferencesEntry("DefaultPathInputFolder",
64
+                "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Paths"));
65
+        folderToEntry.put(FolderType.PathOutput,
66
+                new PreferencesEntry("DefaultPathOutputsFolder", "paths"));
67
+
68
+        // Populate folderToFilter
69
+        folderToFilter.put(FolderType.Map, new FileNameExtensionFilter("Graph files", "mapgr"));
70
+        folderToFilter.put(FolderType.PathInput, new FileNameExtensionFilter("Path files", "path"));
71
+        folderToFilter.put(FolderType.PathOutput,
72
+                new FileNameExtensionFilter("Path files", "path"));
73
+    }
74
+
75
+    /**
76
+     * @param folderType Type of folder to retrieve.
77
+     * 
78
+     * @return A File instance pointing to the preferred folder for the given type.
79
+     * 
80
+     * @see FolderType
81
+     */
82
+    public static File getPreferredFolder(FolderType folderType) {
83
+        PreferencesEntry entry = folderToEntry.get(folderType);
84
+        File folder = new File(preferences.get(entry.key, entry.value));
85
+        if (!folder.exists()) {
86
+            folder = new File(System.getProperty("user.dir"));
87
+        }
88
+        return folder;
89
+    }
90
+
91
+    /**
92
+     * @param folderType Type of folder to update.
93
+     * @param newPreferredFolder New preferred folder.
94
+     */
95
+    public static void updatePreferredFolder(FolderType folderType, File newPreferredFolder) {
96
+        PreferencesEntry entry = folderToEntry.get(folderType);
97
+        preferences.put(entry.key, newPreferredFolder.getAbsolutePath());
98
+    }
99
+
100
+    /**
101
+     * @param folderType Type of folder for which the filter should be retrieved.
102
+     * 
103
+     * @return A FileFilter corresponding to input graph files.
104
+     */
105
+    public static FileFilter getFileFilter(FolderType folderType) {
106
+        return folderToFilter.get(folderType);
107
+    }
108
+
109
+    /**
110
+     * @param folderType Type of folder for which a file chooser should be created.
111
+     * @param defaultFileName Default file name to show, or null to not show any
112
+     *        file.
113
+     * 
114
+     * @return A new JFileChooser pointing to the preferred folder for the given
115
+     *         folderType, with the given default file selected (if given).
116
+     */
117
+    public static JFileChooser createFileChooser(FolderType folderType, String defaultFileName) {
118
+        JFileChooser chooser = new JFileChooser();
119
+        chooser.setCurrentDirectory(getPreferredFolder(folderType));
120
+        if (defaultFileName != null) {
121
+            chooser.setSelectedFile(new File(chooser.getCurrentDirectory().getAbsolutePath()
122
+                    + File.separator + defaultFileName));
123
+        }
124
+        chooser.setFileFilter(getFileFilter(folderType));
125
+        chooser.addActionListener(new ActionListener() {
126
+            @Override
127
+            public void actionPerformed(ActionEvent e) {
128
+                if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
129
+                    if (chooser.getSelectedFile().exists()) {
130
+                        updatePreferredFolder(folderType,
131
+                                chooser.getSelectedFile().getParentFile());
132
+                    }
133
+                }
134
+            }
135
+        });
136
+        return chooser;
137
+    }
138
+
139
+    /**
140
+     * @param folderType Type of folder for which a file chooser should be created.
141
+     * 
142
+     * @return A new JFileChooser pointing to the preferred folder for the given
143
+     *         folderType.
144
+     * 
145
+     * @see #createFileChooser(FolderType, String)
146
+     */
147
+    public static JFileChooser createFileChooser(FolderType folderType) {
148
+        return createFileChooser(folderType, null);
149
+    }
150
+
151
+}

BIN
be-graphes-gui/src/main/resources/delete-icon.png View File


BIN
be-graphes-gui/src/main/resources/marker_mask.bin View File


BIN
be-graphes-gui/src/main/resources/save-icon.png View File


BIN
be-graphes-gui/src/main/resources/zoomIn.png View File


BIN
be-graphes-gui/src/main/resources/zoomOut.png View File


+ 17
- 0
be-graphes-model/pom.xml View File

1
+<?xml version="1.0"?>
2
+<project
3
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4
+	xmlns="http://maven.apache.org/POM/4.0.0"
5
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
6
+	<modelVersion>4.0.0</modelVersion>
7
+	
8
+	<parent>
9
+		<groupId>org.insa.graphs</groupId>
10
+		<artifactId>be-graphes-all</artifactId>
11
+		<version>0.0.1-SNAPSHOT</version>
12
+	</parent>
13
+	
14
+	<artifactId>be-graphes-model</artifactId>
15
+	<name>be-graphes-model</name>
16
+	
17
+</project>

+ 236
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/AccessRestrictions.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.EnumMap;
4
+import java.util.EnumSet;
5
+
6
+/**
7
+ * <p>
8
+ * Class containing access restrictions for roads/arcs.
9
+ * </p>
10
+ * 
11
+ * <p>
12
+ * This class maps transport modes to their restriction and provide interface
13
+ * based on EnumSet to query restrictions.
14
+ * </p>
15
+ * 
16
+ * <p>
17
+ * To each transport is associated at most one restriction per road (no
18
+ * restriction corresponds to {@link AccessRestriction#UNKNOWN} but a road can
19
+ * have different restrictions for different modes.
20
+ * </p>
21
+ *
22
+ */
23
+public class AccessRestrictions {
24
+
25
+    /**
26
+     * Enumeration representing the available transport modes.
27
+     *
28
+     * @see <a href=
29
+     *      "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
30
+     *      reference for access modes.</a>
31
+     */
32
+    public enum AccessMode {
33
+
34
+        /**
35
+         * Access mode corresponding to pedestrians.
36
+         */
37
+        FOOT,
38
+
39
+        /**
40
+         * Access mode corresponding to bicycles (non-motorized).
41
+         */
42
+        BICYCLE,
43
+
44
+        /**
45
+         * Access mode corresponding to small motorcycles (limited speed).
46
+         */
47
+        SMALL_MOTORCYCLE,
48
+
49
+        /**
50
+         * Access mode corresponding to agricultural vehicles.
51
+         */
52
+        AGRICULTURAL,
53
+
54
+        /**
55
+         * Access mode corresponding to motorcycles.
56
+         */
57
+        MOTORCYCLE,
58
+
59
+        /**
60
+         * Access mode corresponding to motorcars.
61
+         */
62
+        MOTORCAR,
63
+
64
+        /**
65
+         * Access mode corresponding to heavy transportation vehicles.
66
+         */
67
+        HEAVY_GOODS,
68
+
69
+        /**
70
+         * Access mode corresponding to public transport vehicles.
71
+         */
72
+        PUBLIC_TRANSPORT;
73
+
74
+        /**
75
+         * {@code EnumSet} containing all possible transport modes.
76
+         * 
77
+         * 
78
+         */
79
+        public static final EnumSet<AccessMode> ALL = EnumSet.allOf(AccessMode.class);
80
+
81
+        /**
82
+         * {@code EnumSet} containing all vehicle transport modes.
83
+         * 
84
+         */
85
+        public static final EnumSet<AccessMode> VEHICLE = EnumSet.range(AccessMode.BICYCLE,
86
+                AccessMode.PUBLIC_TRANSPORT);
87
+
88
+        /**
89
+         * {@code EnumSet} containing all motorized vehicle transport modes.
90
+         * 
91
+         */
92
+        public static final EnumSet<AccessMode> MOTOR_VEHICLE = EnumSet
93
+                .range(AccessMode.SMALL_MOTORCYCLE, AccessMode.PUBLIC_TRANSPORT);
94
+    }
95
+
96
+    /**
97
+     * Possible restrictions for the roads/arcs.
98
+     *
99
+     * @see <a href=
100
+     *      "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
101
+     *      reference for access restrictions.</a>
102
+     */
103
+    public enum AccessRestriction {
104
+
105
+        /**
106
+         * 
107
+         */
108
+        ALLOWED,
109
+
110
+        /**
111
+         * 
112
+         */
113
+        FORBIDDEN,
114
+
115
+        /**
116
+         * 
117
+         */
118
+        PRIVATE,
119
+
120
+        /**
121
+         * 
122
+         */
123
+        DESTINATION,
124
+
125
+        /**
126
+         * 
127
+         */
128
+        DELIVERY,
129
+
130
+        /**
131
+         * 
132
+         */
133
+        CUSTOMERS,
134
+
135
+        /**
136
+         * 
137
+         */
138
+        FORESTRY,
139
+
140
+        /**
141
+         * 
142
+         */
143
+        UNKNOWN;
144
+
145
+        /**
146
+         * {@code EnumSet} corresponding to restrictions that are not totally private.
147
+         * 
148
+         */
149
+        public static final EnumSet<AccessRestriction> ALLOWED_FOR_SOMETHING = EnumSet.of(
150
+                AccessRestriction.ALLOWED, AccessRestriction.DESTINATION,
151
+                AccessRestriction.DESTINATION, AccessRestriction.DELIVERY,
152
+                AccessRestriction.CUSTOMERS, AccessRestriction.FORESTRY);
153
+
154
+    }
155
+
156
+    // Map mode -> restriction
157
+    private final EnumMap<AccessMode, AccessRestriction> restrictions;
158
+
159
+    /**
160
+     * Create new AccessRestrictions instances with unknown restrictions.
161
+     */
162
+    public AccessRestrictions() {
163
+        this.restrictions = new EnumMap<>(AccessMode.class);
164
+        for (AccessMode mode: AccessMode.values()) {
165
+            this.restrictions.put(mode, AccessRestriction.UNKNOWN);
166
+        }
167
+    }
168
+
169
+    /**
170
+     * Create a new AccessRestrictions instances with the given restrictions.
171
+     * 
172
+     * @param restrictions Map of restrictions for this instance of
173
+     *        AccessRestrictions.
174
+     */
175
+    public AccessRestrictions(EnumMap<AccessMode, AccessRestriction> restrictions) {
176
+        this.restrictions = restrictions;
177
+    }
178
+
179
+    /**
180
+     * Retrieve the restriction corresponding to the given mode.
181
+     * 
182
+     * @param mode Mode for which the restriction should be retrieved.
183
+     * 
184
+     * @return Restriction for the given mode.
185
+     */
186
+    public AccessRestriction getRestrictionFor(AccessMode mode) {
187
+        return restrictions.getOrDefault(mode, AccessRestriction.UNKNOWN);
188
+    }
189
+
190
+    /**
191
+     * Check if the restriction associated with the given mode is one of the given
192
+     * restrictions.
193
+     * 
194
+     * @param mode Mode for which to check the restrictions.
195
+     * @param restrictions List of queried restrictions for the mode.
196
+     * 
197
+     * @return {@code true} if the restriction of the given mode is one of the given
198
+     *         restrictions.
199
+     */
200
+    public boolean isAllowedForAny(AccessMode mode, EnumSet<AccessRestriction> restrictions) {
201
+        return restrictions.contains(getRestrictionFor(mode));
202
+    }
203
+
204
+    /**
205
+     * Check if the restriction for the given mode corresponds to the given
206
+     * restriction.
207
+     * 
208
+     * @param mode Mode for which the restriction should be checked.
209
+     * @param restriction Restriction to check against.
210
+     * 
211
+     * @return {@code true} if the restriction of the given mode corresponds to the
212
+     *         given restriction.
213
+     */
214
+    public boolean isAllowedFor(AccessMode mode, AccessRestriction restriction) {
215
+        return getRestrictionFor(mode).equals(restriction);
216
+    }
217
+
218
+    /**
219
+     * Check if the restriction associated to each given mode is one of the
220
+     * restrictions. The restriction may not be the same for all modes.
221
+     * 
222
+     * @param modes Modes for which restrictions should be checked.
223
+     * @param restrictions Set of wanted restrictions for the modes.
224
+     * 
225
+     * @return {@code true} if all the given modes are allowed for any of the given
226
+     *         restrictions.
227
+     */
228
+    public boolean areAllAllowedForAny(EnumSet<AccessMode> modes,
229
+            EnumSet<AccessRestriction> restrictions) {
230
+        boolean allowed = true;
231
+        for (AccessMode mode: modes) {
232
+            allowed = allowed && isAllowedForAny(mode, restrictions);
233
+        }
234
+        return allowed;
235
+    }
236
+}

+ 70
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/Arc.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.List;
4
+
5
+/**
6
+ * <p>
7
+ * Interface representing an arc in the graph. {@code Arc} is an interface and
8
+ * not a class to allow us to represent two-ways roads in a memory efficient
9
+ * manner (without having to duplicate attributes).
10
+ * </p>
11
+ * 
12
+ * <p>
13
+ * Arc should never be created manually but always using the
14
+ * {@link Node#linkNodes(Node, Node, float, RoadInformation, java.util.ArrayList)}
15
+ * method to ensure proper instantiation of the {@link ArcForward} and
16
+ * {@link ArcBackward} classes.
17
+ * </p>
18
+ *
19
+ */
20
+public abstract class Arc {
21
+
22
+    /**
23
+     * @return Origin node of this arc.
24
+     */
25
+    public abstract Node getOrigin();
26
+
27
+    /**
28
+     * @return Destination node of this arc.
29
+     */
30
+    public abstract Node getDestination();
31
+
32
+    /**
33
+     * @return Length of this arc, in meters.
34
+     */
35
+    public abstract float getLength();
36
+
37
+    /**
38
+     * Compute the time required to travel this arc if moving at the given speed.
39
+     * 
40
+     * @param speed Speed to compute the travel time.
41
+     * 
42
+     * @return Time (in seconds) required to travel this arc at the given speed (in
43
+     *         kilometers-per-hour).
44
+     */
45
+    public double getTravelTime(double speed) {
46
+        return getLength() * 3600.0 / (speed * 1000.0);
47
+    }
48
+
49
+    /**
50
+     * Compute and return the minimum time required to travel this arc, or the time
51
+     * required to travel this arc at the maximum speed allowed.
52
+     * 
53
+     * @return Minimum time required to travel this arc, in seconds.
54
+     * 
55
+     * @see Arc#getTravelTime(double)
56
+     */
57
+    public double getMinimumTravelTime() {
58
+        return getTravelTime(getRoadInformation().getMaximumSpeed());
59
+    }
60
+
61
+    /**
62
+     * @return Road information for this arc.
63
+     */
64
+    public abstract RoadInformation getRoadInformation();
65
+
66
+    /**
67
+     * @return Points representing segments of this arc.
68
+     */
69
+    public abstract List<Point> getPoints();
70
+}

+ 55
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/ArcBackward.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.List;
6
+
7
+/**
8
+ * Implementation of Arc that represents a "backward" arc in a graph, i.e., an
9
+ * arc that is the reverse of another one. This arc only holds a reference to
10
+ * the original arc.
11
+ *
12
+ */
13
+class ArcBackward extends Arc {
14
+
15
+    // Original arc
16
+    private final Arc originalArc;
17
+
18
+    /**
19
+     * Create a new backward arc which corresponds to the reverse arc of the given
20
+     * arc.
21
+     * 
22
+     * @param originalArc Original forwarc arc corresponding to this backward arc.
23
+     */
24
+    protected ArcBackward(Arc originalArc) {
25
+        this.originalArc = originalArc;
26
+    }
27
+
28
+    @Override
29
+    public Node getOrigin() {
30
+        return this.originalArc.getDestination();
31
+    }
32
+
33
+    @Override
34
+    public Node getDestination() {
35
+        return this.originalArc.getOrigin();
36
+    }
37
+
38
+    @Override
39
+    public float getLength() {
40
+        return this.originalArc.getLength();
41
+    }
42
+
43
+    @Override
44
+    public RoadInformation getRoadInformation() {
45
+        return this.originalArc.getRoadInformation();
46
+    }
47
+
48
+    @Override
49
+    public List<Point> getPoints() {
50
+        List<Point> pts = new ArrayList<>(this.originalArc.getPoints());
51
+        Collections.reverse(pts);
52
+        return pts;
53
+    }
54
+
55
+}

+ 68
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/ArcForward.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.Collections;
4
+import java.util.List;
5
+
6
+/**
7
+ * Implementation of Arc that represents a "forward" arc in a graph, this is the
8
+ * arc implementation that stores data relative to the arc.
9
+ *
10
+ */
11
+class ArcForward extends Arc {
12
+
13
+    // Destination node.
14
+    private final Node origin, destination;
15
+
16
+    // Length of the road (in meters).
17
+    private final float length;
18
+
19
+    // Road information.
20
+    private final RoadInformation info;
21
+
22
+    // Segments.
23
+    private final List<Point> points;
24
+
25
+    /**
26
+     * Create a new ArcForward with the given attributes.
27
+     * 
28
+     * @param origin Origin of this arc.
29
+     * @param dest Destination of this arc.
30
+     * @param length Length of this arc (in meters).
31
+     * @param roadInformation Road information for this arc.
32
+     * @param points Points representing this arc.
33
+     */
34
+    protected ArcForward(Node origin, Node dest, float length, RoadInformation roadInformation,
35
+            List<Point> points) {
36
+        this.origin = origin;
37
+        this.destination = dest;
38
+        this.length = length;
39
+        this.info = roadInformation;
40
+        this.points = points;
41
+    }
42
+
43
+    @Override
44
+    public Node getOrigin() {
45
+        return origin;
46
+    }
47
+
48
+    @Override
49
+    public Node getDestination() {
50
+        return destination;
51
+    }
52
+
53
+    @Override
54
+    public float getLength() {
55
+        return length;
56
+    }
57
+
58
+    @Override
59
+    public RoadInformation getRoadInformation() {
60
+        return info;
61
+    }
62
+
63
+    @Override
64
+    public List<Point> getPoints() {
65
+        return Collections.unmodifiableList(points);
66
+    }
67
+
68
+}

+ 131
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/Graph.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.List;
6
+
7
+/**
8
+ * <p>
9
+ * Main graph class.
10
+ * </p>
11
+ * 
12
+ * <p>
13
+ * This class acts as a object-oriented <b>adjacency list</b> for a graph, i.e.,
14
+ * it holds a list of nodes and each node holds a list of its successors.
15
+ * </p>
16
+ *
17
+ */
18
+public final class Graph {
19
+
20
+    // Map identifier.
21
+    private final String mapId;
22
+
23
+    // Map name
24
+    private final String mapName;
25
+
26
+    // Nodes of the graph.
27
+    private final List<Node> nodes;
28
+
29
+    // Graph information of this graph.
30
+    private final GraphStatistics graphStatistics;
31
+
32
+    /**
33
+     * Create a new graph with the given ID, name, nodes and information.
34
+     * 
35
+     * @param mapId ID of the map corresponding to this graph.
36
+     * @param mapName Name of the map corresponding to this graph.
37
+     * @param nodes List of nodes for this graph.
38
+     * @param graphStatistics Information for this graph.
39
+     */
40
+    public Graph(String mapId, String mapName, List<Node> nodes, GraphStatistics graphStatistics) {
41
+        this.mapId = mapId;
42
+        this.mapName = mapName;
43
+        this.nodes = Collections.unmodifiableList(nodes);
44
+        this.graphStatistics = graphStatistics;
45
+    }
46
+
47
+    /**
48
+     * @return The GraphStatistics instance associated with this graph.
49
+     */
50
+    public GraphStatistics getGraphInformation() {
51
+        return this.graphStatistics;
52
+    }
53
+
54
+    /**
55
+     * Fetch the node with the given ID.
56
+     * 
57
+     * Complexity: O(1).
58
+     * 
59
+     * @param id ID of the node to fetch.
60
+     * 
61
+     * @return Node with the given ID.
62
+     */
63
+    public Node get(int id) {
64
+        return this.nodes.get(id);
65
+    }
66
+
67
+    /**
68
+     * @return Number of nodes in this graph.
69
+     */
70
+    public int size() {
71
+        return this.nodes.size();
72
+    }
73
+
74
+    /**
75
+     * @return List of nodes in this graph (unmodifiable).
76
+     * 
77
+     * @see Collections#unmodifiableList(List)
78
+     */
79
+    public List<Node> getNodes() {
80
+        return this.nodes;
81
+    }
82
+
83
+    /**
84
+     * @return ID of the map associated with this graph.
85
+     */
86
+    public String getMapId() {
87
+        return mapId;
88
+    }
89
+
90
+    /**
91
+     * @return Name of the map associated with this graph.
92
+     */
93
+    public String getMapName() {
94
+        return mapName;
95
+    }
96
+
97
+    /**
98
+     * @return Transpose graph of this graph.
99
+     */
100
+    public Graph transpose() {
101
+        ArrayList<Node> trNodes = new ArrayList<>(nodes.size());
102
+        for (Node node: nodes) {
103
+            trNodes.add(new Node(node.getId(), node.getPoint()));
104
+        }
105
+        for (Node node: nodes) {
106
+            Node orig = trNodes.get(node.getId());
107
+            for (Arc arc: node.getSuccessors()) {
108
+                if (arc.getRoadInformation().isOneWay()) {
109
+                    Node dest = trNodes.get(arc.getDestination().getId());
110
+                    dest.addSuccessor(new ArcBackward(new ArcForward(orig, dest, arc.getLength(),
111
+                            arc.getRoadInformation(), arc.getPoints())));
112
+                }
113
+                else if (arc instanceof ArcForward) {
114
+                    Node dest = trNodes.get(arc.getDestination().getId());
115
+                    Arc newArc = new ArcForward(orig, dest, arc.getLength(),
116
+                            arc.getRoadInformation(), arc.getPoints());
117
+                    dest.addSuccessor(new ArcBackward(newArc));
118
+                    orig.addSuccessor(newArc);
119
+                }
120
+            }
121
+        }
122
+        return new Graph("R/" + mapId, mapName, trNodes, graphStatistics);
123
+    }
124
+
125
+    @Override
126
+    public String toString() {
127
+        return String.format("%s[id=%s, name=%s, #nodes=%d]", getClass().getCanonicalName(),
128
+                getMapId(), getMapName(), size());
129
+    }
130
+
131
+}

+ 204
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/GraphStatistics.java View File

1
+package org.insa.graphs.model;
2
+
3
+/**
4
+ * <p>
5
+ * Utility class that stores some statistics of graphs that are not easy to
6
+ * access.
7
+ * </p>
8
+ * 
9
+ * <p>
10
+ * This class is used to provide constant ({@code O(1)}) access to information
11
+ * in graph that do not change, and that usually require linear complexity to
12
+ * compute.
13
+ * </p>
14
+ *
15
+ */
16
+public class GraphStatistics {
17
+
18
+    /**
19
+     * Special value used to indicate that the graph has no maximum speed limit
20
+     * (some roads are not limited).
21
+     * 
22
+     */
23
+    public static final int NO_MAXIMUM_SPEED = -1;
24
+
25
+    /**
26
+     * Class representing a bounding box for a graph (a rectangle that contains all
27
+     * nodes in the graph).
28
+     *
29
+     */
30
+    public static class BoundingBox {
31
+
32
+        private final Point topLeft, bottomRight;
33
+
34
+        /**
35
+         * Create a new BoundingBox represented by the given top-left and bottom-right
36
+         * points.
37
+         * 
38
+         * @param topLeft Top left corner of the bounding box.
39
+         * @param bottomRight Bottom right corner of the bounding box.
40
+         */
41
+        public BoundingBox(Point topLeft, Point bottomRight) {
42
+            this.topLeft = topLeft;
43
+            this.bottomRight = bottomRight;
44
+        }
45
+
46
+        /**
47
+         * @return Bottom-right point of this bounding box.
48
+         */
49
+        public Point getBottomRightPoint() {
50
+            return bottomRight;
51
+        }
52
+
53
+        /**
54
+         * @return Top-left point of this bounding box.
55
+         */
56
+        public Point getTopLeftPoint() {
57
+            return topLeft;
58
+        }
59
+
60
+        /**
61
+         * Create a new bounding box by extending the current one according to the given
62
+         * value for each side.
63
+         * 
64
+         * @param left Extra size to add to the left of the box.
65
+         * @param top Extra size to add to the top of the box.
66
+         * @param right Extra size to add to the right of the box.
67
+         * @param bottom Extra size to add to the bottom of the box.
68
+         * 
69
+         * @return New bounding box corresponding to an extension of the current one.
70
+         */
71
+        public BoundingBox extend(float left, float top, float right, float bottom) {
72
+            return new BoundingBox(
73
+                    new Point(this.topLeft.getLongitude() - left, this.topLeft.getLatitude() + top),
74
+                    new Point(this.bottomRight.getLongitude() + right,
75
+                            this.bottomRight.getLatitude() - bottom));
76
+        }
77
+
78
+        /**
79
+         * Create a new bounding box by extending the current one according by the given
80
+         * value on each side.
81
+         * 
82
+         * @param size Extra size to add to each side of this box.
83
+         * 
84
+         * @return New bounding box corresponding to an extension of the current one.
85
+         */
86
+        public BoundingBox extend(float size) {
87
+            return this.extend(size, size, size, size);
88
+        }
89
+
90
+        /**
91
+         * @param point Point to check
92
+         * 
93
+         * @return true if this box contains the given point.
94
+         */
95
+        public boolean contains(Point point) {
96
+            return this.bottomRight.getLatitude() <= point.getLatitude()
97
+                    && this.topLeft.getLatitude() >= point.getLatitude()
98
+                    && this.topLeft.getLongitude() <= point.getLongitude()
99
+                    && this.bottomRight.getLongitude() >= point.getLongitude();
100
+        }
101
+
102
+        /**
103
+         * @param other Box to intersect.
104
+         * 
105
+         * @return true if this box contains the given box.
106
+         */
107
+        public boolean contains(BoundingBox other) {
108
+            return this.contains(other.bottomRight) && this.contains(other.topLeft);
109
+        }
110
+
111
+        @Override
112
+        public String toString() {
113
+            return "BoundingBox(topLeft=" + this.topLeft + ", bottomRight=" + this.bottomRight
114
+                    + ")";
115
+        }
116
+
117
+    }
118
+
119
+    // Bounding box for this graph.
120
+    private final BoundingBox boundingBox;
121
+
122
+    // Number of roads
123
+    private final int nbRoadOneWay, nbRoadTwoWays;
124
+
125
+    // Maximum speed on this graph (in kmph).
126
+    private final int maximumSpeed;
127
+
128
+    // Maximum length of any arc on this graph.
129
+    private final float maximumLength;
130
+
131
+    /**
132
+     * Create a new GraphStatistics instance with the given value.
133
+     * 
134
+     * @param boundingBox Bounding-box for the graph.
135
+     * @param nbRoadOneWay Number of one-way roads in the graph.
136
+     * @param nbRoadTwoWays Number of two-ways roads in the graph.
137
+     * @param maximumSpeed Maximum speed of any road of the graph. You can use
138
+     *        {@link #NO_MAXIMUM_SPEED} to indicate that the graph has no maximum
139
+     *        speed limit.
140
+     * @param maximumLength Maximum length of any arc of the graph.
141
+     */
142
+    public GraphStatistics(BoundingBox boundingBox, int nbRoadOneWay, int nbRoadTwoWays,
143
+            int maximumSpeed, float maximumLength) {
144
+        this.boundingBox = boundingBox;
145
+        this.nbRoadOneWay = nbRoadOneWay;
146
+        this.nbRoadTwoWays = nbRoadTwoWays;
147
+        this.maximumLength = maximumLength;
148
+        this.maximumSpeed = maximumSpeed;
149
+    }
150
+
151
+    /**
152
+     * @return The bounding box for this graph.
153
+     */
154
+    public BoundingBox getBoundingBox() {
155
+        return this.boundingBox;
156
+    }
157
+
158
+    /**
159
+     * @return Amount of one-way roads in this graph.
160
+     */
161
+    public int getOneWayRoadCount() {
162
+        return this.nbRoadOneWay;
163
+    }
164
+
165
+    /**
166
+     * @return Amount of two-ways roads in this graph.
167
+     */
168
+    public int getTwoWaysRoadCount() {
169
+        return this.nbRoadTwoWays;
170
+    }
171
+
172
+    /**
173
+     * @return Number of arcs in this graph.
174
+     * 
175
+     * @see #getOneWayRoadCount()
176
+     * @see #getTwoWaysRoadCount()
177
+     */
178
+    public int getArcCount() {
179
+        return getOneWayRoadCount() + 2 * getTwoWaysRoadCount();
180
+    }
181
+
182
+    /**
183
+     * @return true if this graph has a maximum speed limit, false otherwise.
184
+     */
185
+    public boolean hasMaximumSpeed() {
186
+        return this.maximumLength != NO_MAXIMUM_SPEED;
187
+    }
188
+
189
+    /**
190
+     * @return Maximum speed of any arc in the graph, or {@link #NO_MAXIMUM_SPEED}
191
+     *         if some roads have no speed limitation.
192
+     */
193
+    public int getMaximumSpeed() {
194
+        return this.maximumSpeed;
195
+    }
196
+
197
+    /**
198
+     * @return Maximum length of any arc in the graph.
199
+     */
200
+    public float getMaximumLength() {
201
+        return this.maximumLength;
202
+    }
203
+
204
+}

+ 159
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/Node.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.List;
6
+
7
+/**
8
+ * <p>
9
+ * Class representing a Node in a {@link Graph}.
10
+ * </p>
11
+ * 
12
+ * <p>
13
+ * This class holds information regarding nodes in the graph together with the
14
+ * successors associated to the nodes.
15
+ * </p>
16
+ * 
17
+ * <p>
18
+ * Nodes are comparable based on their ID.
19
+ * </p>
20
+ *
21
+ */
22
+public final class Node implements Comparable<Node> {
23
+
24
+    /**
25
+     * <p>
26
+     * Link the two given nodes with one or two arcs (depending on roadInformation),
27
+     * with the given attributes.
28
+     * </p>
29
+     * 
30
+     * <p>
31
+     * If {@code roadInformation.isOneWay()} is {@code true}, only a forward arc is
32
+     * created (origin to destination) and added to origin. Otherwise, a
33
+     * corresponding backward arc is created and add to destination.
34
+     * </p>
35
+     * 
36
+     * @param origin Origin of the arc.
37
+     * @param destination Destination of the arc.
38
+     * @param length Length of the arc.
39
+     * @param roadInformation Information corresponding to the arc.
40
+     * @param points Points for the arc.
41
+     * 
42
+     * @return The newly created forward arc (origin to destination).
43
+     */
44
+    public static Arc linkNodes(Node origin, Node destination, float length,
45
+            RoadInformation roadInformation, ArrayList<Point> points) {
46
+        Arc arc = null;
47
+        if (roadInformation.isOneWay()) {
48
+            arc = new ArcForward(origin, destination, length, roadInformation, points);
49
+            origin.addSuccessor(arc);
50
+        }
51
+        else {
52
+            Arc d2o;
53
+            if (origin.getId() < destination.getId()) {
54
+                arc = new ArcForward(origin, destination, length, roadInformation, points);
55
+                d2o = new ArcBackward(arc);
56
+            }
57
+            else {
58
+                Collections.reverse(points);
59
+                d2o = new ArcForward(destination, origin, length, roadInformation, points);
60
+                arc = new ArcBackward(d2o);
61
+            }
62
+            origin.addSuccessor(arc);
63
+            destination.addSuccessor(d2o);
64
+        }
65
+        return arc;
66
+    }
67
+
68
+    // ID of the node.
69
+    private final int id;
70
+
71
+    // Point of this graph.
72
+    private final Point point;
73
+
74
+    // Successors.
75
+    private final ArrayList<Arc> successors;
76
+
77
+    /**
78
+     * Create a new Node with the given ID corresponding to the given Point with an
79
+     * empty list of successors.
80
+     * 
81
+     * @param id ID of the node.
82
+     * @param point Position of the node.
83
+     */
84
+    public Node(int id, Point point) {
85
+        this.id = id;
86
+        this.point = point;
87
+        this.successors = new ArrayList<Arc>();
88
+    }
89
+
90
+    /**
91
+     * Add a successor to this node.
92
+     * 
93
+     * @param arc Arc to the successor.
94
+     */
95
+    protected void addSuccessor(Arc arc) {
96
+        successors.add(arc);
97
+    }
98
+
99
+    /**
100
+     * @return ID of this node.
101
+     */
102
+    public int getId() {
103
+        return id;
104
+    }
105
+
106
+    /**
107
+     * @return Number of successors of this node.
108
+     */
109
+    public int getNumberOfSuccessors() {
110
+        return this.successors.size();
111
+    }
112
+
113
+    /**
114
+     * @return true if this node has at least one successor.
115
+     */
116
+    public boolean hasSuccessors() {
117
+        return !this.successors.isEmpty();
118
+    }
119
+
120
+    /**
121
+     * @return List of successors of this node (unmodifiable list).
122
+     * 
123
+     * @see Collections#unmodifiableList(List)
124
+     */
125
+    public List<Arc> getSuccessors() {
126
+        return Collections.unmodifiableList(this.successors);
127
+    }
128
+
129
+    /**
130
+     * @return Location of this node.
131
+     */
132
+    public Point getPoint() {
133
+        return point;
134
+    }
135
+
136
+    @Override
137
+    public boolean equals(Object other) {
138
+        if (other == null) {
139
+            return false;
140
+        }
141
+        if (other instanceof Node) {
142
+            return getId() == ((Node) other).getId();
143
+        }
144
+        return false;
145
+    }
146
+
147
+    /**
148
+     * Compare the ID of this node with the ID of the given node.
149
+     * 
150
+     * @param other Node to compare this node with.
151
+     * 
152
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
153
+     */
154
+    @Override
155
+    public int compareTo(Node other) {
156
+        return Integer.compare(getId(), other.getId());
157
+    }
158
+
159
+}

+ 297
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/Path.java View File

1
+package org.insa.graphs.model;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collections;
5
+import java.util.List;
6
+
7
+/**
8
+ * <p>
9
+ * Class representing a path between nodes in a graph.
10
+ * </p>
11
+ * 
12
+ * <p>
13
+ * A path is represented as a list of {@link Arc} with an origin and not a list
14
+ * of {@link Node} due to the multi-graph nature (multiple arcs between two
15
+ * nodes) of the considered graphs.
16
+ * </p>
17
+ *
18
+ */
19
+public class Path {
20
+
21
+    /**
22
+     * Create a new path that goes through the given list of nodes (in order),
23
+     * choosing the fastest route if multiple are available.
24
+     * 
25
+     * @param graph Graph containing the nodes in the list.
26
+     * @param nodes List of nodes to build the path.
27
+     * 
28
+     * @return A path that goes through the given list of nodes.
29
+     * 
30
+     * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
31
+     *                                  consecutive nodes in the list are not
32
+     *                                  connected in the graph.
33
+     * 
34
+     * Implemented.
35
+     */
36
+    public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes) throws IllegalArgumentException {
37
+        List<Arc> arcs = new ArrayList<Arc>();
38
+
39
+        if (nodes.size() == 1)
40
+            return new Path(graph, nodes.get(0));
41
+
42
+        for (int i = 0; i < nodes.size() - 1; i++) {
43
+            double min_time = Double.MAX_VALUE;
44
+            Arc best = null;
45
+            for (Arc a : nodes.get(i).getSuccessors()) {
46
+                if (a.getDestination() == nodes.get(i + 1) && a.getMinimumTravelTime() < min_time) {
47
+                    min_time = a.getMinimumTravelTime();
48
+                    best = a;
49
+                }
50
+            }
51
+            if (min_time == Double.MAX_VALUE) {
52
+                throw new IllegalArgumentException("At least two nodes cannot be connected.");
53
+            }
54
+            arcs.add(best);
55
+        }
56
+        return new Path(graph, arcs);
57
+    }
58
+
59
+    /**
60
+     * Create a new path that goes through the given list of nodes (in order),
61
+     * choosing the shortest route if multiple are available.
62
+     * 
63
+     * @param graph Graph containing the nodes in the list.
64
+     * @param nodes List of nodes to build the path.
65
+     * 
66
+     * @return A path that goes through the given list of nodes.
67
+     * 
68
+     * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
69
+     *                                  consecutive nodes in the list are not
70
+     *                                  connected in the graph.
71
+     * 
72
+     * 
73
+     */
74
+    public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes) throws IllegalArgumentException {
75
+        List<Arc> arcs = new ArrayList<Arc>();
76
+
77
+        if (nodes.size() == 1)
78
+            return new Path(graph, nodes.get(0));
79
+
80
+        for (int i = 0; i < nodes.size() - 1; i++) {
81
+            float shortest_length = Float.MAX_VALUE;
82
+            Arc best = null;
83
+            for (Arc a : nodes.get(i).getSuccessors()) {
84
+                if (a.getDestination() == nodes.get(i + 1) && a.getLength() < shortest_length) {
85
+                    shortest_length = a.getLength();
86
+                    best = a;
87
+                }
88
+            }
89
+            if (shortest_length == Float.MAX_VALUE) {
90
+                throw new IllegalArgumentException("At least two nodes cannot be connected.");
91
+            }
92
+            arcs.add(best);
93
+        }
94
+        return new Path(graph, arcs);
95
+    }
96
+
97
+    /**
98
+     * Concatenate the given paths.
99
+     * 
100
+     * @param paths Array of paths to concatenate.
101
+     * 
102
+     * @return Concatenated path.
103
+     * 
104
+     * @throws IllegalArgumentException if the paths cannot be concatenated (IDs of
105
+     *                                  map do not match, or the end of a path is
106
+     *                                  not the beginning of the next).
107
+     */
108
+    public static Path concatenate(Path... paths) throws IllegalArgumentException {
109
+        if (paths.length == 0) {
110
+            throw new IllegalArgumentException("Cannot concatenate an empty list of paths.");
111
+        }
112
+        final String mapId = paths[0].getGraph().getMapId();
113
+        for (int i = 1; i < paths.length; ++i) {
114
+            if (!paths[i].getGraph().getMapId().equals(mapId)) {
115
+                throw new IllegalArgumentException("Cannot concatenate paths from different graphs.");
116
+            }
117
+        }
118
+        ArrayList<Arc> arcs = new ArrayList<>();
119
+        for (Path path : paths) {
120
+            arcs.addAll(path.getArcs());
121
+        }
122
+        Path path = new Path(paths[0].getGraph(), arcs);
123
+        if (!path.isValid()) {
124
+            throw new IllegalArgumentException("Cannot concatenate paths that do not form a single path.");
125
+        }
126
+        return path;
127
+    }
128
+
129
+    // Graph containing this path.
130
+    private final Graph graph;
131
+
132
+    // Origin of the path
133
+    private final Node origin;
134
+
135
+    // List of arcs in this path.
136
+    private final List<Arc> arcs;
137
+
138
+    /**
139
+     * Create an empty path corresponding to the given graph.
140
+     * 
141
+     * @param graph Graph containing the path.
142
+     */
143
+    public Path(Graph graph) {
144
+        this.graph = graph;
145
+        this.origin = null;
146
+        this.arcs = new ArrayList<>();
147
+    }
148
+
149
+    /**
150
+     * Create a new path containing a single node.
151
+     * 
152
+     * @param graph Graph containing the path.
153
+     * @param node  Single node of the path.
154
+     */
155
+    public Path(Graph graph, Node node) {
156
+        this.graph = graph;
157
+        this.origin = node;
158
+        this.arcs = new ArrayList<>();
159
+    }
160
+
161
+    /**
162
+     * Create a new path with the given list of arcs.
163
+     * 
164
+     * @param graph Graph containing the path.
165
+     * @param arcs  Arcs to construct the path.
166
+     */
167
+    public Path(Graph graph, List<Arc> arcs) {
168
+        this.graph = graph;
169
+        this.arcs = arcs;
170
+        this.origin = arcs.size() > 0 ? arcs.get(0).getOrigin() : null;
171
+    }
172
+
173
+    /**
174
+     * @return Graph containing the path.
175
+     */
176
+    public Graph getGraph() {
177
+        return graph;
178
+    }
179
+
180
+    /**
181
+     * @return First node of the path.
182
+     */
183
+    public Node getOrigin() {
184
+        return origin;
185
+    }
186
+
187
+    /**
188
+     * @return Last node of the path.
189
+     */
190
+    public Node getDestination() {
191
+        return arcs.get(arcs.size() - 1).getDestination();
192
+    }
193
+
194
+    /**
195
+     * @return List of arcs in the path.
196
+     */
197
+    public List<Arc> getArcs() {
198
+        return Collections.unmodifiableList(arcs);
199
+    }
200
+
201
+    /**
202
+     * Check if this path is empty (it does not contain any node).
203
+     * 
204
+     * @return true if this path is empty, false otherwise.
205
+     */
206
+    public boolean isEmpty() {
207
+        return this.origin == null;
208
+    }
209
+
210
+    /**
211
+     * Get the number of <b>nodes</b> in this path.
212
+     * 
213
+     * @return Number of nodes in this path.
214
+     */
215
+    public int size() {
216
+        return isEmpty() ? 0 : 1 + this.arcs.size();
217
+    }
218
+
219
+    /**
220
+     * Check if this path is valid.
221
+     * 
222
+     * A path is valid if any of the following is true:
223
+     * <ul>
224
+     * <li>it is empty;</li>
225
+     * <li>it contains a single node (without arcs);</li>
226
+     * <li>the first arc has for origin the origin of the path and, for two
227
+     * consecutive arcs, the destination of the first one is the origin of the
228
+     * second one.</li>
229
+     * </ul>
230
+     * 
231
+     * @return true if the path is valid, false otherwise.
232
+     * 
233
+     *         Implemented.
234
+     */
235
+    public boolean isValid() {
236
+        // DONE
237
+        if (isEmpty() || arcs.isEmpty())
238
+            return true;
239
+
240
+        for (int i = 0; i < arcs.size() - 1; ++i) {
241
+            if (arcs.get(i).getDestination() != arcs.get(i + 1).getOrigin()) {
242
+                return false;
243
+            }
244
+        }
245
+        return true && arcs.get(0).getOrigin() == getOrigin();
246
+    }
247
+
248
+    /**
249
+     * Compute the length of this path (in meters).
250
+     * 
251
+     * @return Total length of the path (in meters).
252
+     * 
253
+     *         Implemented.
254
+     */
255
+    public float getLength() {
256
+        // DONE
257
+        float length = 0;
258
+        for (Arc i : arcs)
259
+            length += i.getLength();
260
+        return length;
261
+    }
262
+
263
+    /**
264
+     * Compute the time required to travel this path if moving at the given speed.
265
+     * 
266
+     * @param speed Speed to compute the travel time.
267
+     * 
268
+     * @return Time (in seconds) required to travel this path at the given speed (in
269
+     *         kilometers-per-hour).
270
+     * 
271
+     *         Implemented.
272
+     */
273
+    public double getTravelTime(double speed) {
274
+        // DONE
275
+        double time = 0;
276
+        for (Arc i : arcs)
277
+            time += i.getTravelTime(speed);
278
+        return time;
279
+    }
280
+
281
+    /**
282
+     * Compute the time to travel this path if moving at the maximum allowed speed
283
+     * on every arc.
284
+     * 
285
+     * @return Minimum travel time to travel this path (in seconds).
286
+     * 
287
+     *         Implemented.
288
+     */
289
+    public double getMinimumTravelTime() {
290
+        // DONE
291
+        double time = 0;
292
+        for (Arc i : arcs)
293
+            time += i.getMinimumTravelTime();
294
+        return time;
295
+    }
296
+
297
+}

+ 74
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/Point.java View File

1
+package org.insa.graphs.model;
2
+
3
+/**
4
+ * Class representing a point (position) on Earth.
5
+ *
6
+ */
7
+public final class Point {
8
+
9
+    /**
10
+     * Approximated Earth radius (in meters).
11
+     */
12
+    public static final double EARTH_RADIUS = 6378137.0;
13
+
14
+    /**
15
+     * Compute the distance in meters between the two given points.
16
+     * 
17
+     * @param p1 First point.
18
+     * @param p2 second point.
19
+     * 
20
+     * @return Distance between the two given points (in meters).
21
+     */
22
+    public static double distance(Point p1, Point p2) {
23
+        double sinLat = Math.sin(Math.toRadians(p1.getLatitude()))
24
+                * Math.sin(Math.toRadians(p2.getLatitude()));
25
+        double cosLat = Math.cos(Math.toRadians(p1.getLatitude()))
26
+                * Math.cos(Math.toRadians(p2.getLatitude()));
27
+        double cosLong = Math.cos(Math.toRadians(p2.getLongitude() - p1.getLongitude()));
28
+        return EARTH_RADIUS * Math.acos(sinLat + cosLat * cosLong);
29
+    }
30
+
31
+    // Longitude and latitude of the point.
32
+    private final float longitude, latitude;
33
+
34
+    /**
35
+     * Create a new point corresponding to the given (longitude, latitude) position.
36
+     * 
37
+     * @param longitude Longitude of the point (in degrees).
38
+     * @param latitude Latitude of the point (in degrees).
39
+     */
40
+    public Point(float longitude, float latitude) {
41
+        this.longitude = longitude;
42
+        this.latitude = latitude;
43
+    }
44
+
45
+    /**
46
+     * @return Longitude of this point (in degrees).
47
+     */
48
+    public float getLongitude() {
49
+        return longitude;
50
+    }
51
+
52
+    /**
53
+     * @return Latitude of this point (in degrees).
54
+     */
55
+    public float getLatitude() {
56
+        return latitude;
57
+    }
58
+
59
+    /**
60
+     * Compute the distance from this point to the given point
61
+     * 
62
+     * @param target Target point to compute distance to.
63
+     * 
64
+     * @return Distance between this point and the target point, in meters.
65
+     */
66
+    public double distanceTo(Point target) {
67
+        return distance(this, target);
68
+    }
69
+
70
+    @Override
71
+    public String toString() {
72
+        return String.format("Point(%f, %f)", getLongitude(), getLatitude());
73
+    }
74
+}

+ 0
- 0
be-graphes-model/src/main/java/org/insa/graphs/model/RoadInformation.java View File


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

Loading…
Cancel
Save