INITIAL COMMIT
This commit is contained in:
commit
61e29e6787
114 changed files with 11080 additions and 0 deletions
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# OS specific files and folders
|
||||
.DS_Store
|
||||
|
||||
# Java specific files and folders
|
||||
bin
|
||||
target
|
||||
doc
|
||||
*.jar
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# Editor specific files and folders
|
||||
*~
|
||||
.project
|
||||
|
||||
# Project specific files and folders
|
||||
*.mapfg
|
||||
*.mapgr
|
||||
*.path
|
||||
*.tgz
|
62
README.md
Normal file
62
README.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Graph & Algorithm project — INSA Toulouse
|
||||
|
||||
## How to start?
|
||||
|
||||
You will not be able to use this repository to save your work, you need to copy / import / fork it to
|
||||
your favorite Git platform.
|
||||
|
||||
### Importing to [Github](https://github.com), [Bitbucket](https://bitbucket.org) or [Gitlab](https://gitlab.com):
|
||||
|
||||
You first need to register and then log in to the platform you want. The steps to import the project are detailed below:
|
||||
|
||||
#### Github
|
||||
|
||||
1. In the upper-right corner of any page, click the **"+"** icon, then click **Import repository**, or go to [https://github.com/new/import](https://github.com/new/import).
|
||||
2. Paste the following URL in the first input:
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
3. Choose the name you want for the repository.
|
||||
4. Click *Begin import*.
|
||||
5. Wait for completion... Done!
|
||||
|
||||
#### Bitbucket
|
||||
|
||||
1. On the left panel of any page, click the **"+"** icon, then **Repository**, and then **Import**, or directly go to [https://bitbucket.org/repo/import](https://bitbucket.org/repo/import).
|
||||
2. Paste the following URL in the first input (select Git as source if not already selected):
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
3. Choose the name you want for repository, and select Git as the *Version control system*.
|
||||
4. Click *Import repository*.
|
||||
5. Wait for completion... Done!
|
||||
|
||||
#### Gitlab
|
||||
|
||||
1. In the upper-right corner of any page, click the **"+"** icon, then **New project**, or directly go to [https://gitlab.com/projects/new](https://gitlab.com/projects/new).
|
||||
2. Select the **Import project** tab, and then click **Repo by URL** (right-most option).
|
||||
3. Paste the following URL in the first input:
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
4. Choose the name you want for the repository.
|
||||
5. Click *Create project*.
|
||||
6. Wait for completion... Done!
|
||||
|
||||
### Importing to another repository provider *[not recommended]*:
|
||||
|
||||
1. Create a new **empty** repository (no README, no LICENSE) on your provider. Let's assume the URL of your repository is `$URL_REPOSITORY`.
|
||||
2. Clone this repository somewhere:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.typename.fr/INSA/be-graphes.git
|
||||
```
|
||||
|
||||
3. Go inside the newly cloned repository and update the **remote**:
|
||||
|
||||
```bash
|
||||
cd be-graphes
|
||||
git remote set-url origin $URL_REPOSITORY
|
||||
```
|
||||
|
||||
4. Push to your repository:
|
||||
|
||||
```bash
|
||||
push -u origin master
|
||||
```
|
||||
|
||||
Another way is to do a bare clone and then mirror it to your repository: [https://help.github.com/articles/importing-a-git-repository-using-the-command-line/](https://help.github.com/articles/importing-a-git-repository-using-the-command-line/)
|
25
be-graphes-algos/pom.xml
Normal file
25
be-graphes-algos/pom.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-all</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>be-graphes-algos</artifactId>
|
||||
<name>be-graphes-algos</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,87 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base class for algorithm classes.
|
||||
*
|
||||
* @param <Observer> Observer type for the algorithm.
|
||||
*/
|
||||
public abstract class AbstractAlgorithm<Observer> {
|
||||
|
||||
// Input data for the algorithm
|
||||
protected final AbstractInputData data;
|
||||
|
||||
// List of observers for the algorithm
|
||||
protected final ArrayList<Observer> observers;
|
||||
|
||||
/**
|
||||
* Create a new algorithm with an empty list of observers.
|
||||
*
|
||||
* @param data Input data for the algorithm.
|
||||
*/
|
||||
protected AbstractAlgorithm(AbstractInputData data) {
|
||||
this.data = data;
|
||||
this.observers = new ArrayList<Observer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new algorithm with the given list of observers.
|
||||
*
|
||||
* @param data Input data for the algorithm.
|
||||
* @param observers Initial list of observers for the algorithm.
|
||||
*/
|
||||
protected AbstractAlgorithm(AbstractInputData data, ArrayList<Observer> observers) {
|
||||
this.data = data;
|
||||
this.observers = observers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observer to this algorithm.
|
||||
*
|
||||
* @param observer Observer to add to this algorithm.
|
||||
*/
|
||||
public void addObserver(Observer observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of observers for this algorithm.
|
||||
*/
|
||||
public ArrayList<Observer> getObservers() {
|
||||
return observers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Input for this algorithm.
|
||||
*/
|
||||
public AbstractInputData getInputData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the algorithm and return the solution.
|
||||
*
|
||||
* This methods internally time the call to doRun() and update the result of the
|
||||
* call with the computed solving time.
|
||||
*
|
||||
* @return The solution found by the algorithm (may not be a feasible solution).
|
||||
*/
|
||||
public AbstractSolution run() {
|
||||
Instant start = Instant.now();
|
||||
AbstractSolution solution = this.doRun();
|
||||
solution.setSolvingTime(Duration.between(start, Instant.now()));
|
||||
return solution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that should be implemented by child class.
|
||||
*
|
||||
* @return The solution found, must not be null (use an infeasible or unknown
|
||||
* status if necessary).
|
||||
*/
|
||||
protected abstract AbstractSolution doRun();
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.GraphStatistics;
|
||||
|
||||
/**
|
||||
* Base class for algorithm input data classes. This class contains the basic
|
||||
* data that are required by most graph algorithms, i.e. a graph, a mode (time /
|
||||
* length) and a filter for the arc.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractInputData {
|
||||
|
||||
/**
|
||||
* Enum specifying the top mode of the algorithms.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public enum Mode {
|
||||
TIME, LENGTH
|
||||
}
|
||||
|
||||
// Graph
|
||||
private final Graph graph;
|
||||
|
||||
// Arc filter.
|
||||
protected final ArcInspector arcInspector;
|
||||
|
||||
/**
|
||||
* Create a new AbstractInputData instance for the given graph, mode and filter.
|
||||
*
|
||||
* @param graph Graph for this input data.
|
||||
* @param arcInspector Arc inspector for this input data.
|
||||
*/
|
||||
protected AbstractInputData(Graph graph, ArcInspector arcInspector) {
|
||||
this.graph = graph;
|
||||
this.arcInspector = arcInspector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Graph associated with this input.
|
||||
*/
|
||||
public Graph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cost associated with the given arc according to the underlying
|
||||
* arc inspector.
|
||||
*
|
||||
* @param arc Arc for which cost should be retrieved.
|
||||
*
|
||||
* @return Cost for the given arc.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public double getCost(Arc arc) {
|
||||
return this.arcInspector.getCost(arc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Mode associated with this input data.
|
||||
*
|
||||
* @see Mode
|
||||
*/
|
||||
public Mode getMode() {
|
||||
return this.arcInspector.getMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum speed associated with this input data, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is associated. The maximum
|
||||
* speed associated with input data is different from the maximum speed
|
||||
* associated with graph (accessible via {@link Graph#getGraphInformation()}).
|
||||
*
|
||||
* @return The maximum speed for this inspector, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
|
||||
*/
|
||||
public int getMaximumSpeed() {
|
||||
return this.arcInspector.getMaximumSpeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given arc is allowed for the filter corresponding to this input.
|
||||
*
|
||||
* @param arc Arc to check.
|
||||
*
|
||||
* @return true if the given arc is allowed.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return this.arcInspector.isAllowed(arc);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Base class for solution classes returned by the algorithm. This class
|
||||
* contains the basic information that any solution should have: status of the
|
||||
* solution (unknown, infeasible, etc.), solving time and the original input
|
||||
* data.
|
||||
*/
|
||||
public abstract class AbstractSolution {
|
||||
|
||||
/**
|
||||
* Possible status for a solution.
|
||||
*
|
||||
*/
|
||||
public enum Status {
|
||||
UNKNOWN, INFEASIBLE, FEASIBLE, OPTIMAL,
|
||||
};
|
||||
|
||||
// Status of the solution.
|
||||
private final Status status;
|
||||
|
||||
// Solving time for the solution.
|
||||
private Duration solvingTime;
|
||||
|
||||
// Original input of the solution.
|
||||
private final AbstractInputData data;
|
||||
|
||||
/**
|
||||
* Create a new abstract solution with unknown status.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
protected AbstractSolution(AbstractInputData data) {
|
||||
this.data = data;
|
||||
this.solvingTime = Duration.ZERO;
|
||||
this.status = Status.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @param status
|
||||
*/
|
||||
protected AbstractSolution(AbstractInputData data, Status status) {
|
||||
this.data = data;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Original input for this solution.
|
||||
*/
|
||||
public AbstractInputData getInputData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Status of this solution.
|
||||
*/
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Solving time of this solution.
|
||||
*/
|
||||
public Duration getSolvingTime() {
|
||||
return solvingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the solving time of this solution.
|
||||
*
|
||||
* @param solvingTime Solving time for the solution.
|
||||
*/
|
||||
protected void setSolvingTime(Duration solvingTime) {
|
||||
this.solvingTime = solvingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the solution is feasible or optimal.
|
||||
*/
|
||||
public boolean isFeasible() {
|
||||
return status == Status.FEASIBLE || status == Status.OPTIMAL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
|
||||
|
||||
/**
|
||||
* Factory class used to register and retrieve algorithms based on their common
|
||||
* ancestor and name.
|
||||
*
|
||||
*/
|
||||
public class AlgorithmFactory {
|
||||
|
||||
// Map between algorithm names and class.
|
||||
private final static Map<Class<? extends AbstractAlgorithm<?>>, Map<String, Class<? extends AbstractAlgorithm<?>>>> ALGORITHMS = new IdentityHashMap<>();
|
||||
|
||||
static {
|
||||
// Register weakly-connected components algorithm:
|
||||
registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic",
|
||||
WeaklyConnectedComponentsAlgorithm.class);
|
||||
|
||||
// Register shortest path algorithm:
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class);
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class);
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class);
|
||||
|
||||
// Register your algorithms here:
|
||||
// registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm",
|
||||
// MyCarPoolingAlgorithm.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given algorithm class with the given name as a child class of
|
||||
* the given base algorithm.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class that corresponds to the newly
|
||||
* registered algorithm class (e.g., generic algorithm
|
||||
* class for the problem).
|
||||
* @param name Name for the registered algorithm class.
|
||||
* @param algoClass Algorithm class to register.
|
||||
*/
|
||||
public static void registerAlgorithm(Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
|
||||
String name, Class<? extends AbstractAlgorithm<?>> algoClass) {
|
||||
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
|
||||
ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>());
|
||||
}
|
||||
ALGORITHMS.get(baseAlgorithm).put(name, algoClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the given algorithm class using the given input data.
|
||||
* Assuming algorithm correspond to a class "Algorithm", this function returns
|
||||
* an object equivalent to `new Algorithm(data)`.
|
||||
*
|
||||
* @param algorithm Class of the algorithm to create.
|
||||
* @param data Input data for the algorithm.
|
||||
*
|
||||
* @return A new instance of the given algorithm class using the given data.
|
||||
*
|
||||
* @throws Exception if something wrong happens when constructing the object,
|
||||
* i.e. the given input data does not correspond to the given
|
||||
* algorithm and/or no constructor that takes a single
|
||||
* parameter of type (data.getClass()) exists.
|
||||
*/
|
||||
public static AbstractAlgorithm<?> createAlgorithm(
|
||||
Class<? extends AbstractAlgorithm<?>> algorithm, AbstractInputData data)
|
||||
throws Exception {
|
||||
// Retrieve the set of constructors for the given algorithm class.
|
||||
Constructor<?>[] constructors = algorithm.getDeclaredConstructors();
|
||||
|
||||
// Within this set, find the constructor that can be called with "data" (only).
|
||||
AbstractAlgorithm<?> constructed = null;
|
||||
for (Constructor<?> c: constructors) {
|
||||
Class<?>[] params = c.getParameterTypes();
|
||||
if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) {
|
||||
c.setAccessible(true);
|
||||
constructed = (AbstractAlgorithm<?>) c.newInstance(new Object[] { data });
|
||||
break;
|
||||
}
|
||||
}
|
||||
return constructed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the algorithm class corresponding to the given base algorithm class
|
||||
* and name. The algorithm must have been previously registered using
|
||||
* registerAlgorithm.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class for the algorithm to retrieve.
|
||||
* @param name Name of the algorithm to retrieve.
|
||||
*
|
||||
* @return Class corresponding to the given name.
|
||||
*
|
||||
* @see #registerAlgorithm
|
||||
*/
|
||||
public static Class<? extends AbstractAlgorithm<?>> getAlgorithmClass(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm, String name) {
|
||||
return ALGORITHMS.get(baseAlgorithm).get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of names corresponding to the registered algorithm classes
|
||||
* for the given base algorithm class.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class for the algorithm class names to
|
||||
* retrieve.
|
||||
*
|
||||
* @return Names of the currently registered algorithms.
|
||||
*
|
||||
* @see #registerAlgorithm
|
||||
*/
|
||||
public static Set<String> getAlgorithmNames(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
|
||||
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
|
||||
return new TreeSet<>();
|
||||
}
|
||||
return ALGORITHMS.get(baseAlgorithm).keySet();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.GraphStatistics;
|
||||
|
||||
/**
|
||||
* This class can be used to indicate to an algorithm which arcs can be used and
|
||||
* the costs of the usable arcs..
|
||||
*
|
||||
*/
|
||||
public interface ArcInspector {
|
||||
|
||||
/**
|
||||
* Check if the given arc can be used (is allowed).
|
||||
*
|
||||
* @param arc Arc to check.
|
||||
*
|
||||
* @return true if the given arc is allowed.
|
||||
*/
|
||||
public boolean isAllowed(Arc arc);
|
||||
|
||||
/**
|
||||
* Find the cost of the given arc.
|
||||
*
|
||||
* @param arc Arc for which the cost should be returned.
|
||||
*
|
||||
* @return Cost of the arc.
|
||||
*/
|
||||
public double getCost(Arc arc);
|
||||
|
||||
/**
|
||||
* @return The maximum speed for this inspector, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
|
||||
*/
|
||||
public int getMaximumSpeed();
|
||||
|
||||
/**
|
||||
* @return Mode for this arc inspector.
|
||||
*/
|
||||
public Mode getMode();
|
||||
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.GraphStatistics;
|
||||
import org.insa.graphs.model.AccessRestrictions.AccessMode;
|
||||
import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
|
||||
|
||||
public class ArcInspectorFactory {
|
||||
|
||||
/**
|
||||
* @return List of all arc filters in this factory.
|
||||
*/
|
||||
public static List<ArcInspector> getAllFilters() {
|
||||
List<ArcInspector> filters = new ArrayList<>();
|
||||
|
||||
// Common filters:
|
||||
|
||||
// No filter (all arcs allowed):
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest path, all roads allowed";
|
||||
}
|
||||
});
|
||||
|
||||
// Only road allowed for cars and length:
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest path, only roads open for cars";
|
||||
}
|
||||
});
|
||||
|
||||
// Only road allowed for cars and time:
|
||||
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getMinimumTravelTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path, all roads allowed";
|
||||
}
|
||||
});
|
||||
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getMinimumTravelTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path, only roads open for cars";
|
||||
}
|
||||
});
|
||||
|
||||
// Non-private roads for pedestrian and bicycle:
|
||||
filters.add(new ArcInspector() {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getTravelTime(
|
||||
Math.min(getMaximumSpeed(), arc.getRoadInformation().getMaximumSpeed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path for pedestrian";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
});
|
||||
|
||||
// Add your own filters here (do not forget to implement toString()
|
||||
// to get an understandable output!):
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
|
||||
public abstract class CarPoolingAlgorithm extends AbstractAlgorithm<CarPoolingObserver> {
|
||||
|
||||
protected CarPoolingAlgorithm(CarPoolingData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarPoolingSolution run() {
|
||||
return (CarPoolingSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract CarPoolingSolution doRun();
|
||||
|
||||
@Override
|
||||
public CarPoolingData getInputData() {
|
||||
return (CarPoolingData) super.getInputData();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class CarPoolingData extends AbstractInputData {
|
||||
|
||||
protected CarPoolingData(Graph graph, ArcInspector arcFilter) {
|
||||
super(graph, arcFilter);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public class CarPoolingGraphicObserver implements CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public interface CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class CarPoolingSolution extends AbstractSolution {
|
||||
|
||||
protected CarPoolingSolution(CarPoolingData data, Status status) {
|
||||
super(data, status);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public class CarPoolingTextObserver implements CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
|
||||
public abstract class PackageSwitchAlgorithm extends AbstractAlgorithm<PackageSwitchObserver> {
|
||||
|
||||
/**
|
||||
* Create a new PackageSwitchAlgorithm with the given data.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
protected PackageSwitchAlgorithm(PackageSwitchData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageSwitchSolution run() {
|
||||
return (PackageSwitchSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract PackageSwitchSolution doRun();
|
||||
|
||||
@Override
|
||||
public PackageSwitchData getInputData() {
|
||||
return (PackageSwitchData) super.getInputData();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class PackageSwitchData extends AbstractInputData {
|
||||
|
||||
protected PackageSwitchData(Graph graph, ArcInspector arcFilter) {
|
||||
super(graph, arcFilter);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public interface PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class PackageSwitchSolution extends AbstractSolution {
|
||||
|
||||
protected PackageSwitchSolution(PackageSwitchData data, Status status) {
|
||||
super(data, status);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public class PackageSwitchTextObserver implements PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
public class AStarAlgorithm extends DijkstraAlgorithm {
|
||||
|
||||
public AStarAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.Path;
|
||||
|
||||
public class BellmanFordAlgorithm extends ShortestPathAlgorithm {
|
||||
|
||||
public BellmanFordAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShortestPathSolution doRun() {
|
||||
|
||||
// Retrieve the graph.
|
||||
ShortestPathData data = getInputData();
|
||||
Graph graph = data.getGraph();
|
||||
|
||||
final int nbNodes = graph.size();
|
||||
|
||||
// Initialize array of distances.
|
||||
double[] distances = new double[nbNodes];
|
||||
Arrays.fill(distances, Double.POSITIVE_INFINITY);
|
||||
distances[data.getOrigin().getId()] = 0;
|
||||
|
||||
// Notify observers about the first event (origin processed).
|
||||
notifyOriginProcessed(data.getOrigin());
|
||||
|
||||
// Initialize array of predecessors.
|
||||
Arc[] predecessorArcs = new Arc[nbNodes];
|
||||
|
||||
// Actual algorithm, we will assume the graph does not contain negative
|
||||
// cycle...
|
||||
boolean found = false;
|
||||
for (int i = 0; !found && i < nbNodes; ++i) {
|
||||
found = true;
|
||||
for (Node node: graph.getNodes()) {
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
|
||||
// Small test to check allowed roads...
|
||||
if (!data.isAllowed(arc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve weight of the arc.
|
||||
double w = data.getCost(arc);
|
||||
double oldDistance = distances[arc.getDestination().getId()];
|
||||
double newDistance = distances[node.getId()] + w;
|
||||
|
||||
if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) {
|
||||
notifyNodeReached(arc.getDestination());
|
||||
}
|
||||
|
||||
// Check if new distances would be better, if so update...
|
||||
if (newDistance < oldDistance) {
|
||||
found = false;
|
||||
distances[arc.getDestination().getId()] = distances[node.getId()] + w;
|
||||
predecessorArcs[arc.getDestination().getId()] = arc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortestPathSolution solution = null;
|
||||
|
||||
// Destination has no predecessor, the solution is infeasible...
|
||||
if (predecessorArcs[data.getDestination().getId()] == null) {
|
||||
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
|
||||
}
|
||||
else {
|
||||
|
||||
// The destination has been found, notify the observers.
|
||||
notifyDestinationReached(data.getDestination());
|
||||
|
||||
// Create the path from the array of predecessors...
|
||||
ArrayList<Arc> arcs = new ArrayList<>();
|
||||
Arc arc = predecessorArcs[data.getDestination().getId()];
|
||||
while (arc != null) {
|
||||
arcs.add(arc);
|
||||
arc = predecessorArcs[arc.getOrigin().getId()];
|
||||
}
|
||||
|
||||
// Reverse the path...
|
||||
Collections.reverse(arcs);
|
||||
|
||||
// Create the final solution.
|
||||
solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs));
|
||||
}
|
||||
|
||||
return solution;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
|
||||
|
||||
public DijkstraAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShortestPathSolution doRun() {
|
||||
final ShortestPathData data = getInputData();
|
||||
ShortestPathSolution solution = null;
|
||||
// TODO:
|
||||
return solution;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public abstract class ShortestPathAlgorithm extends AbstractAlgorithm<ShortestPathObserver> {
|
||||
|
||||
protected ShortestPathAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortestPathSolution run() {
|
||||
return (ShortestPathSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract ShortestPathSolution doRun();
|
||||
|
||||
@Override
|
||||
public ShortestPathData getInputData() {
|
||||
return (ShortestPathData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the origin has been processed.
|
||||
*
|
||||
* @param node Origin.
|
||||
*/
|
||||
public void notifyOriginProcessed(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyOriginProcessed(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a node has been reached for the first time.
|
||||
*
|
||||
* @param node Node that has been reached.
|
||||
*/
|
||||
public void notifyNodeReached(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyNodeReached(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a node has been marked, i.e. its final value has
|
||||
* been set.
|
||||
*
|
||||
* @param node Node that has been marked.
|
||||
*/
|
||||
public void notifyNodeMarked(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyNodeMarked(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the destination has been reached.
|
||||
*
|
||||
* @param node Destination.
|
||||
*/
|
||||
public void notifyDestinationReached(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyDestinationReached(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class ShortestPathData extends AbstractInputData {
|
||||
|
||||
// Origin and destination nodes.
|
||||
private final Node origin, destination;
|
||||
|
||||
/**
|
||||
* Construct a new instance of ShortestPathInputData with the given parameters.
|
||||
*
|
||||
* @param graph Graph in which the path should be looked for.
|
||||
* @param origin Origin node of the path.
|
||||
* @param destination Destination node of the path.
|
||||
* @param arcInspector Filter for arcs (used to allow only a specific set of
|
||||
* arcs in the graph to be used).
|
||||
*/
|
||||
public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) {
|
||||
super(graph, arcInspector);
|
||||
this.origin = origin;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Origin node for the path.
|
||||
*/
|
||||
public Node getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Destination node for the path.
|
||||
*/
|
||||
public Node getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " ["
|
||||
+ this.arcInspector.toString().toLowerCase() + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public interface ShortestPathObserver {
|
||||
|
||||
/**
|
||||
* Notify the observer that the origin has been processed.
|
||||
*
|
||||
* @param node Origin.
|
||||
*/
|
||||
public void notifyOriginProcessed(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that a node has been reached for the first
|
||||
* time.
|
||||
*
|
||||
* @param node Node that has been reached.
|
||||
*/
|
||||
public void notifyNodeReached(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that a node has been marked, i.e. its final
|
||||
* value has been set.
|
||||
*
|
||||
* @param node Node that has been marked.
|
||||
*/
|
||||
public void notifyNodeMarked(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that the destination has been reached.
|
||||
*
|
||||
* @param node Destination.
|
||||
*/
|
||||
public void notifyDestinationReached(Node node);
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class ShortestPathSolution extends AbstractSolution {
|
||||
|
||||
// Optimal solution.
|
||||
private final Path path;
|
||||
|
||||
/**
|
||||
* Create a new infeasible shortest-path solution for the given input and
|
||||
* status.
|
||||
*
|
||||
* @param data Original input data for this solution.
|
||||
* @param status Status of the solution (UNKNOWN / INFEASIBLE).
|
||||
*/
|
||||
public ShortestPathSolution(ShortestPathData data, Status status) {
|
||||
super(data, status);
|
||||
this.path = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new shortest-path solution.
|
||||
*
|
||||
* @param data Original input data for this solution.
|
||||
* @param status Status of the solution (FEASIBLE / OPTIMAL).
|
||||
* @param path Path corresponding to the solution.
|
||||
*/
|
||||
public ShortestPathSolution(ShortestPathData data, Status status, Path path) {
|
||||
super(data, status);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortestPathData getInputData() {
|
||||
return (ShortestPathData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path of this solution, if any.
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String info = null;
|
||||
if (!isFeasible()) {
|
||||
info = String.format("No path found from node #%d to node #%d",
|
||||
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
|
||||
}
|
||||
else {
|
||||
double cost = 0;
|
||||
for (Arc arc: getPath().getArcs()) {
|
||||
cost += getInputData().getCost(arc);
|
||||
}
|
||||
info = String.format("Found a path from node #%d to node #%d",
|
||||
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
|
||||
if (getInputData().getMode() == Mode.LENGTH) {
|
||||
info = String.format("%s, %.4f kilometers", info, cost / 1000.0);
|
||||
}
|
||||
else {
|
||||
info = String.format("%s, %.4f minutes", info, cost / 60.0);
|
||||
}
|
||||
}
|
||||
info += " in " + getSolvingTime().getSeconds() + " seconds.";
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class ShortestPathTextObserver implements ShortestPathObserver {
|
||||
|
||||
private final PrintStream stream;
|
||||
|
||||
public ShortestPathTextObserver(PrintStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyOriginProcessed(Node node) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeReached(Node node) {
|
||||
stream.println("Node " + node.getId() + " reached.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeMarked(Node node) {
|
||||
stream.println("Node " + node.getId() + " marked.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDestinationReached(Node node) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Implements a binary heap containing elements of type E.
|
||||
*
|
||||
* Note that all comparisons are based on the compareTo method, hence E must
|
||||
* implement Comparable
|
||||
*
|
||||
* @author Mark Allen Weiss
|
||||
* @author DLB
|
||||
*/
|
||||
public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
|
||||
|
||||
// Number of elements in heap.
|
||||
private int currentSize;
|
||||
|
||||
// The heap array.
|
||||
protected final ArrayList<E> array;
|
||||
|
||||
/**
|
||||
* Construct a new empty binary heap.
|
||||
*/
|
||||
public BinaryHeap() {
|
||||
this.currentSize = 0;
|
||||
this.array = new ArrayList<E>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of the given heap.
|
||||
*
|
||||
* @param heap Binary heap to copy.
|
||||
*/
|
||||
public BinaryHeap(BinaryHeap<E> heap) {
|
||||
this.currentSize = heap.currentSize;
|
||||
this.array = new ArrayList<E>(heap.array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element at the given index.
|
||||
*
|
||||
* @param index Index at which the element should be set.
|
||||
* @param value Element to set.
|
||||
*/
|
||||
private void arraySet(int index, E value) {
|
||||
if (index == this.array.size()) {
|
||||
this.array.add(value);
|
||||
}
|
||||
else {
|
||||
this.array.set(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Index of the parent of the given index.
|
||||
*/
|
||||
protected int indexParent(int index) {
|
||||
return (index - 1) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Index of the left child of the given index.
|
||||
*/
|
||||
protected int indexLeft(int index) {
|
||||
return index * 2 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to percolate up in the heap.
|
||||
*
|
||||
* @param index Index at which the percolate begins.
|
||||
*/
|
||||
private void percolateUp(int index) {
|
||||
E x = this.array.get(index);
|
||||
|
||||
for (; index > 0
|
||||
&& x.compareTo(this.array.get(indexParent(index))) < 0; index = indexParent(
|
||||
index)) {
|
||||
E moving_val = this.array.get(indexParent(index));
|
||||
this.arraySet(index, moving_val);
|
||||
}
|
||||
|
||||
this.arraySet(index, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to percolate down in the heap.
|
||||
*
|
||||
* @param index Index at which the percolate begins.
|
||||
*/
|
||||
private void percolateDown(int index) {
|
||||
int ileft = indexLeft(index);
|
||||
int iright = ileft + 1;
|
||||
|
||||
if (ileft < this.currentSize) {
|
||||
E current = this.array.get(index);
|
||||
E left = this.array.get(ileft);
|
||||
boolean hasRight = iright < this.currentSize;
|
||||
E right = (hasRight) ? this.array.get(iright) : null;
|
||||
|
||||
if (!hasRight || left.compareTo(right) < 0) {
|
||||
// Left is smaller
|
||||
if (left.compareTo(current) < 0) {
|
||||
this.arraySet(index, left);
|
||||
this.arraySet(ileft, current);
|
||||
this.percolateDown(ileft);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Right is smaller
|
||||
if (right.compareTo(current) < 0) {
|
||||
this.arraySet(index, right);
|
||||
this.arraySet(iright, current);
|
||||
this.percolateDown(iright);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.currentSize == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.currentSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(E x) {
|
||||
int index = this.currentSize++;
|
||||
this.arraySet(index, x);
|
||||
this.percolateUp(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(E x) throws ElementNotFoundException {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
@Override
|
||||
public E findMin() throws EmptyPriorityQueueException {
|
||||
if (isEmpty())
|
||||
throw new EmptyPriorityQueueException();
|
||||
return this.array.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E deleteMin() throws EmptyPriorityQueueException {
|
||||
E minItem = findMin();
|
||||
E lastItem = this.array.get(--this.currentSize);
|
||||
this.arraySet(0, lastItem);
|
||||
this.percolateDown(0);
|
||||
return minItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of this binary heap.
|
||||
*
|
||||
* @return a string containing a sorted view this binary heap.
|
||||
*/
|
||||
public String toStringSorted() {
|
||||
return BinaryHeapFormatter.toStringSorted(this, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of this binary heap.
|
||||
*
|
||||
* @param maxElement Maximum number of elements to display. or {@code -1} to
|
||||
* display all the elements.
|
||||
*
|
||||
* @return a string containing a sorted view this binary heap.
|
||||
*/
|
||||
public String toStringSorted(int maxElement) {
|
||||
return BinaryHeapFormatter.toStringSorted(this, maxElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of this binary heap.
|
||||
*
|
||||
* @return a string containing a tree view of this binary heap.
|
||||
*/
|
||||
public String toStringTree() {
|
||||
return BinaryHeapFormatter.toStringTree(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of this binary heap.
|
||||
*
|
||||
* @param maxDepth Maximum depth of the tree to display.
|
||||
*
|
||||
* @return a string containing a tree view of this binary heap.
|
||||
*/
|
||||
public String toStringTree(int maxDepth) {
|
||||
return BinaryHeapFormatter.toStringTree(this, maxDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return BinaryHeapFormatter.toStringTree(this, 8);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BinaryHeapFormatter {
|
||||
|
||||
/**
|
||||
* This class is used by {@link #toStringTree}, and simply contains three string
|
||||
* accumulating. This is an immutable class.
|
||||
*
|
||||
*/
|
||||
private static class Context {
|
||||
|
||||
// Output text:
|
||||
public final String acu;
|
||||
|
||||
// Margin to get back exactly under the current position:
|
||||
public final String margin;
|
||||
|
||||
// Last margin used for the last child of a node. The markers are different:
|
||||
public final String lastmargin;
|
||||
|
||||
/**
|
||||
* Creaet a new {@code Context}.
|
||||
*
|
||||
* @param acu The accumulated string.
|
||||
* @param margin The current margin.
|
||||
* @param lastMargin The last margin used.
|
||||
*/
|
||||
public Context(String acu, String margin, String lastMargin) {
|
||||
this.acu = acu;
|
||||
this.margin = margin;
|
||||
this.lastmargin = lastMargin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending newlines to this context.
|
||||
*
|
||||
* @param n Number of newlines to append.
|
||||
*
|
||||
* @return a new context with {@code n} newlines appended.
|
||||
*/
|
||||
public Context appendNewlines(int n) {
|
||||
if (n <= 0) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return (new Context(this.acu + "\n" + this.margin, this.margin, this.lastmargin)
|
||||
.appendNewlines(n - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending the given string to this context.
|
||||
*
|
||||
* @param count Number of spaces to add to the margin, or {@code null} to use
|
||||
* the length of the string.
|
||||
* @param text String to append.
|
||||
*
|
||||
* @return a new context with {@code text} appended.
|
||||
*/
|
||||
public Context appendText(Integer count, String text) {
|
||||
int cnt = (count == null) ? text.length() : count;
|
||||
final String spaces = new String(new char[cnt]).replace('\0', ' ');
|
||||
return new Context(this.acu + text, this.margin + spaces, this.lastmargin + spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending a branch to this context.
|
||||
*
|
||||
* @param n Number of spaces to add to the margin, or {@code null} to use
|
||||
* the length of the string.
|
||||
* @param label Name of the branch.
|
||||
*
|
||||
* @return a new context with the branch appended.
|
||||
*/
|
||||
public Context appendBranch(Integer count, String label) {
|
||||
final Context ctxt = this.appendText(count, label);
|
||||
|
||||
if (count == null) {
|
||||
return new Context(ctxt.acu + "_", ctxt.margin + "|", ctxt.margin + " ");
|
||||
}
|
||||
else {
|
||||
return new Context(ctxt.acu, ctxt.margin + "|", ctxt.margin + " ")
|
||||
.appendNewlines(1);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Input : ready to write the current node at the current context position.
|
||||
* Output : the last character of acu is the last character of the current node.
|
||||
*/
|
||||
protected static <E extends Comparable<E>> Context toStringLoop(BinaryHeap<E> heap,
|
||||
Context ctxt, int node, int max_depth) {
|
||||
|
||||
if (max_depth < 0) {
|
||||
return ctxt.appendText(null, "...");
|
||||
}
|
||||
else {
|
||||
E nodeval = heap.array.get(node);
|
||||
String nodevals = nodeval.toString();
|
||||
|
||||
ArrayList<Integer> childs = new ArrayList<Integer>();
|
||||
// Add childs
|
||||
int index_left = heap.indexLeft(node);
|
||||
int index_right = index_left + 1;
|
||||
|
||||
if (index_left < heap.size()) {
|
||||
childs.add(index_left);
|
||||
}
|
||||
if (index_right < heap.size()) {
|
||||
childs.add(index_right);
|
||||
}
|
||||
|
||||
Context ctxt2 = childs.isEmpty() ? ctxt.appendText(null, nodevals)
|
||||
: ctxt.appendBranch(1, nodevals);
|
||||
|
||||
for (int ch = 0; ch < childs.size(); ch++) {
|
||||
boolean is_last = (ch == childs.size() - 1);
|
||||
int child = childs.get(ch);
|
||||
|
||||
if (is_last) {
|
||||
Context ctxt3 = new Context(ctxt2.acu, ctxt2.lastmargin, ctxt2.lastmargin);
|
||||
ctxt2 = new Context(toStringLoop(heap, ctxt3.appendText(null, "___"), child,
|
||||
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin);
|
||||
}
|
||||
else {
|
||||
ctxt2 = new Context(toStringLoop(heap, ctxt2.appendText(null, "___"), child,
|
||||
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin).appendNewlines(2);
|
||||
}
|
||||
}
|
||||
|
||||
return ctxt2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of the given binary
|
||||
* heap.
|
||||
*
|
||||
* @param heap The binary heap to display.
|
||||
* @param maxDepth Maximum depth of the tree to display.
|
||||
*
|
||||
* @return a string containing a tree view of the given binary heap.
|
||||
*/
|
||||
public static <E extends Comparable<E>> String toStringTree(BinaryHeap<E> heap, int maxDepth) {
|
||||
final Context init_context = new Context(" ", " ", " ");
|
||||
final Context result = toStringLoop(heap, init_context, 0, maxDepth);
|
||||
return result.acu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of the given binary
|
||||
* heap.
|
||||
*
|
||||
* @param heap The binary heap to display.
|
||||
* @param maxElement Maximum number of elements to display. or {@code -1} to
|
||||
* display all the elements.
|
||||
*
|
||||
* @return a string containing a sorted view the given binary heap.
|
||||
*/
|
||||
public static <E extends Comparable<E>> String toStringSorted(BinaryHeap<E> heap,
|
||||
int max_elements) {
|
||||
String result = "";
|
||||
final BinaryHeap<E> copy = new BinaryHeap<E>(heap);
|
||||
|
||||
final String truncate;
|
||||
if (max_elements < 0 || max_elements >= heap.size()) {
|
||||
truncate = "";
|
||||
}
|
||||
else {
|
||||
truncate = ", only " + max_elements + " elements are shown";
|
||||
}
|
||||
|
||||
result += "======== Sorted HEAP (size = " + heap.size() + truncate + ") ========\n\n";
|
||||
|
||||
while (!copy.isEmpty() && max_elements-- != 0) {
|
||||
result += copy.deleteMin() + "\n";
|
||||
}
|
||||
|
||||
result += "\n-------- End of heap --------";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
final BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
heap.insert(i);
|
||||
}
|
||||
|
||||
System.out.println(heap.toStringSorted(-1));
|
||||
System.out.println(heap.toStringTree(6));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class BinarySearchTree<E extends Comparable<E>> implements PriorityQueue<E> {
|
||||
|
||||
// Underlying implementation
|
||||
private final SortedSet<E> sortedSet;
|
||||
|
||||
/**
|
||||
* Create a new empty binary search tree.
|
||||
*/
|
||||
public BinarySearchTree() {
|
||||
this.sortedSet = new TreeSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of the given binary search tree.
|
||||
*
|
||||
* @param bst Binary search tree to copy.
|
||||
*/
|
||||
public BinarySearchTree(BinarySearchTree<E> bst) {
|
||||
this.sortedSet = new TreeSet<>(bst.sortedSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return sortedSet.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return sortedSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(E x) {
|
||||
sortedSet.add(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(E x) throws ElementNotFoundException {
|
||||
if (!sortedSet.remove(x)) {
|
||||
throw new ElementNotFoundException(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E findMin() throws EmptyPriorityQueueException {
|
||||
if (isEmpty()) {
|
||||
throw new EmptyPriorityQueueException();
|
||||
}
|
||||
return sortedSet.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E deleteMin() throws EmptyPriorityQueueException {
|
||||
E min = findMin();
|
||||
remove(min);
|
||||
return min;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class ElementNotFoundException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Element not found
|
||||
private final Object element;
|
||||
|
||||
/**
|
||||
* @param element Element that was not found.
|
||||
*/
|
||||
public ElementNotFoundException(Object element) {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The element that was not found.
|
||||
*/
|
||||
public Object getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "element not found: " + element;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class EmptyPriorityQueueException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public EmptyPriorityQueueException() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
/**
|
||||
* Interface representing a basic priority queue.
|
||||
*
|
||||
* Implementation should enforce the required complexity of each method.
|
||||
*
|
||||
*/
|
||||
public interface PriorityQueue<E extends Comparable<E>> {
|
||||
|
||||
/**
|
||||
* Check if the priority queue is empty.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return true if the queue is empty, false otherwise.
|
||||
*/
|
||||
public boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Get the number of elements in this queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return Current size (number of elements) of this queue.
|
||||
*/
|
||||
public int size();
|
||||
|
||||
/**
|
||||
* Insert the given element into the queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @param x Item to insert.
|
||||
*/
|
||||
public void insert(E x);
|
||||
|
||||
/**
|
||||
* Remove the given element from the priority queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @param x Item to remove.
|
||||
*/
|
||||
public void remove(E x) throws ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Retrieve (but not remove) the smallest item in the queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return The smallest item in the queue.
|
||||
*
|
||||
* @throws EmptyPriorityQueueException if this queue is empty.
|
||||
*/
|
||||
public E findMin() throws EmptyPriorityQueueException;
|
||||
|
||||
/**
|
||||
* Remove and return the smallest item from the priority queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return The smallest item in the queue.
|
||||
*
|
||||
* @throws EmptyPriorityQueueException if this queue is empty.
|
||||
*/
|
||||
public E deleteMin() throws EmptyPriorityQueueException;
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public interface WeaklyConnectedComponentObserver {
|
||||
|
||||
/**
|
||||
* Notify that the algorithm is entering a new component.
|
||||
*
|
||||
* @param curNode Starting node for the component.
|
||||
*/
|
||||
public void notifyStartComponent(Node curNode);
|
||||
|
||||
/**
|
||||
* Notify that a new node has been found for the current component.
|
||||
*
|
||||
* @param node New node found for the current component.
|
||||
*/
|
||||
public void notifyNewNodeInComponent(Node node);
|
||||
|
||||
/**
|
||||
* Notify that the algorithm has computed a new component.
|
||||
*
|
||||
* @param nodes List of nodes in the component.
|
||||
*/
|
||||
public void notifyEndComponent(ArrayList<Node> nodes);
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentTextObserver implements WeaklyConnectedComponentObserver {
|
||||
|
||||
// Number of the current component.
|
||||
private int numComponent = 1;
|
||||
|
||||
// Output stream
|
||||
PrintStream stream;
|
||||
|
||||
public WeaklyConnectedComponentTextObserver(PrintStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartComponent(Node curNode) {
|
||||
stream.print("Entering component #" + numComponent + " from node #" + curNode.getId() + "... ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewNodeInComponent(Node node) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEndComponent(ArrayList<Node> nodes) {
|
||||
stream.println(nodes.size() + " nodes found.");
|
||||
stream.flush();
|
||||
numComponent += 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentsAlgorithm
|
||||
extends AbstractAlgorithm<WeaklyConnectedComponentObserver> {
|
||||
|
||||
/**
|
||||
* @param data Input data for this algorithm.
|
||||
*/
|
||||
public WeaklyConnectedComponentsAlgorithm(WeaklyConnectedComponentsData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsSolution run() {
|
||||
return (WeaklyConnectedComponentsSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsData getInputData() {
|
||||
return (WeaklyConnectedComponentsData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the algorithm is entering a new component.
|
||||
*
|
||||
* @param curNode Starting node for the component.
|
||||
*/
|
||||
protected void notifyStartComponent(Node curNode) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyStartComponent(curNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a new node has been found for the current
|
||||
* component.
|
||||
*
|
||||
* @param node New node found for the current component.
|
||||
*/
|
||||
protected void notifyNewNodeInComponent(Node node) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyNewNodeInComponent(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the algorithm has computed a new component.
|
||||
*
|
||||
* @param nodes List of nodes in the component.
|
||||
*/
|
||||
protected void notifyEndComponent(ArrayList<Node> nodes) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyEndComponent(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An adjacency list for the undirected graph equivalent to the stored
|
||||
* graph.
|
||||
*/
|
||||
protected ArrayList<HashSet<Integer>> createUndirectedGraph() {
|
||||
int nNodes = getInputData().getGraph().size();
|
||||
ArrayList<HashSet<Integer>> res = new ArrayList<HashSet<Integer>>(nNodes);
|
||||
for (int i = 0; i < nNodes; ++i) {
|
||||
res.add(new HashSet<Integer>());
|
||||
}
|
||||
|
||||
for (Node node: getInputData().getGraph().getNodes()) {
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
res.get(node.getId()).add(arc.getDestination().getId());
|
||||
if (arc.getRoadInformation().isOneWay()) {
|
||||
res.get(arc.getDestination().getId()).add(node.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a breadth first search algorithm on the given undirected graph
|
||||
* (adjacency list), starting at node cur, and marking nodes in marked.
|
||||
*
|
||||
* @param marked
|
||||
* @param cur
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected ArrayList<Node> bfs(ArrayList<HashSet<Integer>> ugraph, boolean[] marked, int cur) {
|
||||
Graph graph = getInputData().getGraph();
|
||||
ArrayList<Node> component = new ArrayList<Node>();
|
||||
|
||||
// Using a queue because we are doing a BFS
|
||||
Queue<Integer> queue = new LinkedList<Integer>();
|
||||
|
||||
// Notify observers about the current component.
|
||||
notifyStartComponent(graph.get(cur));
|
||||
|
||||
// Add original node and loop until the queue is empty.
|
||||
queue.add(cur);
|
||||
marked[cur] = true;
|
||||
while (!queue.isEmpty()) {
|
||||
Node node = graph.get(queue.remove());
|
||||
component.add(node);
|
||||
|
||||
// Notify observers
|
||||
notifyNewNodeInComponent(node);
|
||||
|
||||
for (Integer destId: ugraph.get(node.getId())) {
|
||||
Node dest = graph.get(destId);
|
||||
if (!marked[dest.getId()]) {
|
||||
queue.add(destId);
|
||||
marked[destId] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyEndComponent(component);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WeaklyConnectedComponentsSolution doRun() {
|
||||
|
||||
Graph graph = getInputData().getGraph();
|
||||
ArrayList<HashSet<Integer>> ugraph = createUndirectedGraph();
|
||||
boolean[] marked = new boolean[graph.size()];
|
||||
Arrays.fill(marked, false);
|
||||
|
||||
ArrayList<ArrayList<Node>> components = new ArrayList<ArrayList<Node>>();
|
||||
|
||||
// perform algorithm
|
||||
int cur = 0;
|
||||
while (cur < marked.length) {
|
||||
// Apply BFS
|
||||
components.add(this.bfs(ugraph, marked, cur));
|
||||
|
||||
// Find next non-marked
|
||||
for (; cur < marked.length && marked[cur]; ++cur)
|
||||
;
|
||||
}
|
||||
|
||||
return new WeaklyConnectedComponentsSolution(getInputData(), Status.OPTIMAL, components);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class WeaklyConnectedComponentsData extends AbstractInputData {
|
||||
|
||||
/**
|
||||
* @param graph Graph for which components should be retrieved.
|
||||
*/
|
||||
public WeaklyConnectedComponentsData(Graph graph) {
|
||||
super(graph, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Weakly-connected components from #0.";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentsSolution extends AbstractSolution {
|
||||
|
||||
// Components
|
||||
private ArrayList<ArrayList<Node>> components;
|
||||
|
||||
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data, Status status,
|
||||
ArrayList<ArrayList<Node>> components) {
|
||||
super(data, status);
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsData getInputData() {
|
||||
return (WeaklyConnectedComponentsData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Components of the solution, if any.
|
||||
*/
|
||||
public ArrayList<ArrayList<Node>> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
int nIsolated = 0;
|
||||
int nGt10 = 0;
|
||||
for (ArrayList<Node> component: components) {
|
||||
if (component.size() == 1) {
|
||||
nIsolated += 1;
|
||||
}
|
||||
else if (component.size() > 10) {
|
||||
nGt10 += 1;
|
||||
}
|
||||
}
|
||||
return "Found " + components.size() + " components (" + nGt10 + " with more than 10 nodes, "
|
||||
+ nIsolated + " isolated nodes) in " + getSolvingTime().getSeconds() + " seconds.";
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class BinaryHeapTest extends PriorityQueueTest {
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue() {
|
||||
return new BinaryHeap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
|
||||
return new BinaryHeap<>((BinaryHeap<MutableInteger>) queue);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class BinarySearchTreeTest extends PriorityQueueTest {
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue() {
|
||||
return new BinarySearchTree<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
|
||||
return new BinarySearchTree<>((BinarySearchTree<MutableInteger>) queue);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public abstract class PriorityQueueTest {
|
||||
|
||||
/**
|
||||
* Needs to be implemented by child class to actually provide priority queue
|
||||
* implementation.
|
||||
*
|
||||
* @return A new instance of a PriorityQueue implementation.
|
||||
*/
|
||||
public abstract PriorityQueue<MutableInteger> createQueue();
|
||||
|
||||
/**
|
||||
* Needs to be implemented by child class to actually provide priority queue
|
||||
* implementation.
|
||||
*
|
||||
* @param queue Queue to copy.
|
||||
*
|
||||
* @return Copy of the given queue.
|
||||
*/
|
||||
public abstract PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue);
|
||||
|
||||
protected static class MutableInteger implements Comparable<MutableInteger> {
|
||||
|
||||
// Actual value
|
||||
private int value;
|
||||
|
||||
public MutableInteger(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The integer value stored inside this MutableInteger.
|
||||
*/
|
||||
public int get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the integer value stored inside this MutableInteger.
|
||||
*
|
||||
* @param value New value to set.
|
||||
*/
|
||||
public void set(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MutableInteger other) {
|
||||
return Integer.compare(this.value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(get());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
protected static class TestParameters<E extends Comparable<E>> {
|
||||
|
||||
// Data to insert
|
||||
public final E[] data;
|
||||
public final int[] deleteOrder;
|
||||
|
||||
public TestParameters(E[] data, int[] deleteOrder) {
|
||||
this.data = data;
|
||||
this.deleteOrder = deleteOrder;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Set of parameters.
|
||||
*
|
||||
*/
|
||||
@Parameters
|
||||
public static Collection<Object> data() {
|
||||
Collection<Object> objects = new ArrayList<>();
|
||||
|
||||
// Empty queue
|
||||
objects.add(new TestParameters<>(new MutableInteger[0], new int[0]));
|
||||
|
||||
// Queue with 50 elements from 0 to 49, inserted in order and deleted in order.
|
||||
objects.add(new TestParameters<>(
|
||||
IntStream.range(0, 50).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
IntStream.range(0, 50).toArray()));
|
||||
|
||||
// Queue with 20 elements from 0 to 19, inserted in order, deleted in the given
|
||||
// order.
|
||||
objects.add(new TestParameters<>(
|
||||
IntStream.range(0, 20).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, 6, 1,
|
||||
11 }));
|
||||
|
||||
// Queue with 7 elements.
|
||||
objects.add(
|
||||
new TestParameters<>(
|
||||
Arrays.stream(new int[] { 8, 1, 6, 3, 4, 5, 9 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 6, 5, 0, 1, 4, 2, 3 }));
|
||||
|
||||
// Queue with 7 elements.
|
||||
objects.add(
|
||||
new TestParameters<>(
|
||||
Arrays.stream(new int[] { 1, 7, 4, 8, 9, 6, 5 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 2, 0, 1, 3, 4, 5, 6 }));
|
||||
|
||||
// Queue with 13 elements.
|
||||
objects.add(new TestParameters<>(
|
||||
Arrays.stream(new int[] { 1, 7, 2, 8, 9, 3, 4, 10, 11, 12, 13, 5, 6 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 3, 4, 0, 2, 5, 6, 1, 7, 8, 9, 10, 11, 12 }));
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
@Parameter
|
||||
public TestParameters<MutableInteger> parameters;
|
||||
|
||||
// Actual queue.
|
||||
private PriorityQueue<MutableInteger> queue;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
// Create the range queue
|
||||
this.queue = createQueue();
|
||||
|
||||
for (MutableInteger v: parameters.data) {
|
||||
this.queue.insert(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
assertEquals(parameters.data.length == 0, this.queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
assertEquals(parameters.data.length, this.queue.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsert() {
|
||||
PriorityQueue<MutableInteger> queue = createQueue();
|
||||
int size = 0;
|
||||
for (MutableInteger x: parameters.data) {
|
||||
queue.insert(x);
|
||||
assertEquals(++size, queue.size());
|
||||
}
|
||||
assertEquals(parameters.data.length, queue.size());
|
||||
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
|
||||
Arrays.sort(range);
|
||||
|
||||
for (MutableInteger mi: range) {
|
||||
assertEquals(mi.get(), queue.deleteMin().value);
|
||||
assertEquals(--size, queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = EmptyPriorityQueueException.class)
|
||||
public void testEmptyFindMin() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.findMin();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindMin() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
assertEquals(Collections.min(Arrays.asList(parameters.data)).get(), queue.findMin().get());
|
||||
}
|
||||
|
||||
@Test(expected = EmptyPriorityQueueException.class)
|
||||
public void testEmptyDeleteMin() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.deleteMin();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMin() {
|
||||
int size = parameters.data.length;
|
||||
assertEquals(size, queue.size());
|
||||
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
|
||||
Arrays.sort(range);
|
||||
for (MutableInteger x: range) {
|
||||
assertEquals(x, queue.deleteMin());
|
||||
size -= 1;
|
||||
assertEquals(size, queue.size());
|
||||
}
|
||||
assertEquals(0, queue.size());
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test(expected = ElementNotFoundException.class)
|
||||
public void testRemoveEmpty() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.remove(new MutableInteger(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveNotFound() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
List<MutableInteger> data = Arrays.asList(parameters.data);
|
||||
MutableInteger min = new MutableInteger(Collections.min(data).get() - 1),
|
||||
max = new MutableInteger(Collections.max(data).get() + 1);
|
||||
try {
|
||||
queue.remove(min);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(min, e.getElement());
|
||||
}
|
||||
try {
|
||||
queue.remove(max);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(max, e.getElement());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteThenRemove() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
while (!queue.isEmpty()) {
|
||||
MutableInteger min = queue.deleteMin();
|
||||
try {
|
||||
queue.remove(min);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(min, e.getElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveTwice() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
for (MutableInteger data: parameters.data) {
|
||||
PriorityQueue<MutableInteger> copyQueue = this.createQueue(this.queue);
|
||||
copyQueue.remove(data);
|
||||
try {
|
||||
copyQueue.remove(data);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(data, e.getElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
int size1 = queue.size();
|
||||
for (int i = 0; i < parameters.deleteOrder.length; ++i) {
|
||||
// Remove from structure
|
||||
queue.remove(parameters.data[parameters.deleteOrder[i]]);
|
||||
|
||||
// Copy the remaining elements
|
||||
PriorityQueue<MutableInteger> copyTree = createQueue(queue);
|
||||
|
||||
// Retrieve all remaining elements in both structures
|
||||
ArrayList<MutableInteger> remains_in = new ArrayList<>(),
|
||||
remains_cp = new ArrayList<>();
|
||||
for (int j = i + 1; j < parameters.deleteOrder.length; ++j) {
|
||||
remains_in.add(parameters.data[parameters.deleteOrder[j]]);
|
||||
remains_cp.add(copyTree.deleteMin());
|
||||
}
|
||||
|
||||
Collections.sort(remains_in);
|
||||
|
||||
// Check that the copy is now empty, and that both list contains all
|
||||
// elements.
|
||||
assertTrue(copyTree.isEmpty());
|
||||
assertEquals(remains_in, remains_cp);
|
||||
|
||||
// Check that the size of the original tree is correct.
|
||||
assertEquals(--size1, queue.size());
|
||||
}
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveThenAdd() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
int min = Collections.min(Arrays.asList(parameters.data)).get();
|
||||
for (MutableInteger mi: parameters.data) {
|
||||
queue.remove(mi);
|
||||
assertEquals(parameters.data.length - 1, queue.size());
|
||||
mi.set(--min);
|
||||
queue.insert(mi);
|
||||
assertEquals(parameters.data.length, queue.size());
|
||||
assertEquals(min, queue.findMin().get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
87
be-graphes-gui/pom.xml
Normal file
87
be-graphes-gui/pom.xml
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-all</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<mapsforge.version>0.13.0</mapsforge.version>
|
||||
</properties>
|
||||
|
||||
<artifactId>be-graphes-gui</artifactId>
|
||||
<name>be-graphes-gui</name>
|
||||
|
||||
<!-- Repository for Salamander (dependency of Mapsforge). -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-algos</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-core -->
|
||||
<dependency>
|
||||
<groupId>net.sf.kxml</groupId>
|
||||
<artifactId>kxml2</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-themes</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-awt -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map-awt</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-themes</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map-reader</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,381 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.algorithm.AlgorithmFactory;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.algorithm.ArcInspectorFactory;
|
||||
import org.insa.graphs.gui.NodesInputPanel.InputChangedEvent;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.utils.ColorUtils;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class AlgorithmPanel extends JPanel implements DrawingChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public class StartActionEvent extends ActionEvent {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 4090710269781229078L;
|
||||
|
||||
protected static final String START_EVENT_COMMAND = "allInputFilled";
|
||||
|
||||
protected static final int START_EVENT_ID = 0x1;
|
||||
|
||||
private final List<Node> nodes;
|
||||
private final Class<? extends AbstractAlgorithm<?>> algoClass;
|
||||
|
||||
private final ArcInspector arcFilter;
|
||||
|
||||
private final boolean graphicVisualization;
|
||||
private final boolean textualVisualization;
|
||||
|
||||
public StartActionEvent(Class<? extends AbstractAlgorithm<?>> algoClass, List<Node> nodes,
|
||||
ArcInspector arcFilter, boolean graphicVisualization,
|
||||
boolean textualVisualization) {
|
||||
super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND);
|
||||
this.nodes = nodes;
|
||||
this.algoClass = algoClass;
|
||||
this.graphicVisualization = graphicVisualization;
|
||||
this.textualVisualization = textualVisualization;
|
||||
this.arcFilter = arcFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Nodes associated with this event.
|
||||
*/
|
||||
public List<Node> getNodes() {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Arc filter associated with this event.
|
||||
*/
|
||||
public ArcInspector getArcFilter() {
|
||||
return this.arcFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Algorithm class associated with this event.
|
||||
*/
|
||||
public Class<? extends AbstractAlgorithm<?>> getAlgorithmClass() {
|
||||
return this.algoClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if graphic visualization is enabled.
|
||||
*/
|
||||
public boolean isGraphicVisualizationEnabled() {
|
||||
return this.graphicVisualization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if textual visualization is enabled.
|
||||
*/
|
||||
public boolean isTextualVisualizationEnabled() {
|
||||
return this.textualVisualization;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Input panels for node.
|
||||
protected NodesInputPanel nodesInputPanel;
|
||||
|
||||
// Solution
|
||||
protected SolutionPanel solutionPanel;
|
||||
|
||||
// Component that can be enabled/disabled.
|
||||
private ArrayList<JComponent> components = new ArrayList<>();
|
||||
|
||||
// Graphic / Text checkbox observer
|
||||
private final JCheckBox graphicObserverCheckbox, textualObserverCheckbox;
|
||||
|
||||
private JButton startAlgoButton;
|
||||
|
||||
// Start listeners
|
||||
List<ActionListener> startActionListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new AlgorithmPanel with the given parameters.
|
||||
*
|
||||
* @param parent Parent component for this panel. Only use for centering
|
||||
* dialogs.
|
||||
* @param baseAlgorithm Base algorithm for this algorithm panel.
|
||||
* @param title Title of the panel.
|
||||
* @param nodeNames Names of the input nodes.
|
||||
* @param enableArcFilterSelection <code>true</code> to enable
|
||||
* {@link ArcInspector} selection.
|
||||
*
|
||||
* @see ArcInspectorFactory
|
||||
*/
|
||||
public AlgorithmPanel(Component parent, Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
|
||||
String title, String[] nodeNames, boolean enableArcFilterSelection) {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
|
||||
setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
|
||||
// Set title.
|
||||
add(createTitleLabel(title));
|
||||
add(Box.createVerticalStrut(8));
|
||||
|
||||
// Add algorithm selection
|
||||
JComboBox<String> algoSelect = createAlgoritmSelectComboBox(baseAlgorithm);
|
||||
if (algoSelect.getItemCount() > 1) {
|
||||
add(algoSelect);
|
||||
components.add(algoSelect);
|
||||
}
|
||||
|
||||
// Add inputs for node.
|
||||
this.nodesInputPanel = createNodesInputPanel(nodeNames);
|
||||
add(this.nodesInputPanel);
|
||||
components.add(this.nodesInputPanel);
|
||||
|
||||
JComboBox<ArcInspector> arcFilterSelect = new JComboBox<>(
|
||||
ArcInspectorFactory.getAllFilters().toArray(new ArcInspector[0]));
|
||||
arcFilterSelect.setBackground(Color.WHITE);
|
||||
|
||||
// Add mode selection
|
||||
JPanel modeAndObserverPanel = new JPanel();
|
||||
modeAndObserverPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
modeAndObserverPanel.setLayout(new GridBagLayout());
|
||||
|
||||
graphicObserverCheckbox = new JCheckBox("Graphic");
|
||||
graphicObserverCheckbox.setSelected(true);
|
||||
textualObserverCheckbox = new JCheckBox("Textual");
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
c.gridy = 2;
|
||||
c.gridx = 0;
|
||||
c.weightx = 0;
|
||||
modeAndObserverPanel.add(new JLabel("Visualization: "), c);
|
||||
c.gridx = 1;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(graphicObserverCheckbox, c);
|
||||
c.gridx = 2;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(textualObserverCheckbox, c);
|
||||
|
||||
if (enableArcFilterSelection) {
|
||||
c.gridy = 1;
|
||||
c.gridx = 0;
|
||||
c.weightx = 0;
|
||||
modeAndObserverPanel.add(new JLabel("Mode: "), c);
|
||||
c.gridx = 1;
|
||||
c.gridwidth = 2;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(arcFilterSelect, c);
|
||||
}
|
||||
|
||||
components.add(arcFilterSelect);
|
||||
components.add(textualObserverCheckbox);
|
||||
|
||||
add(modeAndObserverPanel);
|
||||
|
||||
solutionPanel = new SolutionPanel(parent);
|
||||
solutionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
solutionPanel.setVisible(false);
|
||||
add(Box.createVerticalStrut(10));
|
||||
add(solutionPanel);
|
||||
|
||||
// Bottom panel
|
||||
JPanel bottomPanel = new JPanel();
|
||||
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
|
||||
|
||||
startAlgoButton = new JButton("Start");
|
||||
startAlgoButton.setEnabled(false);
|
||||
startAlgoButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
for (ActionListener lis: startActionListeners) {
|
||||
lis.actionPerformed(new StartActionEvent(
|
||||
AlgorithmFactory.getAlgorithmClass(baseAlgorithm,
|
||||
(String) algoSelect.getSelectedItem()),
|
||||
nodesInputPanel.getNodeForInputs(),
|
||||
(ArcInspector) arcFilterSelect.getSelectedItem(),
|
||||
graphicObserverCheckbox.isSelected(),
|
||||
textualObserverCheckbox.isSelected()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JButton hideButton = new JButton("Hide");
|
||||
hideButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
nodesInputPanel.setEnabled(false);
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
bottomPanel.add(startAlgoButton);
|
||||
bottomPanel.add(Box.createHorizontalGlue());
|
||||
bottomPanel.add(hideButton);
|
||||
|
||||
components.add(hideButton);
|
||||
|
||||
bottomPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
add(Box.createVerticalStrut(8));
|
||||
add(bottomPanel);
|
||||
|
||||
nodesInputPanel.addInputChangedListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
InputChangedEvent evt = (InputChangedEvent) e;
|
||||
startAlgoButton.setEnabled(allNotNull(evt.getNodes()));
|
||||
}
|
||||
});
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
setEnabled(true);
|
||||
nodesInputPanel.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
setEnabled(false);
|
||||
nodesInputPanel.setVisible(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the title JLabel for this panel.
|
||||
*
|
||||
* @param title Title for the label.
|
||||
*
|
||||
* @return A new JLabel containing the given title with proper font.
|
||||
*/
|
||||
protected JLabel createTitleLabel(String title) {
|
||||
JLabel titleLabel = new JLabel(title);
|
||||
titleLabel.setBackground(Color.RED);
|
||||
titleLabel.setHorizontalAlignment(JLabel.LEFT);
|
||||
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
Font font = titleLabel.getFont();
|
||||
font = font.deriveFont(Font.BOLD, 18);
|
||||
titleLabel.setFont(font);
|
||||
return titleLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the combo box for the algorithm selection.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm for which the select box should be
|
||||
* created.
|
||||
*
|
||||
* @return A new JComboBox containing algorithms for the given base algorithm.
|
||||
*
|
||||
* @see AlgorithmFactory
|
||||
*/
|
||||
protected JComboBox<String> createAlgoritmSelectComboBox(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
|
||||
JComboBox<String> algoSelect = new JComboBox<>(
|
||||
AlgorithmFactory.getAlgorithmNames(baseAlgorithm).toArray(new String[0]));
|
||||
algoSelect.setBackground(Color.WHITE);
|
||||
algoSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
return algoSelect;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node input panel with the given node input names.
|
||||
*
|
||||
* @param nodeNames Field names for the inputs to create.
|
||||
*
|
||||
* @return A new NodesInputPanel containing inputs for the given names.
|
||||
*/
|
||||
protected NodesInputPanel createNodesInputPanel(String[] nodeNames) {
|
||||
NodesInputPanel panel = new NodesInputPanel();
|
||||
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
for (int i = 0; i < nodeNames.length; ++i) {
|
||||
panel.addTextField(nodeNames[i] + ": ", ColorUtils.getColor(i));
|
||||
}
|
||||
panel.setEnabled(false);
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given list of nodes does not contain any <code>null</code>
|
||||
* value.
|
||||
*
|
||||
* @param nodes List of {@link Node} to check.
|
||||
*
|
||||
* @return <code>true</code> if the list does not contain any <code>null</code>
|
||||
* value, <code>false</code> otherwise.
|
||||
*/
|
||||
protected boolean allNotNull(List<Node> nodes) {
|
||||
boolean allNotNull = true;
|
||||
for (Node node: nodes) {
|
||||
allNotNull = allNotNull && node != null;
|
||||
}
|
||||
return allNotNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
nodesInputPanel.setEnabled(enabled);
|
||||
solutionPanel.setEnabled(enabled);
|
||||
for (JComponent component: components) {
|
||||
component.setEnabled(enabled);
|
||||
}
|
||||
graphicObserverCheckbox.setEnabled(enabled);
|
||||
enabled = enabled && allNotNull(this.nodesInputPanel.getNodeForInputs());
|
||||
startAlgoButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new start action listener to this class.
|
||||
*
|
||||
* @param listener Listener to add.
|
||||
*/
|
||||
public void addStartActionListener(ActionListener listener) {
|
||||
this.startActionListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
public class BlockingActionFactory {
|
||||
|
||||
// List of running actions.
|
||||
private ArrayList<RunningAction> actions = new ArrayList<>();
|
||||
|
||||
// Parent component.
|
||||
private Component parentComponent;
|
||||
|
||||
public BlockingActionFactory(Component parentComponent) {
|
||||
this.parentComponent = parentComponent;
|
||||
}
|
||||
|
||||
public void addAction(RunningAction action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
public ActionListener createBlockingAction(ActionListener listener) {
|
||||
return new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean accepted = true;
|
||||
|
||||
// Check if actions...
|
||||
for (int i = 0; i < actions.size() && accepted; ++i) {
|
||||
RunningAction action = actions.get(i);
|
||||
// If action is running, ask user...
|
||||
if (action.isRunning()) {
|
||||
if (JOptionPane.showConfirmDialog(parentComponent, "Action {"
|
||||
+ action.getInformation()
|
||||
+ "} is running, do you want to stop it?") == JOptionPane.OK_OPTION) {
|
||||
action.interrupt();
|
||||
}
|
||||
else {
|
||||
accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If action is accepted, run it...
|
||||
if (accepted) {
|
||||
listener.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
|
||||
public interface DrawingChangeListener {
|
||||
|
||||
/**
|
||||
* Event fired when a new drawing is loaded.
|
||||
*
|
||||
* @param oldDrawing Old drawing, may be null if no drawing exits prior to this
|
||||
* one.
|
||||
* @param newDrawing New drawing.
|
||||
*/
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing);
|
||||
|
||||
/**
|
||||
* Event fired when a redraw request is emitted - This is typically emitted
|
||||
* after a onDrawingLoaded event, but not always, and request that elements are
|
||||
* drawn again on the new drawing.
|
||||
*
|
||||
*/
|
||||
public void onRedrawRequest();
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public interface GraphChangeListener {
|
||||
|
||||
/**
|
||||
* Event fire when a new graph has been loaded.
|
||||
*
|
||||
* @param graph The new graph.
|
||||
*/
|
||||
public void newGraphLoaded(Graph graph);
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.RoadInformation;
|
||||
import org.insa.graphs.model.io.GraphReaderObserver;
|
||||
|
||||
/**
|
||||
* One-time use GraphReaderObserver that display progress in three different
|
||||
* JProgressBar.
|
||||
*
|
||||
* @author Mikael
|
||||
*
|
||||
*/
|
||||
public class GraphReaderProgressBar extends JDialog implements GraphReaderObserver {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
// Index...
|
||||
private static final int NODE = 0, DESC = 1, ARC = 2;
|
||||
|
||||
// Progress bar
|
||||
private final JProgressBar[] progressBars = new JProgressBar[3];
|
||||
|
||||
// Current element read, and modulo.
|
||||
private int[] counters = new int[]{ 0, 0, 0 };
|
||||
private int[] modulos = new int[3];
|
||||
|
||||
public GraphReaderProgressBar(JFrame owner) {
|
||||
super(owner);
|
||||
this.setVisible(false);
|
||||
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
final String[] infos = { "nodes", "road informations", "arcs" };
|
||||
|
||||
JPanel pane = new JPanel();
|
||||
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
|
||||
pane.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
pane.add(Box.createVerticalGlue());
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
JLabel label = new JLabel("Reading " + infos[i] + "... ");
|
||||
label.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
progressBars[i] = new JProgressBar();
|
||||
progressBars[i].setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
pane.add(label);
|
||||
pane.add(progressBars[i]);
|
||||
}
|
||||
|
||||
pane.add(Box.createVerticalGlue());
|
||||
setContentPane(pane);
|
||||
pack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReading(String mapId) {
|
||||
setTitle("Reading graph " + mapId + "... ");
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEndReading() {
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
protected void initProgressBar(int index, int max) {
|
||||
progressBars[index].setMaximum(max);
|
||||
modulos[index] = Math.max(max / 100, 1);
|
||||
}
|
||||
|
||||
protected void incCounter(int index) {
|
||||
counters[index] += 1;
|
||||
if (counters[index] % modulos[index] == 0) {
|
||||
progressBars[index].setValue(counters[index]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingNodes(int nNodes) {
|
||||
initProgressBar(NODE, nNodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewNodeRead(Node node) {
|
||||
incCounter(NODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingDescriptors(int nDesc) {
|
||||
initProgressBar(DESC, nDesc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewDescriptorRead(RoadInformation desc) {
|
||||
incCounter(DESC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingArcs(int nArcs) {
|
||||
initProgressBar(ARC, nArcs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewArcRead(Arc arc) {
|
||||
incCounter(ARC);
|
||||
}
|
||||
|
||||
}
|
862
be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java
Normal file
862
be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java
Normal file
|
@ -0,0 +1,862 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
import org.insa.graphs.algorithm.AlgorithmFactory;
|
||||
import org.insa.graphs.algorithm.carpooling.CarPoolingAlgorithm;
|
||||
import org.insa.graphs.algorithm.packageswitch.PackageSwitchAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathTextObserver;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentTextObserver;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsData;
|
||||
import org.insa.graphs.gui.AlgorithmPanel.StartActionEvent;
|
||||
import org.insa.graphs.gui.drawing.BasicGraphPalette;
|
||||
import org.insa.graphs.gui.drawing.BlackAndWhiteGraphPalette;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.GraphPalette;
|
||||
import org.insa.graphs.gui.drawing.components.BasicDrawing;
|
||||
import org.insa.graphs.gui.drawing.components.MapViewDrawing;
|
||||
import org.insa.graphs.gui.observers.ShortestPathGraphicObserver;
|
||||
import org.insa.graphs.gui.observers.WeaklyConnectedComponentGraphicObserver;
|
||||
import org.insa.graphs.gui.utils.FileUtils;
|
||||
import org.insa.graphs.gui.utils.FileUtils.FolderType;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.io.BinaryGraphReader;
|
||||
import org.insa.graphs.model.io.BinaryPathReader;
|
||||
import org.insa.graphs.model.io.GraphReader;
|
||||
import org.insa.graphs.model.io.MapMismatchException;
|
||||
|
||||
public class MainWindow extends JFrame {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final String WINDOW_TITLE = "BE Graphes INSA";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final int THREAD_TIMER_DELAY = 1000; // in milliseconds
|
||||
|
||||
// Current graph.
|
||||
protected Graph graph;
|
||||
|
||||
// Path to the last opened graph file.
|
||||
private String graphFilePath;
|
||||
|
||||
// Drawing and click adapter.
|
||||
protected Drawing drawing;
|
||||
private final MapViewDrawing mapViewDrawing;
|
||||
private final BasicDrawing basicDrawing;
|
||||
|
||||
private final GraphPalette basicPalette, blackAndWhitePalette;
|
||||
private GraphPalette currentPalette;
|
||||
|
||||
// Main panel.
|
||||
private final JSplitPane mainPanel;
|
||||
|
||||
// Algorithm panels
|
||||
private final List<AlgorithmPanel> algoPanels = new ArrayList<>();
|
||||
private final AlgorithmPanel wccPanel, spPanel, cpPanel, psPanel;
|
||||
|
||||
// Path panel
|
||||
private final PathsPanel pathPanel;
|
||||
|
||||
// List of items that cannot be used without a graph
|
||||
private final ArrayList<JMenuItem> graphLockItems = new ArrayList<JMenuItem>();
|
||||
|
||||
// Label containing the map ID of the current graph.
|
||||
private JLabel graphInfoPanel;
|
||||
|
||||
// Thread information
|
||||
private Timer threadTimer;
|
||||
private JPanel threadPanel;
|
||||
|
||||
// Log stream and print stream
|
||||
private StreamCapturer logStream;
|
||||
|
||||
private PrintStream printStream;
|
||||
|
||||
// Current running thread
|
||||
private ThreadWrapper currentThread;
|
||||
|
||||
// Factory
|
||||
private BlockingActionFactory baf;
|
||||
|
||||
// Observers
|
||||
private List<DrawingChangeListener> drawingChangeListeners = new ArrayList<>();
|
||||
private List<GraphChangeListener> graphChangeListeneres = new ArrayList<>();
|
||||
|
||||
public MainWindow() {
|
||||
super(WINDOW_TITLE);
|
||||
|
||||
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
setMinimumSize(new Dimension(800, 600));
|
||||
|
||||
// Create drawing and action listeners...
|
||||
this.basicDrawing = new BasicDrawing();
|
||||
this.mapViewDrawing = new MapViewDrawing();
|
||||
this.drawing = basicDrawing;
|
||||
|
||||
// Createa palettes
|
||||
this.basicPalette = new BasicGraphPalette();
|
||||
this.blackAndWhitePalette = new BlackAndWhiteGraphPalette();
|
||||
this.currentPalette = this.basicPalette;
|
||||
|
||||
wccPanel = new AlgorithmPanel(this, WeaklyConnectedComponentsAlgorithm.class,
|
||||
"Weakly-Connected Components", new String[] {}, false);
|
||||
wccPanel.addStartActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StartActionEvent evt = (StartActionEvent) e;
|
||||
WeaklyConnectedComponentsData data = new WeaklyConnectedComponentsData(graph);
|
||||
|
||||
WeaklyConnectedComponentsAlgorithm wccAlgorithm = null;
|
||||
try {
|
||||
wccAlgorithm = (WeaklyConnectedComponentsAlgorithm) AlgorithmFactory
|
||||
.createAlgorithm(evt.getAlgorithmClass(), data);
|
||||
}
|
||||
catch (Exception e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"An error occurred while creating the specified algorithm.",
|
||||
"Internal error: Algorithm instantiation failure",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
wccPanel.setEnabled(false);
|
||||
|
||||
if (evt.isGraphicVisualizationEnabled()) {
|
||||
wccAlgorithm.addObserver(new WeaklyConnectedComponentGraphicObserver(drawing));
|
||||
}
|
||||
if (evt.isTextualVisualizationEnabled()) {
|
||||
wccAlgorithm.addObserver(new WeaklyConnectedComponentTextObserver(printStream));
|
||||
}
|
||||
|
||||
// We love Java...
|
||||
final WeaklyConnectedComponentsAlgorithm copyAlgorithm = wccAlgorithm;
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AbstractSolution solution = copyAlgorithm.run();
|
||||
wccPanel.solutionPanel.addSolution(solution, false);
|
||||
wccPanel.solutionPanel.setVisible(true);
|
||||
wccPanel.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class, "Shortest-Path",
|
||||
new String[] { "Origin", "Destination" }, true);
|
||||
spPanel.addStartActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StartActionEvent evt = (StartActionEvent) e;
|
||||
ShortestPathData data = new ShortestPathData(graph, evt.getNodes().get(0),
|
||||
evt.getNodes().get(1), evt.getArcFilter());
|
||||
|
||||
ShortestPathAlgorithm spAlgorithm = null;
|
||||
try {
|
||||
spAlgorithm = (ShortestPathAlgorithm) AlgorithmFactory
|
||||
.createAlgorithm(evt.getAlgorithmClass(), data);
|
||||
}
|
||||
catch (Exception e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"An error occurred while creating the specified algorithm.",
|
||||
"Internal error: Algorithm instantiation failure",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
spPanel.setEnabled(false);
|
||||
|
||||
if (evt.isGraphicVisualizationEnabled()) {
|
||||
spAlgorithm.addObserver(new ShortestPathGraphicObserver(drawing));
|
||||
}
|
||||
if (evt.isTextualVisualizationEnabled()) {
|
||||
spAlgorithm.addObserver(new ShortestPathTextObserver(printStream));
|
||||
}
|
||||
|
||||
final ShortestPathAlgorithm copyAlgorithm = spAlgorithm;
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Run the algorithm.
|
||||
ShortestPathSolution solution = copyAlgorithm.run();
|
||||
// Add the solution to the solution panel (but do not display
|
||||
// overlay).
|
||||
spPanel.solutionPanel.addSolution(solution, false);
|
||||
// If the solution is feasible, add the path to the path panel.
|
||||
if (solution.isFeasible()) {
|
||||
pathPanel.addPath(solution.getPath());
|
||||
}
|
||||
// Show the solution panel and enable the shortest-path panel.
|
||||
spPanel.solutionPanel.setVisible(true);
|
||||
spPanel.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cpPanel = new AlgorithmPanel(this, CarPoolingAlgorithm.class, "Car-Pooling", new String[] {
|
||||
"Origin Car", "Origin Pedestrian", "Destination Car", "Destination Pedestrian" },
|
||||
true);
|
||||
|
||||
psPanel = new AlgorithmPanel(this, PackageSwitchAlgorithm.class, "Car-Pooling",
|
||||
new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true);
|
||||
|
||||
// add algorithm panels
|
||||
algoPanels.add(wccPanel);
|
||||
algoPanels.add(spPanel);
|
||||
algoPanels.add(cpPanel);
|
||||
algoPanels.add(psPanel);
|
||||
|
||||
this.pathPanel = new PathsPanel(this);
|
||||
|
||||
// Add click listeners to both drawing.
|
||||
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
this.basicDrawing.addDrawingClickListener(panel.nodesInputPanel);
|
||||
this.mapViewDrawing.addDrawingClickListener(panel.nodesInputPanel);
|
||||
this.graphChangeListeneres.add(panel.nodesInputPanel);
|
||||
this.graphChangeListeneres.add(panel.solutionPanel);
|
||||
this.drawingChangeListeners.add(panel.nodesInputPanel);
|
||||
this.drawingChangeListeners.add(panel.solutionPanel);
|
||||
this.drawingChangeListeners.add(panel);
|
||||
}
|
||||
|
||||
this.graphChangeListeneres.add(pathPanel);
|
||||
this.drawingChangeListeners.add(pathPanel);
|
||||
|
||||
// Create action factory.
|
||||
this.currentThread = new ThreadWrapper(this);
|
||||
this.baf = new BlockingActionFactory(this);
|
||||
this.baf.addAction(currentThread);
|
||||
|
||||
// Click adapter
|
||||
ActionListener openMapActionListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser chooser = FileUtils.createFileChooser(FolderType.Map);
|
||||
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
|
||||
graphFilePath = chooser.getSelectedFile().getAbsolutePath();
|
||||
|
||||
// Note: Don't use a try-resources block since loadGraph is asynchronous.
|
||||
final DataInputStream stream;
|
||||
try {
|
||||
stream = new DataInputStream(new BufferedInputStream(
|
||||
new FileInputStream(chooser.getSelectedFile())));
|
||||
}
|
||||
catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Cannot open the selected file.");
|
||||
return;
|
||||
}
|
||||
loadGraph(new BinaryGraphReader(stream));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setJMenuBar(createMenuBar(openMapActionListener));
|
||||
|
||||
// Initial panel to show "Open Map... "
|
||||
JPanel openPanel = new JPanel();
|
||||
openPanel.setLayout(new BoxLayout(openPanel, BoxLayout.PAGE_AXIS));
|
||||
JButton openButton = new JButton("Open Map... ");
|
||||
openButton.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
openButton.addActionListener(openMapActionListener);
|
||||
openButton.setFocusPainted(false);
|
||||
openPanel.add(Box.createVerticalGlue());
|
||||
openPanel.add(openButton);
|
||||
openPanel.add(Box.createVerticalGlue());
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent e) {
|
||||
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
|
||||
"Are you sure you want to close the application?", "Exit Confirmation",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
|
||||
if (confirmed == JOptionPane.YES_OPTION) {
|
||||
dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create graph area
|
||||
mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
|
||||
JTextArea infoPanel = new JTextArea();
|
||||
infoPanel.setMinimumSize(new Dimension(200, 50));
|
||||
infoPanel.setBackground(Color.WHITE);
|
||||
infoPanel.setLineWrap(true);
|
||||
infoPanel.setEditable(false);
|
||||
this.logStream = new StreamCapturer(infoPanel);
|
||||
this.printStream = new PrintStream(this.logStream);
|
||||
|
||||
JPanel rightComponent = new JPanel();
|
||||
rightComponent.setLayout(new GridBagLayout());
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
rightComponent.add(pathPanel, c);
|
||||
|
||||
c.gridy = 1;
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
panel.setVisible(false);
|
||||
rightComponent.add(panel, c);
|
||||
}
|
||||
|
||||
c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.weightx = 1;
|
||||
c.weighty = 1;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
c.gridheight = GridBagConstraints.REMAINDER;
|
||||
rightComponent.add(new JScrollPane(infoPanel), c);
|
||||
|
||||
mainPanel.setResizeWeight(0.8);
|
||||
mainPanel.setDividerSize(5);
|
||||
|
||||
mainPanel.setBackground(Color.WHITE);
|
||||
mainPanel.setLeftComponent(openPanel);
|
||||
mainPanel.setRightComponent(rightComponent);
|
||||
this.add(mainPanel, BorderLayout.CENTER);
|
||||
|
||||
// Top Panel
|
||||
this.add(createStatusBar(), BorderLayout.SOUTH);
|
||||
|
||||
// Notify everythin
|
||||
notifyDrawingLoaded(null, drawing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param runnable
|
||||
* @param canInterrupt
|
||||
*/
|
||||
private void launchThread(Runnable runnable, boolean canInterrupt) {
|
||||
if (canInterrupt) {
|
||||
currentThread.setThread(new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
threadTimer.restart();
|
||||
threadPanel.setVisible(true);
|
||||
runnable.run();
|
||||
clearCurrentThread();
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
currentThread.setThread(new Thread(runnable));
|
||||
}
|
||||
currentThread.startThread();
|
||||
}
|
||||
|
||||
private void launchThread(Runnable runnable) {
|
||||
launchThread(runnable, true);
|
||||
}
|
||||
|
||||
protected void clearCurrentThread() {
|
||||
threadTimer.stop();
|
||||
threadPanel.setVisible(false);
|
||||
currentThread.setThread(null);
|
||||
if (spPanel.isVisible()) {
|
||||
spPanel.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a new graph has been loaded.
|
||||
*/
|
||||
private void notifyNewGraphLoaded() {
|
||||
for (GraphChangeListener listener: graphChangeListeneres) {
|
||||
listener.newGraphLoaded(graph);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a new drawing has been set up.
|
||||
*
|
||||
* @param oldDrawing
|
||||
* @param newDrawing
|
||||
*/
|
||||
private void notifyDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
for (DrawingChangeListener listener: drawingChangeListeners) {
|
||||
listener.onDrawingLoaded(oldDrawing, newDrawing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a redraw request is emitted.
|
||||
*/
|
||||
private void notifyRedrawRequest() {
|
||||
for (DrawingChangeListener listener: drawingChangeListeners) {
|
||||
listener.onRedrawRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the stored graph on the drawing.
|
||||
*/
|
||||
private void drawGraph(Class<? extends Drawing> newClass, GraphPalette palette) {
|
||||
|
||||
// Save old divider location
|
||||
int oldLocation = mainPanel.getDividerLocation();
|
||||
|
||||
// Set drawing if not set
|
||||
if (!(mainPanel.getLeftComponent() instanceof Drawing)) {
|
||||
mainPanel.setLeftComponent((Component) this.drawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
// Need to re-validate or the drawing will not have the
|
||||
// correct size prior to drawing, which can cause issue.
|
||||
this.revalidate();
|
||||
}
|
||||
|
||||
boolean isNewGraph = newClass == null;
|
||||
boolean isMapView = (isNewGraph && drawing == mapViewDrawing)
|
||||
|| (!isNewGraph && newClass.equals(MapViewDrawing.class));
|
||||
|
||||
// We need to draw MapView, we have to check if the file exists.
|
||||
File mfile = null;
|
||||
if (isMapView) {
|
||||
String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map"))
|
||||
+ ".mapfg";
|
||||
mfile = new File(mfpath);
|
||||
if (!mfile.exists()) {
|
||||
if (JOptionPane.showConfirmDialog(this,
|
||||
"The associated mapsforge (.mapfg) file has not been found, do you want to specify it manually?",
|
||||
"File not found",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) {
|
||||
JFileChooser chooser = new JFileChooser(mfile.getParentFile());
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
mfile = chooser.getSelectedFile();
|
||||
}
|
||||
else {
|
||||
mfile = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mfile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Runnable runnable = null;
|
||||
|
||||
if (isMapView && mfile != null) {
|
||||
final File mfileFinal = mfile;
|
||||
// It is a mapview drawing and the file was found, so:
|
||||
// 1. We create the drawing if necessary.
|
||||
if (drawing != mapViewDrawing) {
|
||||
drawing.clear();
|
||||
drawing = mapViewDrawing;
|
||||
mainPanel.setLeftComponent(mapViewDrawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
notifyDrawingLoaded(basicDrawing, mapViewDrawing);
|
||||
drawing.clear();
|
||||
isNewGraph = true;
|
||||
mainPanel.revalidate();
|
||||
}
|
||||
if (isNewGraph) {
|
||||
drawing.clear();
|
||||
runnable = new Runnable() {
|
||||
public void run() {
|
||||
((MapViewDrawing) drawing).drawGraph(mfileFinal);
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
else if (!isMapView || (isMapView && mfile == null && isNewGraph)) {
|
||||
if (drawing == mapViewDrawing) {
|
||||
mapViewDrawing.clear();
|
||||
drawing = basicDrawing;
|
||||
mainPanel.setLeftComponent(basicDrawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
notifyDrawingLoaded(mapViewDrawing, basicDrawing);
|
||||
isNewGraph = true;
|
||||
}
|
||||
if (isNewGraph || palette != this.currentPalette) {
|
||||
this.currentPalette = palette;
|
||||
drawing.clear();
|
||||
runnable = new Runnable() {
|
||||
public void run() {
|
||||
drawing.drawGraph(graph, palette);
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (runnable != null) {
|
||||
launchThread(runnable, false);
|
||||
}
|
||||
else {
|
||||
drawing.clearOverlays();
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newClass
|
||||
*/
|
||||
private void drawGraph(Class<? extends Drawing> newClass) {
|
||||
drawGraph(newClass, new BasicGraphPalette());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void drawGraph() {
|
||||
drawGraph(null, this.currentPalette);
|
||||
}
|
||||
|
||||
private void loadGraph(GraphReader reader) {
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GraphReaderProgressBar progressBar = new GraphReaderProgressBar(MainWindow.this);
|
||||
progressBar.setLocationRelativeTo(mainPanel.getLeftComponent());
|
||||
reader.addObserver(progressBar);
|
||||
try {
|
||||
graph = reader.read();
|
||||
reader.close();
|
||||
}
|
||||
catch (Exception exception) {
|
||||
progressBar.setVisible(false);
|
||||
progressBar.dispose();
|
||||
progressBar = null;
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"<html><p>Unable to read graph from the selected file:</p><p>"
|
||||
+ exception.getMessage() + "</p>");
|
||||
exception.printStackTrace(System.out);
|
||||
return;
|
||||
}
|
||||
|
||||
// In case of....
|
||||
progressBar.setVisible(false);
|
||||
progressBar.dispose();
|
||||
progressBar = null;
|
||||
|
||||
String info = graph.getMapId();
|
||||
if (graph.getMapName() != null && !graph.getMapName().isEmpty()) {
|
||||
// The \u200e character is the left-to-right mark, we need to avoid issue with
|
||||
// name that are right-to-left (e.g. arabic names).
|
||||
info += " - " + graph.getMapName() + "\u200e";
|
||||
}
|
||||
info += ", " + graph.size() + " nodes, " + graph.getGraphInformation().getArcCount()
|
||||
+ " arcs.";
|
||||
graphInfoPanel.setText(info);
|
||||
|
||||
drawGraph();
|
||||
|
||||
notifyNewGraphLoaded();
|
||||
|
||||
for (JMenuItem item: graphLockItems) {
|
||||
item.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show and enable the given AlgorithmPanel (and hide all others).
|
||||
*
|
||||
* @param algorithmPanel
|
||||
*/
|
||||
private void enableAlgorithmPanel(AlgorithmPanel algorithmPanel) {
|
||||
int dividerLocation = mainPanel.getDividerLocation();
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
panel.setVisible(panel == algorithmPanel);
|
||||
}
|
||||
mainPanel.setDividerLocation(dividerLocation);
|
||||
}
|
||||
|
||||
private JMenuBar createMenuBar(ActionListener openMapActionListener) {
|
||||
|
||||
// Open Map item...
|
||||
JMenuItem openMapItem = new JMenuItem("Open Map... ", KeyEvent.VK_O);
|
||||
openMapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
|
||||
openMapItem.addActionListener(baf.createBlockingAction(openMapActionListener));
|
||||
|
||||
// Open Path item...
|
||||
JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P);
|
||||
openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK));
|
||||
openPathItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathInput);
|
||||
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
|
||||
try (BinaryPathReader reader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(
|
||||
new FileInputStream(chooser.getSelectedFile()))))){
|
||||
Path path = reader.readPath(graph);
|
||||
pathPanel.addPath(path);
|
||||
}
|
||||
catch (MapMismatchException exception) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"The selected file does not contain a path for the current graph.");
|
||||
}
|
||||
catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Cannot open the selected file.");
|
||||
}
|
||||
catch (Exception exception) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Unable to read path from the selected file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(openPathItem);
|
||||
|
||||
// Close item
|
||||
JMenuItem closeItem = new JMenuItem("Quit", KeyEvent.VK_Q);
|
||||
closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
|
||||
closeItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MainWindow.this.dispatchEvent(
|
||||
new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
});
|
||||
|
||||
// Build the first menu.
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
fileMenu.add(openMapItem);
|
||||
fileMenu.add(openPathItem);
|
||||
fileMenu.addSeparator();
|
||||
fileMenu.add(closeItem);
|
||||
|
||||
// Second menu
|
||||
JMenuItem drawGraphItem = new JMenuItem("Redraw", KeyEvent.VK_R);
|
||||
drawGraphItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
|
||||
drawGraphItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(BasicDrawing.class, basicPalette);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphItem);
|
||||
JMenuItem drawGraphBWItem = new JMenuItem("Redraw (B&W)", KeyEvent.VK_B);
|
||||
drawGraphBWItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.ALT_MASK));
|
||||
drawGraphBWItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(BasicDrawing.class, blackAndWhitePalette);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphBWItem);
|
||||
JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M);
|
||||
drawGraphMapsforgeItem
|
||||
.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK));
|
||||
drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(MapViewDrawing.class);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphMapsforgeItem);
|
||||
|
||||
JMenu graphMenu = new JMenu("Graph");
|
||||
graphMenu.add(drawGraphItem);
|
||||
graphMenu.add(drawGraphBWItem);
|
||||
graphMenu.addSeparator();
|
||||
graphMenu.add(drawGraphMapsforgeItem);
|
||||
|
||||
// Algo menu
|
||||
JMenu algoMenu = new JMenu("Algorithms");
|
||||
|
||||
// Weakly connected components
|
||||
JMenuItem wccItem = new JMenuItem("Weakly Connected Components");
|
||||
wccItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(wccPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Shortest path
|
||||
JMenuItem spItem = new JMenuItem("Shortest-Path");
|
||||
spItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(spPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Car pooling
|
||||
JMenuItem cpItem = new JMenuItem("Car Pooling");
|
||||
cpItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(cpPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Car pooling
|
||||
JMenuItem psItem = new JMenuItem("Package Switch");
|
||||
psItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(psPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
graphLockItems.add(wccItem);
|
||||
graphLockItems.add(spItem);
|
||||
graphLockItems.add(cpItem);
|
||||
graphLockItems.add(psItem);
|
||||
|
||||
algoMenu.add(wccItem);
|
||||
algoMenu.addSeparator();
|
||||
algoMenu.add(spItem);
|
||||
algoMenu.add(cpItem);
|
||||
algoMenu.add(psItem);
|
||||
|
||||
// Create the menu bar.
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
menuBar.add(fileMenu);
|
||||
menuBar.add(graphMenu);
|
||||
menuBar.add(algoMenu);
|
||||
|
||||
for (JMenuItem item: graphLockItems) {
|
||||
item.setEnabled(false);
|
||||
}
|
||||
|
||||
return menuBar;
|
||||
}
|
||||
|
||||
private JPanel createStatusBar() {
|
||||
// create the status bar panel and shove it down the bottom of the frame
|
||||
JPanel statusPanel = new JPanel();
|
||||
statusPanel.setBorder(
|
||||
new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
|
||||
new EmptyBorder(0, 15, 0, 15)));
|
||||
statusPanel.setPreferredSize(new Dimension(getWidth(), 38));
|
||||
statusPanel.setLayout(new BorderLayout());
|
||||
|
||||
graphInfoPanel = new JLabel();
|
||||
graphInfoPanel.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
statusPanel.add(graphInfoPanel, BorderLayout.WEST);
|
||||
|
||||
JLabel threadInfo = new JLabel("Thread running... ");
|
||||
JLabel threadTimerLabel = new JLabel("00:00:00");
|
||||
JButton threadButton = new JButton("Stop");
|
||||
threadButton.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (currentThread.isRunning()) {
|
||||
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
|
||||
"Are you sure you want to kill the running thread?",
|
||||
"Kill Confirmation", JOptionPane.YES_NO_OPTION);
|
||||
if (confirmed == JOptionPane.YES_OPTION) {
|
||||
currentThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
threadTimer = new Timer(THREAD_TIMER_DELAY, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
long seconds = currentThread.getDuration().getSeconds();
|
||||
threadTimerLabel.setText(String.format("%02d:%02d:%02d", seconds / 3600,
|
||||
seconds / 60 % 60, seconds % 60));
|
||||
}
|
||||
});
|
||||
threadTimer.setInitialDelay(0);
|
||||
|
||||
threadPanel = new JPanel();
|
||||
threadPanel.add(threadInfo);
|
||||
threadPanel.add(threadTimerLabel);
|
||||
threadPanel.add(threadButton);
|
||||
threadPanel.setVisible(false);
|
||||
statusPanel.add(threadPanel, BorderLayout.EAST);
|
||||
|
||||
return statusPanel;
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
|
||||
// Try to set system look and feel.
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MainWindow w = new MainWindow();
|
||||
w.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
w.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,424 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
|
||||
import org.insa.graphs.gui.drawing.DrawingClickListener;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public class NodesInputPanel extends JPanel
|
||||
implements DrawingClickListener, DrawingChangeListener, GraphChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Color DEFAULT_MARKER_COLOR = Color.BLUE;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to find a node from coordinates in a "fast"
|
||||
* way.
|
||||
*
|
||||
*/
|
||||
private static class NodeFinder {
|
||||
|
||||
// Graph associated with this node finder.
|
||||
private Graph graph;
|
||||
|
||||
/**
|
||||
* @param graph
|
||||
*/
|
||||
public NodeFinder(Graph graph) {
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param point
|
||||
*
|
||||
* @return the closest node to the given point, or null if no node is "close
|
||||
* enough".
|
||||
*/
|
||||
public Node findClosestNode(Point point) {
|
||||
Node minNode = null;
|
||||
double minDis = Double.POSITIVE_INFINITY;
|
||||
for (Node node: graph.getNodes()) {
|
||||
double dlon = point.getLongitude() - node.getPoint().getLongitude();
|
||||
double dlat = point.getLatitude() - node.getPoint().getLatitude();
|
||||
double dis = dlon * dlon + dlat * dlat; // No need to square
|
||||
if (dis < minDis) {
|
||||
minNode = node;
|
||||
minDis = dis;
|
||||
}
|
||||
}
|
||||
return minNode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data send when a node input has changed.
|
||||
*
|
||||
*/
|
||||
public class InputChangedEvent extends ActionEvent {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 3440024811352247731L;
|
||||
|
||||
protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled";
|
||||
|
||||
protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1;
|
||||
|
||||
// List of nodes
|
||||
List<Node> nodes;
|
||||
|
||||
public InputChangedEvent(List<Node> nodes2) {
|
||||
super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND);
|
||||
this.nodes = nodes2;
|
||||
}
|
||||
|
||||
List<Node> getNodes() {
|
||||
return Collections.unmodifiableList(nodes);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Node inputs and markers.
|
||||
private final ArrayList<JTextField> nodeInputs = new ArrayList<>();
|
||||
private final Map<JTextField, MarkerOverlay> markerTrackers = new IdentityHashMap<JTextField, MarkerOverlay>();
|
||||
|
||||
// Component that can be enabled/disabled.
|
||||
private ArrayList<JComponent> components = new ArrayList<>();
|
||||
private int inputToFillIndex;
|
||||
|
||||
// ActionListener called when all inputs are filled.
|
||||
private ArrayList<ActionListener> inputChangeListeners = new ArrayList<>();
|
||||
|
||||
// Drawing and graph
|
||||
private Drawing drawing;
|
||||
private Graph graph;
|
||||
private NodeFinder nodeFinder;
|
||||
|
||||
/**
|
||||
* Create a new NodesInputPanel.
|
||||
*
|
||||
*/
|
||||
public NodesInputPanel() {
|
||||
super(new GridBagLayout());
|
||||
initInputToFill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an InputChanged listener to this panel. This listener will be notified by
|
||||
* a {@link InputChangedEvent} each time an input in this panel change (click,
|
||||
* clear, manual input).
|
||||
*
|
||||
* @param listener Listener to add.
|
||||
*
|
||||
* @see InputChangedEvent
|
||||
*/
|
||||
public void addInputChangedListener(ActionListener listener) {
|
||||
inputChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
super.setVisible(visible);
|
||||
for (JTextField input: nodeInputs) {
|
||||
MarkerOverlay marker = markerTrackers.getOrDefault(input, null);
|
||||
if (marker != null) {
|
||||
marker.setVisible(visible && !input.getText().trim().isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
for (JComponent component: components) {
|
||||
component.setEnabled(enabled);
|
||||
}
|
||||
super.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
// Enable: Check if there is an input to fill, otherwize find the next one.
|
||||
if (getInputToFill() == null) {
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Disable, next input to fill = -1.
|
||||
this.inputToFillIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (JTextField field: nodeInputs) {
|
||||
field.setText("");
|
||||
markerTrackers.put(field, null);
|
||||
}
|
||||
initInputToFill();
|
||||
}
|
||||
|
||||
public void addTextField(String label) {
|
||||
addTextField(label, DEFAULT_MARKER_COLOR);
|
||||
}
|
||||
|
||||
public void addTextField(String label, Color markerColor) {
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.insets = new Insets(3, 3, 3, 3);
|
||||
|
||||
JLabel jLabel = new JLabel(label);
|
||||
jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD));
|
||||
JTextField textField = new JTextField();
|
||||
jLabel.setLabelFor(textField);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(jLabel, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(textField, c);
|
||||
|
||||
JButton clearButton = new JButton("Clear");
|
||||
c.gridx = 2;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(clearButton, c);
|
||||
|
||||
JButton clickButton = new JButton("Click");
|
||||
c.gridx = 3;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(clickButton, c);
|
||||
|
||||
// Did not find something easier that this... ?
|
||||
textField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
insertUpdate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
insertUpdate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
|
||||
// Draw marker if possible
|
||||
Node curnode = getNodeForInput(textField);
|
||||
MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null);
|
||||
if (curnode != null) {
|
||||
if (tracker == null) {
|
||||
tracker = drawing.drawMarker(curnode.getPoint(), markerColor, Color.BLACK,
|
||||
AlphaMode.TRANSPARENT);
|
||||
markerTrackers.put(textField, tracker);
|
||||
}
|
||||
else {
|
||||
tracker.moveTo(curnode.getPoint());
|
||||
}
|
||||
tracker.setVisible(true);
|
||||
}
|
||||
else if (tracker != null) {
|
||||
tracker.setVisible(false);
|
||||
if (getInputToFill() == null) {
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
|
||||
// Create array of nodes
|
||||
List<Node> nodes = getNodeForInputs();
|
||||
|
||||
// Trigger change event.
|
||||
for (ActionListener lis: inputChangeListeners) {
|
||||
lis.actionPerformed(new InputChangedEvent(nodes));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clearButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
textField.setText("");
|
||||
setInputToFill(textField);
|
||||
}
|
||||
});
|
||||
|
||||
clickButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setInputToFill(textField);
|
||||
}
|
||||
});
|
||||
|
||||
nodeInputs.add(textField);
|
||||
components.add(textField);
|
||||
components.add(clearButton);
|
||||
components.add(clickButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current graph associated with this input panel.
|
||||
*/
|
||||
protected Graph getGraph() {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The node for the given text field, or null if the content of the text
|
||||
* field is invalid.
|
||||
*/
|
||||
protected Node getNodeForInput(JTextField textfield) {
|
||||
try {
|
||||
Node node = graph.get(Integer.valueOf(textfield.getText().trim()));
|
||||
return node;
|
||||
}
|
||||
catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of nodes associated with the input. Some nodes may be null if
|
||||
* their associated input is invalid.
|
||||
*/
|
||||
public List<Node> getNodeForInputs() {
|
||||
List<Node> nodes = new ArrayList<>(nodeInputs.size());
|
||||
for (JTextField input: nodeInputs) {
|
||||
nodes.add(getNodeForInput(input));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next input that should be filled by a click, or null if none should
|
||||
* be filled.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected JTextField getInputToFill() {
|
||||
if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) {
|
||||
return null;
|
||||
}
|
||||
return nodeInputs.get(inputToFillIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the next input to fill.
|
||||
*/
|
||||
protected void initInputToFill() {
|
||||
inputToFillIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next input to fill to the given text field.
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
protected void setInputToFill(JTextField input) {
|
||||
inputToFillIndex = nodeInputs.indexOf(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next input to fill, if any.
|
||||
*/
|
||||
protected void nextInputToFill() {
|
||||
boolean found = false;
|
||||
if (inputToFillIndex == -1) {
|
||||
inputToFillIndex = 0;
|
||||
}
|
||||
for (int i = 0; i < nodeInputs.size() && !found; ++i) {
|
||||
int nextIndex = (i + inputToFillIndex) % nodeInputs.size();
|
||||
JTextField input = nodeInputs.get(nextIndex);
|
||||
if (input.getText().trim().isEmpty()) {
|
||||
inputToFillIndex = nextIndex;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
inputToFillIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(Point point) {
|
||||
JTextField input = getInputToFill();
|
||||
if (input != null) {
|
||||
Node node = nodeFinder.findClosestNode(point);
|
||||
input.setText(String.valueOf(node.getId()));
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newGraphLoaded(Graph graph) {
|
||||
if (graph != this.graph) {
|
||||
this.clear();
|
||||
this.graph = graph;
|
||||
|
||||
nodeFinder = new NodeFinder(graph);
|
||||
|
||||
// Disable if previously disabled...
|
||||
setEnabled(this.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
if (newDrawing != drawing) {
|
||||
this.drawing = newDrawing;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
for (JTextField input: nodeInputs) {
|
||||
MarkerOverlay tracker = markerTrackers.getOrDefault(input, null);
|
||||
if (tracker != null) {
|
||||
MarkerOverlay newMarker = this.drawing.drawMarker(tracker.getPoint(),
|
||||
tracker.getColor(), Color.BLACK, AlphaMode.TRANSPARENT);
|
||||
markerTrackers.put(input, newMarker);
|
||||
newMarker.setVisible(tracker.isVisible());
|
||||
tracker.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
361
be-graphes-gui/src/main/java/org/insa/graphs/gui/PathsPanel.java
Normal file
361
be-graphes-gui/src/main/java/org/insa/graphs/gui/PathsPanel.java
Normal file
|
@ -0,0 +1,361 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JColorChooser;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
|
||||
import org.insa.graphs.gui.utils.ColorUtils;
|
||||
import org.insa.graphs.gui.utils.FileUtils;
|
||||
import org.insa.graphs.gui.utils.FileUtils.FolderType;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.io.BinaryPathWriter;
|
||||
|
||||
public class PathsPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private class PathPanel extends JPanel {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Simple icon that represents a unicolor rectangle.
|
||||
*
|
||||
*/
|
||||
protected class ColorIcon implements Icon {
|
||||
|
||||
private Color color;
|
||||
private int width, height;
|
||||
|
||||
public ColorIcon(Color color, int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
g.setColor(this.color);
|
||||
g.fillRect(x, y, getIconWidth(), getIconHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return this.height;
|
||||
}
|
||||
}
|
||||
|
||||
// Solution
|
||||
private final Path path;
|
||||
|
||||
// Path Overlay (not final due to redraw)
|
||||
private PathOverlay overlay;
|
||||
|
||||
// Color button
|
||||
private final JButton colorButton;
|
||||
|
||||
/**
|
||||
* Create a new bundle with the given path and create a new overlay
|
||||
* corresponding to the path.
|
||||
*
|
||||
* @param path Path for this bundle, must not be null.
|
||||
*
|
||||
* @throws IOException If a resource was not found.
|
||||
*
|
||||
*/
|
||||
public PathPanel(Path path, Color color) throws IOException {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
|
||||
setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY),
|
||||
new EmptyBorder(5, 0, 5, 0)));
|
||||
this.path = path;
|
||||
this.overlay = drawing.drawPath(this.path, color);
|
||||
|
||||
JCheckBox checkbox = new JCheckBox();
|
||||
checkbox.setSelected(true);
|
||||
checkbox.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
overlay.setVisible(checkbox.isSelected());
|
||||
}
|
||||
});
|
||||
|
||||
JLabel infoPanel = new JLabel();
|
||||
String info = "";
|
||||
|
||||
// Display length
|
||||
float length = path.getLength();
|
||||
if (length < 2000) {
|
||||
info += String.format("Length = %.1f meters", length);
|
||||
}
|
||||
else {
|
||||
info += String.format("Length = %.3f kilometers", length / 1000.);
|
||||
}
|
||||
|
||||
// Display time
|
||||
info += ", Duration=";
|
||||
double time = path.getMinimumTravelTime();
|
||||
int hours = (int) (time / 3600);
|
||||
int minutes = (int) (time / 60) % 60;
|
||||
int seconds = ((int) time) % 60;
|
||||
if (hours > 0) {
|
||||
info += String.format("%d hours, ", hours);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
info += String.format("%d minutes, ", minutes);
|
||||
}
|
||||
info += String.format("%d seconds.", seconds);
|
||||
infoPanel.setText("<html>" + toString() + "<br/>" + info + "</html>");
|
||||
infoPanel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
checkbox.setSelected(!checkbox.isSelected());
|
||||
overlay.setVisible(checkbox.isSelected());
|
||||
}
|
||||
});
|
||||
|
||||
Dimension size = new Dimension(24, 24);
|
||||
|
||||
ColorIcon icon = new ColorIcon(overlay.getColor(), 14, 14);
|
||||
colorButton = new JButton(icon);
|
||||
colorButton.setFocusable(false);
|
||||
colorButton.setFocusPainted(false);
|
||||
colorButton.setMinimumSize(size);
|
||||
colorButton.setPreferredSize(size);
|
||||
colorButton.setMaximumSize(size);
|
||||
|
||||
colorButton.setToolTipText("Pick a color");
|
||||
|
||||
colorButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final Color originalColor = overlay.getColor();
|
||||
JColorChooser chooser = new JColorChooser(overlay.getColor());
|
||||
chooser.getSelectionModel().addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
icon.setColor(chooser.getSelectionModel().getSelectedColor());
|
||||
colorButton.repaint();
|
||||
overlay.setColor(chooser.getSelectionModel().getSelectedColor());
|
||||
overlay.redraw();
|
||||
}
|
||||
});
|
||||
|
||||
JColorChooser.createDialog(getTopLevelAncestor(), "Pick a new color", true,
|
||||
chooser, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
icon.setColor(chooser.getSelectionModel().getSelectedColor());
|
||||
colorButton.repaint();
|
||||
overlay.setColor(
|
||||
chooser.getSelectionModel().getSelectedColor());
|
||||
overlay.redraw();
|
||||
}
|
||||
}, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
icon.setColor(originalColor);
|
||||
colorButton.repaint();
|
||||
overlay.setColor(originalColor);
|
||||
overlay.redraw();
|
||||
}
|
||||
}).setVisible(true);
|
||||
;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Image saveImg = ImageIO.read(getClass().getResourceAsStream("/save-icon.png"))
|
||||
.getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
|
||||
JButton saveButton = new JButton(new ImageIcon(saveImg));
|
||||
saveButton.setFocusPainted(false);
|
||||
saveButton.setFocusable(false);
|
||||
saveButton.setMinimumSize(size);
|
||||
saveButton.setPreferredSize(size);
|
||||
saveButton.setMaximumSize(size);
|
||||
saveButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String filepath = String.format("path_%s_%d_%d.path",
|
||||
path.getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", ""),
|
||||
path.getOrigin().getId(), path.getDestination().getId());
|
||||
JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathOutput,
|
||||
filepath);
|
||||
|
||||
if (chooser
|
||||
.showSaveDialog(getTopLevelAncestor()) == JFileChooser.APPROVE_OPTION) {
|
||||
File file = chooser.getSelectedFile();
|
||||
try (BinaryPathWriter writer = new BinaryPathWriter(new DataOutputStream(
|
||||
new BufferedOutputStream(new FileOutputStream(file))))) {
|
||||
writer.writePath(path);
|
||||
}
|
||||
catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(getTopLevelAncestor(),
|
||||
"Unable to write path to the selected file.");
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Image newimg = ImageIO.read(getClass().getResourceAsStream("/delete-icon.png"))
|
||||
.getScaledInstance(14, 14, java.awt.Image.SCALE_SMOOTH);
|
||||
JButton deleteButton = new JButton(new ImageIcon(newimg));
|
||||
deleteButton.setFocusPainted(false);
|
||||
deleteButton.setFocusable(false);
|
||||
deleteButton.setMinimumSize(size);
|
||||
deleteButton.setPreferredSize(size);
|
||||
deleteButton.setMaximumSize(size);
|
||||
deleteButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
overlay.delete();
|
||||
PathsPanel.this.removePath(PathPanel.this);
|
||||
}
|
||||
});
|
||||
|
||||
add(checkbox);
|
||||
add(Box.createHorizontalStrut(5));
|
||||
add(infoPanel);
|
||||
add(Box.createHorizontalGlue());
|
||||
add(colorButton);
|
||||
add(saveButton);
|
||||
add(deleteButton);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-draw the current overlay (if any) on the new drawing.
|
||||
*
|
||||
*/
|
||||
public void updateOverlay() {
|
||||
PathOverlay oldOverlay = this.overlay;
|
||||
this.overlay = drawing.drawPath(path);
|
||||
this.overlay.setColor(oldOverlay.getColor());
|
||||
((ColorIcon) this.colorButton.getIcon()).setColor(this.overlay.getColor());
|
||||
this.colorButton.repaint();
|
||||
this.overlay.setVisible(oldOverlay.isVisible());
|
||||
oldOverlay.delete();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "Path from #" + path.getOrigin().getId() + " to #"
|
||||
+ path.getDestination().getId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Solution
|
||||
private Drawing drawing;
|
||||
|
||||
public PathsPanel(Component parent) {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
|
||||
// Default hidden
|
||||
this.setVisible(false);
|
||||
|
||||
}
|
||||
|
||||
public void addPath(Path path) {
|
||||
try {
|
||||
this.add(new PathPanel(path, ColorUtils.getColor(this.getComponentCount())));
|
||||
this.setVisible(true);
|
||||
this.revalidate();
|
||||
this.repaint();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void removePath(PathPanel panel) {
|
||||
PathsPanel.this.remove(panel);
|
||||
PathsPanel.this.revalidate();
|
||||
PathsPanel.this.repaint();
|
||||
if (this.getComponentCount() == 0) {
|
||||
this.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newGraphLoaded(Graph graph) {
|
||||
for (Component c: this.getComponents()) {
|
||||
if (c instanceof PathPanel) {
|
||||
((PathPanel) c).overlay.delete();
|
||||
}
|
||||
}
|
||||
this.removeAll();
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
if (newDrawing != drawing) {
|
||||
drawing = newDrawing;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
for (Component c: this.getComponents()) {
|
||||
if (c instanceof PathPanel) {
|
||||
((PathPanel) c).updateOverlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public interface RunningAction {
|
||||
|
||||
/**
|
||||
* @return true if this action is running.
|
||||
*/
|
||||
public boolean isRunning();
|
||||
|
||||
/**
|
||||
* Interrupt this action.
|
||||
*/
|
||||
public void interrupt();
|
||||
|
||||
/**
|
||||
* @return Starting time of this action.
|
||||
*/
|
||||
public Instant getStartingTime();
|
||||
|
||||
/**
|
||||
* @return Current duration of this action.
|
||||
*/
|
||||
public Duration getDuration();
|
||||
|
||||
/**
|
||||
* @return Information for this action.
|
||||
*/
|
||||
public String getInformation();
|
||||
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
|
||||
public class SolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private class SolutionBundle {
|
||||
|
||||
// Solution
|
||||
private final AbstractSolution solution;
|
||||
|
||||
// Path Overlay (not final due to redraw)
|
||||
private List<PathOverlay> overlays = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new bundle with the given solution and create a new overlay
|
||||
* corresponding to the solution (if the solution is feasible).
|
||||
*
|
||||
* @param solution Solution for this bundle, must not be null.
|
||||
*
|
||||
*/
|
||||
public SolutionBundle(AbstractSolution solution, boolean createOverlays) {
|
||||
this.solution = solution;
|
||||
if (createOverlays) {
|
||||
this.overlays = createOverlaysFromSolution();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Solution associated with this bundle.
|
||||
*/
|
||||
public AbstractSolution getSolution() {
|
||||
return this.solution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data assocaited with this bundle.
|
||||
*/
|
||||
public AbstractInputData getData() {
|
||||
return this.solution.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Overlays associated with this bundle, or null.
|
||||
*/
|
||||
public List<PathOverlay> getOverlays() {
|
||||
return this.overlays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this bundle has overlays.
|
||||
*/
|
||||
public boolean hasOverlays() {
|
||||
return !this.overlays.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-draw the current overlay (if any) on the new drawing.
|
||||
*
|
||||
*/
|
||||
public void updateOverlays() {
|
||||
if (this.overlays.isEmpty()) {
|
||||
return; // This bundle has no overlay.
|
||||
}
|
||||
List<PathOverlay> oldOverlays = this.overlays;
|
||||
this.overlays = createOverlaysFromSolution();
|
||||
for (int i = 0; i < oldOverlays.size(); ++i) {
|
||||
oldOverlays.get(i).delete();
|
||||
}
|
||||
}
|
||||
|
||||
private List<PathOverlay> createOverlaysFromSolution() {
|
||||
List<PathOverlay> overlays = new ArrayList<>();
|
||||
if (solution.isFeasible()) {
|
||||
Method[] methods = this.solution.getClass().getDeclaredMethods();
|
||||
for (Method method: methods) {
|
||||
if (method.getReturnType().equals(Path.class)
|
||||
&& method.getParameterCount() == 0) {
|
||||
try {
|
||||
Path path = (Path) method.invoke(this.solution);
|
||||
overlays.add(drawing.drawPath(path));
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException e) {
|
||||
// This has been check before, so should never happen...
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return overlays;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return getData().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Solution
|
||||
private Drawing drawing;
|
||||
|
||||
// Solution selector
|
||||
private final JComboBox<SolutionBundle> solutionSelect;
|
||||
|
||||
private final JLabel informationPanel;
|
||||
|
||||
// Current bundle
|
||||
private SolutionBundle currentBundle = null;
|
||||
|
||||
public SolutionPanel(Component parent) {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
setBorder(new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY),
|
||||
new EmptyBorder(10, 0, 10, 0)));
|
||||
|
||||
solutionSelect = new JComboBox<>();
|
||||
solutionSelect.setBackground(Color.WHITE);
|
||||
solutionSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
add(solutionSelect);
|
||||
|
||||
informationPanel = new JLabel();
|
||||
informationPanel.setOpaque(true);
|
||||
informationPanel.setFocusable(false);
|
||||
// informationPanel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
|
||||
informationPanel.setHorizontalAlignment(JLabel.LEFT);
|
||||
|
||||
add(Box.createVerticalStrut(8));
|
||||
add(informationPanel);
|
||||
|
||||
JButton clearButton = new JButton("Hide");
|
||||
clearButton.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
for (PathOverlay overlay: currentBundle.getOverlays()) {
|
||||
if (overlay.isVisible()) {
|
||||
overlay.setVisible(false);
|
||||
clearButton.setText("Show");
|
||||
}
|
||||
else {
|
||||
overlay.setVisible(true);
|
||||
clearButton.setText("Hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
|
||||
buttonPanel.add(Box.createHorizontalGlue());
|
||||
buttonPanel.add(clearButton);
|
||||
buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
add(Box.createVerticalStrut(4));
|
||||
add(buttonPanel);
|
||||
|
||||
solutionSelect.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
||||
if (currentBundle != null) {
|
||||
for (PathOverlay overlay: currentBundle.getOverlays()) {
|
||||
overlay.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
SolutionBundle bundle = (SolutionBundle) solutionSelect.getSelectedItem();
|
||||
|
||||
if (bundle != null) {
|
||||
|
||||
updateInformationLabel(bundle);
|
||||
buttonPanel
|
||||
.setVisible(bundle.getSolution().isFeasible() && bundle.hasOverlays());
|
||||
clearButton.setText(bundle.getSolution().isFeasible() ? "Hide" : "Show");
|
||||
|
||||
for (PathOverlay overlay: bundle.getOverlays()) {
|
||||
overlay.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
currentBundle = bundle;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void addSolution(AbstractSolution solution) {
|
||||
addSolution(solution, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given solution to the panel.
|
||||
*
|
||||
* @param solution the solution to add to the panel
|
||||
* @param createOverlays Whether or not overlay should be created for this
|
||||
* solution.
|
||||
*/
|
||||
public void addSolution(AbstractSolution solution, boolean createOverlays) {
|
||||
SolutionBundle bundle = new SolutionBundle(solution, createOverlays);
|
||||
solutionSelect.addItem(bundle);
|
||||
solutionSelect.setSelectedItem(bundle);
|
||||
}
|
||||
|
||||
protected void updateInformationLabel(SolutionBundle bundle) {
|
||||
informationPanel.setText(bundle.getSolution().toString());
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
solutionSelect.setEnabled(enabled);
|
||||
|
||||
if (enabled) {
|
||||
// Trigger event
|
||||
solutionSelect.setSelectedItem(currentBundle);
|
||||
}
|
||||
else {
|
||||
SolutionBundle bundle = (SolutionBundle) this.solutionSelect.getSelectedItem();
|
||||
if (bundle != null) {
|
||||
for (PathOverlay overlay: bundle.getOverlays()) {
|
||||
overlay.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newGraphLoaded(Graph graph) {
|
||||
for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
|
||||
for (PathOverlay overlay: this.solutionSelect.getItemAt(i).getOverlays()) {
|
||||
overlay.delete();
|
||||
}
|
||||
}
|
||||
this.solutionSelect.removeAllItems();
|
||||
this.currentBundle = null;
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
if (newDrawing != drawing) {
|
||||
drawing = newDrawing;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) {
|
||||
this.solutionSelect.getItemAt(i).updateOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
public class StreamCapturer extends OutputStream {
|
||||
|
||||
private StringBuilder buffer;
|
||||
private String prefix = null;
|
||||
private JTextArea output;
|
||||
|
||||
/**
|
||||
* @param output Output JTextArea to which this stream should print.
|
||||
* @param prefix Prefix to add to each line of this output.
|
||||
*/
|
||||
public StreamCapturer(JTextArea output, String prefix) {
|
||||
this.prefix = prefix;
|
||||
buffer = new StringBuilder(128);
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new StreamCapturer without prefix.
|
||||
*
|
||||
* @param output Output JTextArea to which this stream should print.
|
||||
*/
|
||||
public StreamCapturer(JTextArea output) {
|
||||
this(output, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
char c = (char) b;
|
||||
String value = Character.toString(c);
|
||||
buffer.append(value);
|
||||
if (value.equals("\n")) {
|
||||
output.append(getPrefix() + buffer.toString());
|
||||
output.setCaretPosition(output.getText().length());
|
||||
buffer.delete(0, buffer.length());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Formatted prefix, or empty string if no prefix is set.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
if (this.prefix == null) {
|
||||
return "";
|
||||
}
|
||||
return "[" + prefix + "] ";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public class ThreadWrapper implements RunningAction {
|
||||
|
||||
// Thread hold by this wrapper.
|
||||
private Thread thread;
|
||||
|
||||
// Starting time of the thread.
|
||||
Instant startingTime;
|
||||
|
||||
// MainWindow
|
||||
private MainWindow mainWindow;
|
||||
|
||||
public ThreadWrapper(MainWindow mainWindow) {
|
||||
this.thread = null;
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void setThread(Thread thread) {
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
public void startThread() {
|
||||
this.startingTime = Instant.now();
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public Thread getThread() {
|
||||
return this.thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return thread != null && thread.isAlive();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void interrupt() {
|
||||
thread.stop();
|
||||
this.mainWindow.clearCurrentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getStartingTime() {
|
||||
return startingTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDuration() {
|
||||
return Duration.between(getStartingTime(), Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInformation() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.RoadInformation.RoadType;
|
||||
|
||||
public class BasicGraphPalette implements GraphPalette {
|
||||
|
||||
// Color types for arc.
|
||||
private static final Color MOTORWAY_COLOR = Color.RED;
|
||||
private static final Color BIG_ROAD_COLOR = new Color(255, 105, 0);
|
||||
private static final Color SMALL_ROAD_COLOR = new Color(255, 200, 0);
|
||||
private static final Color COASTLINE_COLOR = Color.BLUE;
|
||||
|
||||
@Override
|
||||
public Color getColorForArc(Arc arc) {
|
||||
RoadType type = arc.getRoadInformation().getType();
|
||||
switch (type) {
|
||||
case MOTORWAY:
|
||||
return MOTORWAY_COLOR;
|
||||
case TRUNK:
|
||||
case PRIMARY:
|
||||
case SECONDARY:
|
||||
case MOTORWAY_LINK:
|
||||
case TRUNK_LINK:
|
||||
case PRIMARY_LINK:
|
||||
return BIG_ROAD_COLOR;
|
||||
case SECONDARY_LINK:
|
||||
case TERTIARY:
|
||||
case RESIDENTIAL:
|
||||
case UNCLASSIFIED:
|
||||
case LIVING_STREET:
|
||||
case SERVICE:
|
||||
case ROUNDABOUT:
|
||||
case PEDESTRIAN:
|
||||
case CYCLEWAY:
|
||||
case TRACK:
|
||||
return SMALL_ROAD_COLOR;
|
||||
case COASTLINE:
|
||||
return COASTLINE_COLOR;
|
||||
}
|
||||
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidthForArc(Arc arc) {
|
||||
RoadType type = arc.getRoadInformation().getType();
|
||||
int width = 1;
|
||||
switch (type) {
|
||||
case MOTORWAY:
|
||||
width = 2;
|
||||
break;
|
||||
case TRUNK:
|
||||
case PRIMARY:
|
||||
case SECONDARY:
|
||||
case MOTORWAY_LINK:
|
||||
case TRUNK_LINK:
|
||||
case PRIMARY_LINK:
|
||||
width = 1;
|
||||
break;
|
||||
case SECONDARY_LINK:
|
||||
case TERTIARY:
|
||||
case RESIDENTIAL:
|
||||
case UNCLASSIFIED:
|
||||
case LIVING_STREET:
|
||||
case SERVICE:
|
||||
case ROUNDABOUT:
|
||||
case PEDESTRIAN:
|
||||
case CYCLEWAY:
|
||||
case TRACK:
|
||||
width = 1;
|
||||
break;
|
||||
case COASTLINE:
|
||||
width = 4;
|
||||
break;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
|
||||
public class BlackAndWhiteGraphPalette extends BasicGraphPalette {
|
||||
|
||||
// Road colors (index
|
||||
private final static Color[] ROAD_COLOR_FROM_WIDTH = { null, new Color(140, 140, 140),
|
||||
new Color(80, 80, 80), new Color(40, 40, 40), new Color(30, 30, 30) };
|
||||
|
||||
@Override
|
||||
public Color getColorForArc(Arc arc) {
|
||||
int width = getWidthForArc(arc);
|
||||
return ROAD_COLOR_FROM_WIDTH[width];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public interface Drawing {
|
||||
|
||||
/**
|
||||
* Available fill mode for the creation of markers, see the documentation of
|
||||
* each value for more details.
|
||||
*/
|
||||
enum AlphaMode {
|
||||
|
||||
/**
|
||||
* Do not use the original transparency of the inner part to fill it.
|
||||
*/
|
||||
OPAQUE,
|
||||
|
||||
/**
|
||||
* Use the original transparency of the inner part to fill it.
|
||||
*/
|
||||
TRANSPARENT
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to click to this drawing.
|
||||
*
|
||||
* @param listener DrawingClickListener to add to this Drawing.
|
||||
*/
|
||||
public void addDrawingClickListener(DrawingClickListener listener);
|
||||
|
||||
/**
|
||||
* Remove the given listener from the drawing.
|
||||
*
|
||||
* @param listener DrawingClickListener to remove from this Drawing.
|
||||
*/
|
||||
public void removeDrawingClickListener(DrawingClickListener listener);
|
||||
|
||||
/**
|
||||
* Clear the drawing (overlays and underlying graph/map).
|
||||
*/
|
||||
public void clear();
|
||||
|
||||
/**
|
||||
* Remove overlays from the drawing (do not remove the underlying graph/map).
|
||||
*/
|
||||
public void clearOverlays();
|
||||
|
||||
/**
|
||||
* Draw a marker at the given position using the given colors and according to
|
||||
* the given mode.
|
||||
*
|
||||
* @param point Position of the marker to draw.
|
||||
* @param outer Color for the outer part of the marker to draw.
|
||||
* @param inner Color for the inner part of the marker to draw.
|
||||
* @param mode Mode for filling the inner par of the marker.
|
||||
*
|
||||
* @return A MarkerOverlay instance representing the newly drawn marker.
|
||||
*/
|
||||
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode);
|
||||
|
||||
/**
|
||||
* Create a new PointSetOverlay that can be used to add overlay points to this
|
||||
* drawing.
|
||||
*
|
||||
* PointSetOverlay are heavy memory resources, do not use one for each point!
|
||||
*
|
||||
* @return A new PointSetOverlay for this drawing.
|
||||
*/
|
||||
public PointSetOverlay createPointSetOverlay();
|
||||
|
||||
/**
|
||||
* Create a new PointSetOverlay with the given initial width and color that can
|
||||
* be used to add overlay points to this drawing.
|
||||
*
|
||||
* PointSetOverlay are heavy memory resources, do not use one for each point!
|
||||
*
|
||||
* @param width Initial width of points in the overlay.
|
||||
* @param color Initial width of points in the overlay.
|
||||
*
|
||||
* @return A new PointSetOverlay for this drawing.
|
||||
*/
|
||||
public PointSetOverlay createPointSetOverlay(int width, Color color);
|
||||
|
||||
/**
|
||||
* Draw the given graph using the given palette.
|
||||
*
|
||||
* @param graph Graph to draw.
|
||||
* @param palette Palette to use to draw the graph.
|
||||
*
|
||||
* @see BasicGraphPalette
|
||||
* @see BlackAndWhiteGraphPalette
|
||||
*/
|
||||
public void drawGraph(Graph graph, GraphPalette palette);
|
||||
|
||||
/**
|
||||
* Draw the given graph using a default palette specific to the implementation.
|
||||
*
|
||||
* @param graph Graph to draw.
|
||||
*/
|
||||
public void drawGraph(Graph graph);
|
||||
|
||||
/**
|
||||
* Draw a path using the given color.
|
||||
*
|
||||
* @param path Path to draw.
|
||||
* @param color Color of the path to draw.
|
||||
* @param markers true to show origin and destination markers.
|
||||
*
|
||||
* @return A PathOverlay instance representing the newly drawn path.
|
||||
*/
|
||||
public PathOverlay drawPath(Path path, Color color, boolean markers);
|
||||
|
||||
/**
|
||||
* Draw a path with both origin and destination markers using the given color.
|
||||
*
|
||||
* @param path Path to draw.
|
||||
* @param color Color of the path to draw.
|
||||
*
|
||||
* @return A PathOverlay instance representing the newly drawn path.
|
||||
*
|
||||
* @see Drawing#drawPath(Path, Color, boolean)
|
||||
*/
|
||||
public PathOverlay drawPath(Path path, Color color);
|
||||
|
||||
/**
|
||||
* Draw a path using a default color specific to the implementation
|
||||
*
|
||||
* @param path Path to draw.
|
||||
* @param markers true to show origin and destination markers.
|
||||
*
|
||||
* @return A PathOverlay instance representing the newly drawn path.
|
||||
*
|
||||
* @see Drawing#drawPath(Path, Color, boolean)
|
||||
*/
|
||||
public PathOverlay drawPath(Path path, boolean markers);
|
||||
|
||||
/**
|
||||
* Draw a path with both origin and destination markers using a default color
|
||||
* specific to the implementation
|
||||
*
|
||||
*
|
||||
* @param path Path to draw.
|
||||
*
|
||||
* @return A PathOverlay instance representing the newly drawn path.
|
||||
*
|
||||
* @see Drawing#drawPath(Path, Color, boolean)
|
||||
*/
|
||||
public PathOverlay drawPath(Path path);
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public interface DrawingClickListener {
|
||||
|
||||
/**
|
||||
* Event triggered when a click is made on the map.
|
||||
*
|
||||
* @param point Position (on the map) of the mouse click.
|
||||
*/
|
||||
public void mouseClicked(Point point);
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
|
||||
public interface GraphPalette {
|
||||
|
||||
/**
|
||||
* @param arc Arc for which color should be retrieved.
|
||||
*
|
||||
* @return Color associated with the given arc.
|
||||
*/
|
||||
public Color getColorForArc(Arc arc);
|
||||
|
||||
/**
|
||||
* @param arc Arc for which width should be retrieved.
|
||||
*
|
||||
* @return Width associated with the given arc.
|
||||
*/
|
||||
public int getWidthForArc(Arc arc);
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import java.awt.Dimension;
|
||||
|
||||
import org.insa.graphs.model.GraphStatistics.BoundingBox;
|
||||
|
||||
public class MercatorProjection implements Projection {
|
||||
|
||||
public static final double MAX_LATITUDE = 82;
|
||||
|
||||
public static final double MIN_LATITUDE = -MAX_LATITUDE;
|
||||
|
||||
// From Wikipedia... for the above max/min latitude.
|
||||
private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746;
|
||||
|
||||
private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE);
|
||||
private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE);
|
||||
|
||||
// Bounding box
|
||||
private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
|
||||
|
||||
// Projection of min and max latitude.
|
||||
private final double minLatitudeProj, maxLatitudeProj;
|
||||
|
||||
// Dimension of the image
|
||||
private final double width, height;
|
||||
|
||||
/**
|
||||
* Create a new MercatorProjection corresponding to the given BoundingBox and
|
||||
* maxSize.
|
||||
*
|
||||
* @param boundingBox Box for this projection.
|
||||
* @param maxSize Maximum size of any side (width / height) of the image to
|
||||
* which this projection should draw.
|
||||
*/
|
||||
public MercatorProjection(BoundingBox boundingBox, int maxSize) {
|
||||
// Find minimum/maximum longitude and latitude.
|
||||
this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
|
||||
this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
|
||||
this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
|
||||
this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
|
||||
|
||||
// Compute projection
|
||||
this.minLatitudeProj = projectY(this.minLatitude);
|
||||
this.maxLatitudeProj = projectY(this.maxLatitude);
|
||||
|
||||
Dimension imageDimension = computeImageSize(maxSize);
|
||||
this.width = imageDimension.getWidth();
|
||||
this.height = imageDimension.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the projection (without scaling) of the given latitude.
|
||||
*
|
||||
* @param latitude Latitude to project.
|
||||
*
|
||||
* @return Projection of the given latitude (without scaling).
|
||||
*/
|
||||
private static double projectY(double latitude) {
|
||||
double sinLatitude = Math.sin(latitude * Math.PI / 180.0);
|
||||
return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the dimension required for drawing a projection of the given box on
|
||||
* an image, ensuring that none of the side of image is greater than maxSize.
|
||||
*
|
||||
* @param maxSize Maximum side of any side of the image.
|
||||
*
|
||||
* @return Dimension corresponding to the preferred size for the image.
|
||||
*/
|
||||
protected Dimension computeImageSize(int maxSize) {
|
||||
double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0;
|
||||
double propHeight = (this.maxLatitudeProj - this.minLatitudeProj)
|
||||
/ (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT;
|
||||
|
||||
return propWidth < propHeight
|
||||
? new Dimension((int) (maxSize * propWidth / propHeight), maxSize)
|
||||
: new Dimension(maxSize, (int) (maxSize * propHeight / propWidth));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getImageWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getImageHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int latitudeToPixelY(float latitude) {
|
||||
return (int) ((this.maxLatitudeProj - projectY(latitude))
|
||||
/ (this.maxLatitudeProj - this.minLatitudeProj) * this.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int longitudeToPixelX(float longitude) {
|
||||
return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pixelYToLatitude(double py) {
|
||||
float y = (float) (this.maxLatitudeProj
|
||||
- (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj));
|
||||
return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pixelXToLongitude(double px) {
|
||||
return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude)
|
||||
+ this.minLongitude);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
import org.insa.graphs.model.GraphStatistics.BoundingBox;
|
||||
|
||||
public class PlateCarreProjection implements Projection {
|
||||
|
||||
// Bounding box
|
||||
private final float minLatitude, minLongitude, maxLatitude, maxLongitude;
|
||||
|
||||
// Dimension of the image
|
||||
private final double width, height;
|
||||
|
||||
/**
|
||||
* Create a new PlateCarreProjection corresponding to the given BoundingBox and
|
||||
* maxSize.
|
||||
*
|
||||
* @param boundingBox Box for this projection.
|
||||
* @param maxSize Maximum size of any side (width / height) of the image to
|
||||
* which this projection should draw.
|
||||
*/
|
||||
public PlateCarreProjection(BoundingBox boundingBox, int maxSize) {
|
||||
// Find minimum/maximum longitude and latitude.
|
||||
this.minLongitude = boundingBox.getTopLeftPoint().getLongitude();
|
||||
this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude();
|
||||
this.minLatitude = boundingBox.getBottomRightPoint().getLatitude();
|
||||
this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude();
|
||||
|
||||
float diffLon = maxLongitude - minLongitude, diffLat = maxLatitude - minLatitude;
|
||||
|
||||
this.width = diffLon < diffLat ? (int) (maxSize * diffLon / diffLat) : maxSize;
|
||||
this.height = diffLon < diffLat ? maxSize : (int) (maxSize * diffLat / diffLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getImageWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getImageHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int latitudeToPixelY(float latitude) {
|
||||
return (int) (this.height * (this.maxLatitude - latitude)
|
||||
/ (this.maxLatitude - this.minLatitude));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int longitudeToPixelX(float longitude) {
|
||||
return (int) (this.width * (longitude - this.minLongitude)
|
||||
/ (this.maxLongitude - this.minLongitude));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pixelYToLatitude(double py) {
|
||||
return (float) (this.maxLatitude
|
||||
- py / this.height * (this.maxLatitude - this.minLatitude));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float pixelXToLongitude(double px) {
|
||||
return (float) (px / this.width * (this.maxLongitude - this.minLongitude)
|
||||
+ this.minLongitude);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.insa.graphs.gui.drawing;
|
||||
|
||||
public interface Projection {
|
||||
|
||||
/**
|
||||
* @return Image width for this projection to work properly.
|
||||
*/
|
||||
public double getImageWidth();
|
||||
|
||||
/**
|
||||
* @return Image weight for this projection to work properly.
|
||||
*/
|
||||
public double getImageHeight();
|
||||
|
||||
/**
|
||||
* Project the given latitude on the image.
|
||||
*
|
||||
* @param latitude Latitude to project.
|
||||
*
|
||||
* @return Projected position of the latitude on the image.
|
||||
*/
|
||||
public int latitudeToPixelY(float latitude);
|
||||
|
||||
/**
|
||||
* Project the given longitude on the image.
|
||||
*
|
||||
* @param longitude Longitude to project.
|
||||
*
|
||||
* @return Projected position of the longitude on the image.
|
||||
*/
|
||||
public int longitudeToPixelX(float longitude);
|
||||
|
||||
/**
|
||||
* Retrieve the latitude associated to the given projected point.
|
||||
*
|
||||
* @param py Projected y-position for which latitude should be retrieved.
|
||||
*
|
||||
* @return The original latitude of the point.
|
||||
*/
|
||||
public float pixelYToLatitude(double py);
|
||||
|
||||
/**
|
||||
* Retrieve the longitude associated to the given projected point.
|
||||
*
|
||||
* @param px Projected x-position for which longitude should be retrieved.
|
||||
*
|
||||
* @return The original longitude of the point.
|
||||
*/
|
||||
public float pixelXToLongitude(double px);
|
||||
|
||||
}
|
|
@ -0,0 +1,747 @@
|
|||
package org.insa.graphs.gui.drawing.components;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.NoninvertibleTransformException;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.insa.graphs.gui.drawing.BasicGraphPalette;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.DrawingClickListener;
|
||||
import org.insa.graphs.gui.drawing.GraphPalette;
|
||||
import org.insa.graphs.gui.drawing.MercatorProjection;
|
||||
import org.insa.graphs.gui.drawing.PlateCarreProjection;
|
||||
import org.insa.graphs.gui.drawing.Projection;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
|
||||
import org.insa.graphs.gui.drawing.overlays.Overlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.Point;
|
||||
import org.insa.graphs.model.GraphStatistics.BoundingBox;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BasicDrawing extends JPanel implements Drawing {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 96779785877771827L;
|
||||
|
||||
private abstract class BasicOverlay implements Overlay {
|
||||
|
||||
// Visible?
|
||||
protected boolean visible;
|
||||
|
||||
// Color
|
||||
protected Color color;
|
||||
|
||||
public BasicOverlay(Color color) {
|
||||
this.visible = true;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Z level of this overlay (>= 1).
|
||||
*/
|
||||
public abstract int getZLevel();
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
BasicDrawing.this.overlays.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given overlay.
|
||||
*/
|
||||
public void draw(Graphics2D g) {
|
||||
if (this.visible) {
|
||||
drawImpl(g);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void drawImpl(Graphics2D g);
|
||||
|
||||
public void redraw() {
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private class BasicMarkerOverlay extends BasicOverlay implements MarkerOverlay {
|
||||
|
||||
// Marker width and height
|
||||
public static final int MARKER_WIDTH = 30, MARKER_HEIGHT = 60;
|
||||
|
||||
// Point of the marker.
|
||||
private Point point;
|
||||
|
||||
// Image to draw
|
||||
private Image image;
|
||||
|
||||
// Inner color and fill mode.
|
||||
private Color innerColor;
|
||||
private final AlphaMode alphaMode;
|
||||
|
||||
public BasicMarkerOverlay(Point point, Color color, Color inner, AlphaMode alphaMode) {
|
||||
super(color);
|
||||
this.point = point;
|
||||
this.image = MarkerUtils.getMarkerForColor(color, inner, alphaMode);
|
||||
this.innerColor = inner;
|
||||
this.alphaMode = alphaMode;
|
||||
}
|
||||
|
||||
public int getZLevel() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
this.innerColor = this.innerColor.equals(this.color) ? color : innerColor;
|
||||
super.setColor(color);
|
||||
this.image = MarkerUtils.getMarkerForColor(color, this.innerColor, alphaMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(Point point) {
|
||||
this.point = point;
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawImpl(Graphics2D graphics) {
|
||||
|
||||
int px = projection.longitudeToPixelX(getPoint().getLongitude());
|
||||
int py = projection.latitudeToPixelY(getPoint().getLatitude());
|
||||
|
||||
graphics.drawImage(this.image, px - MARKER_WIDTH / 2, py - MARKER_HEIGHT, MARKER_WIDTH,
|
||||
MARKER_HEIGHT, BasicDrawing.this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private class BasicPathOverlay extends BasicOverlay implements PathOverlay {
|
||||
|
||||
// List of points
|
||||
private final List<Point> points;
|
||||
|
||||
// Origin / Destination markers.
|
||||
private BasicMarkerOverlay origin, destination;
|
||||
|
||||
public BasicPathOverlay(List<Point> points, Color color, BasicMarkerOverlay origin,
|
||||
BasicMarkerOverlay destination) {
|
||||
super(color);
|
||||
this.points = points;
|
||||
this.origin = origin;
|
||||
this.destination = destination;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getZLevel() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
super.setColor(color);
|
||||
this.origin.setColor(color);
|
||||
this.destination.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawImpl(Graphics2D graphics) {
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
|
||||
graphics.setStroke(new BasicStroke(2));
|
||||
graphics.setColor(getColor());
|
||||
|
||||
Iterator<Point> itPoint = points.iterator();
|
||||
Point prev = itPoint.next();
|
||||
|
||||
while (itPoint.hasNext()) {
|
||||
Point curr = itPoint.next();
|
||||
|
||||
int x1 = projection.longitudeToPixelX(prev.getLongitude());
|
||||
int x2 = projection.longitudeToPixelX(curr.getLongitude());
|
||||
int y1 = projection.latitudeToPixelY(prev.getLatitude());
|
||||
int y2 = projection.latitudeToPixelY(curr.getLatitude());
|
||||
|
||||
graphics.drawLine(x1, y1, x2, y2);
|
||||
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.origin != null) {
|
||||
this.origin.draw(graphics);
|
||||
}
|
||||
if (this.destination != null) {
|
||||
this.destination.draw(graphics);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private class BasicPointSetOverlay extends BasicOverlay implements PointSetOverlay {
|
||||
|
||||
// Default point width
|
||||
private static final int DEFAULT_POINT_WIDTH = 5;
|
||||
|
||||
// Image for path / points
|
||||
private final BufferedImage image;
|
||||
private final Graphics2D graphics;
|
||||
|
||||
private int width = DEFAULT_POINT_WIDTH;
|
||||
|
||||
public BasicPointSetOverlay() {
|
||||
super(Color.BLACK);
|
||||
this.image = new BufferedImage(BasicDrawing.this.width, BasicDrawing.this.height,
|
||||
BufferedImage.TYPE_4BYTE_ABGR);
|
||||
this.graphics = image.createGraphics();
|
||||
this.graphics.setBackground(new Color(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
public int getZLevel() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
super.setColor(color);
|
||||
this.graphics.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidth(int width) {
|
||||
this.width = Math.max(2, width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidthAndColor(int width, Color color) {
|
||||
setWidth(width);
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point) {
|
||||
int x = projection.longitudeToPixelX(point.getLongitude()) - this.width / 2;
|
||||
int y = projection.latitudeToPixelY(point.getLatitude()) - this.width / 2;
|
||||
this.graphics.fillOval(x, y, this.width, this.width);
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, int width) {
|
||||
setWidth(width);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, Color color) {
|
||||
setColor(color);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, int width, Color color) {
|
||||
setWidth(width);
|
||||
setColor(color);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawImpl(Graphics2D g) {
|
||||
g.drawImage(this.image, 0, 0, BasicDrawing.this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class encapsulating a set of overlays.
|
||||
*
|
||||
*/
|
||||
private class BasicOverlays {
|
||||
|
||||
// List of overlays.
|
||||
private ArrayList<ArrayList<BasicOverlay>> overlays = new ArrayList<>();
|
||||
|
||||
public synchronized void draw(Graphics2D g) {
|
||||
// Clear overlays.
|
||||
for (ArrayList<BasicOverlay> arr: this.overlays) {
|
||||
for (BasicOverlay overlay: arr) {
|
||||
overlay.draw(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void remove(BasicOverlay overlay) {
|
||||
overlays.get(overlay.getZLevel() - 1).remove(overlay);
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
clear(true);
|
||||
}
|
||||
|
||||
public void clear(boolean repaint) {
|
||||
// Clear overlays.
|
||||
for (ArrayList<BasicOverlay> arr: this.overlays) {
|
||||
arr.clear();
|
||||
}
|
||||
// Repaint if requested.
|
||||
if (repaint) {
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public BasicOverlay add(BasicOverlay marker) {
|
||||
return add(marker, true);
|
||||
}
|
||||
|
||||
public synchronized BasicOverlay add(BasicOverlay overlay, boolean repaint) {
|
||||
|
||||
// Check if we have a level for this...
|
||||
for (int i = overlays.size(); i < overlay.getZLevel(); ++i) {
|
||||
overlays.add(new ArrayList<>());
|
||||
}
|
||||
|
||||
// Add overlay to the given list.
|
||||
overlays.get(overlay.getZLevel() - 1).add(overlay);
|
||||
|
||||
// Repaint if requested.
|
||||
if (repaint) {
|
||||
BasicDrawing.this.repaint();
|
||||
}
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Default path color.
|
||||
public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
|
||||
|
||||
// Default palette.
|
||||
public static final GraphPalette DEFAULT_PALETTE = new BasicGraphPalette();
|
||||
|
||||
// Maximum width for the drawing (in pixels).
|
||||
private static final int MAXIMUM_DRAWING_WIDTH = 2000;
|
||||
|
||||
private Projection projection;
|
||||
|
||||
// Width and height of the image
|
||||
private int width, height;
|
||||
|
||||
// Zoom controls
|
||||
private MapZoomControls zoomControls;
|
||||
private ZoomAndPanListener zoomAndPanListener;
|
||||
|
||||
//
|
||||
private Image graphImage = null;
|
||||
private Graphics2D graphGraphics = null;
|
||||
|
||||
// List of image for markers
|
||||
private BasicOverlays overlays = new BasicOverlays();
|
||||
|
||||
// Mapping DrawingClickListener -> MouseEventListener
|
||||
private List<DrawingClickListener> drawingClickListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new BasicDrawing.
|
||||
*
|
||||
*/
|
||||
public BasicDrawing() {
|
||||
setLayout(null);
|
||||
this.setBackground(new Color(245, 245, 245));
|
||||
this.zoomAndPanListener = new ZoomAndPanListener(this,
|
||||
ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20, 1.2);
|
||||
|
||||
// Try...
|
||||
try {
|
||||
this.zoomControls = new MapZoomControls(this, 0,
|
||||
ZoomAndPanListener.DEFAULT_MIN_ZOOM_LEVEL, 20);
|
||||
this.zoomControls.addZoomInListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
zoomAndPanListener.zoomIn();
|
||||
}
|
||||
});
|
||||
this.zoomControls.addZoomOutListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
zoomAndPanListener.zoomOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent evt) {
|
||||
if (zoomControls.contains(evt.getPoint())) {
|
||||
return;
|
||||
}
|
||||
Point lonlat = null;
|
||||
try {
|
||||
lonlat = getLongitudeLatitude(evt);
|
||||
}
|
||||
catch (NoninvertibleTransformException e) {
|
||||
return;
|
||||
}
|
||||
for (DrawingClickListener listener: drawingClickListeners) {
|
||||
listener.mouseClicked(lonlat);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g1) {
|
||||
super.paintComponent(g1);
|
||||
Graphics2D g = (Graphics2D) g1;
|
||||
AffineTransform sTransform = g.getTransform();
|
||||
g.setColor(this.getBackground());
|
||||
g.fillRect(0, 0, getWidth(), getHeight());
|
||||
g.setTransform(zoomAndPanListener.getCoordTransform());
|
||||
|
||||
if (graphImage != null) {
|
||||
// Draw graph
|
||||
g.drawImage(graphImage, 0, 0, this);
|
||||
}
|
||||
|
||||
// Draw markers
|
||||
this.overlays.draw(g);
|
||||
|
||||
g.setTransform(sTransform);
|
||||
if (this.zoomControls != null) {
|
||||
this.zoomControls.setZoomLevel(this.zoomAndPanListener.getZoomLevel());
|
||||
this.zoomControls.draw(g, getWidth() - this.zoomControls.getWidth() - 20,
|
||||
this.getHeight() - this.zoomControls.getHeight() - 10, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.insa.graphics.drawing.Drawing#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
if (this.graphGraphics != null) {
|
||||
this.graphGraphics.clearRect(0, 0, this.width, this.height);
|
||||
}
|
||||
this.overlays.clear(false);
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.insa.graphics.drawing.Drawing#clearOverlays()
|
||||
*/
|
||||
@Override
|
||||
public void clearOverlays() {
|
||||
this.overlays.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current ZoomAndPanListener associated with this drawing.
|
||||
*/
|
||||
public ZoomAndPanListener getZoomAndPanListener() {
|
||||
return this.zoomAndPanListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the longitude and latitude corresponding to the given position of the
|
||||
* MouseEvent.
|
||||
*
|
||||
* @param event MouseEvent from which longitude/latitude should be retrieved.
|
||||
*
|
||||
* @return Point representing the projection of the MouseEvent position in the
|
||||
* graph/map.
|
||||
*
|
||||
* @throws NoninvertibleTransformException if the actual transformation is
|
||||
* invalid.
|
||||
*/
|
||||
protected Point getLongitudeLatitude(MouseEvent event) throws NoninvertibleTransformException {
|
||||
// Get the point using the inverse transform of the Zoom/Pan object, this gives
|
||||
// us
|
||||
// a point within the drawing box (between [0, 0] and [width, height]).
|
||||
Point2D ptDst = this.zoomAndPanListener.getCoordTransform()
|
||||
.inverseTransform(event.getPoint(), null);
|
||||
|
||||
// Inverse the "projection" on x/y to get longitude and latitude.
|
||||
return new Point(projection.pixelXToLongitude(ptDst.getX()),
|
||||
projection.pixelYToLatitude(ptDst.getY()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.insa.graphics.drawing.Drawing#addDrawingClickListener(org.insa.graphics.
|
||||
* drawing.DrawingClickListener)
|
||||
*/
|
||||
@Override
|
||||
public void addDrawingClickListener(DrawingClickListener listener) {
|
||||
this.drawingClickListeners.add(listener);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.insa.graphics.drawing.Drawing#removeDrawingClickListener(org.insa.
|
||||
* graphics.drawing.DrawingClickListener)
|
||||
*/
|
||||
@Override
|
||||
public void removeDrawingClickListener(DrawingClickListener listener) {
|
||||
this.drawingClickListeners.remove(listener);
|
||||
}
|
||||
|
||||
public BasicMarkerOverlay createMarker(Point point, Color outer, Color inner, AlphaMode mode) {
|
||||
return new BasicMarkerOverlay(point, outer, inner, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
|
||||
return (MarkerOverlay) this.overlays.add(createMarker(point, outer, inner, mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointSetOverlay createPointSetOverlay() {
|
||||
return (PointSetOverlay) this.overlays.add(new BasicPointSetOverlay(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointSetOverlay createPointSetOverlay(int width, Color color) {
|
||||
PointSetOverlay ps = createPointSetOverlay();
|
||||
ps.setWidthAndColor(width, color);
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given arc.
|
||||
*
|
||||
* @param arc Arc to draw.
|
||||
* @param palette Palette to use to retrieve color and width for arc, or null to
|
||||
* use current settings.
|
||||
*/
|
||||
protected void drawArc(Arc arc, GraphPalette palette, boolean repaint) {
|
||||
List<Point> pts = arc.getPoints();
|
||||
if (!pts.isEmpty()) {
|
||||
if (palette != null) {
|
||||
this.graphGraphics.setColor(palette.getColorForArc(arc));
|
||||
this.graphGraphics.setStroke(new BasicStroke(palette.getWidthForArc(arc)));
|
||||
}
|
||||
Iterator<Point> it1 = pts.iterator();
|
||||
Point prev = it1.next();
|
||||
while (it1.hasNext()) {
|
||||
Point curr = it1.next();
|
||||
|
||||
int x1 = projection.longitudeToPixelX(prev.getLongitude());
|
||||
int x2 = projection.longitudeToPixelX(curr.getLongitude());
|
||||
int y1 = projection.latitudeToPixelY(prev.getLatitude());
|
||||
int y2 = projection.latitudeToPixelY(curr.getLatitude());
|
||||
|
||||
graphGraphics.drawLine(x1, y1, x2, y2);
|
||||
prev = curr;
|
||||
}
|
||||
}
|
||||
if (repaint) {
|
||||
this.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the drawing for the given graph.
|
||||
*
|
||||
* @param graph
|
||||
*/
|
||||
protected void initialize(Graph graph) {
|
||||
|
||||
// Clear everything.
|
||||
this.clear();
|
||||
|
||||
BoundingBox box = graph.getGraphInformation().getBoundingBox();
|
||||
|
||||
// Find minimum/maximum longitude and latitude.
|
||||
float minLon = box.getTopLeftPoint().getLongitude(),
|
||||
maxLon = box.getBottomRightPoint().getLongitude(),
|
||||
minLat = box.getBottomRightPoint().getLatitude(),
|
||||
maxLat = box.getTopLeftPoint().getLatitude();
|
||||
|
||||
// Add a little delta to avoid drawing on the edge...
|
||||
float diffLon = maxLon - minLon, diffLat = maxLat - minLat;
|
||||
float deltaLon = 0.01f * diffLon, deltaLat = 0.01f * diffLat;
|
||||
|
||||
// Create the projection and retrieve width and height for the box.
|
||||
BoundingBox extendedBox = box.extend(deltaLon, deltaLat, deltaLon, deltaLat);
|
||||
|
||||
// Special projection for non-realistic maps...
|
||||
if (graph.getMapId().startsWith("0x")) {
|
||||
projection = new PlateCarreProjection(extendedBox, MAXIMUM_DRAWING_WIDTH / 4);
|
||||
}
|
||||
else {
|
||||
projection = new MercatorProjection(extendedBox, MAXIMUM_DRAWING_WIDTH);
|
||||
}
|
||||
this.width = (int) projection.getImageWidth();
|
||||
this.height = (int) projection.getImageHeight();
|
||||
|
||||
// Create the image
|
||||
BufferedImage img = new BufferedImage(this.width, this.height,
|
||||
BufferedImage.TYPE_3BYTE_BGR);
|
||||
this.graphImage = img;
|
||||
this.graphGraphics = img.createGraphics();
|
||||
this.graphGraphics.setBackground(this.getBackground());
|
||||
this.graphGraphics.clearRect(0, 0, this.width, this.height);
|
||||
|
||||
// Set the zoom and pan listener
|
||||
|
||||
double scale = 1 / Math.max(this.width / (double) this.getWidth(),
|
||||
this.height / (double) this.getHeight());
|
||||
|
||||
this.zoomAndPanListener.setCoordTransform(this.graphGraphics.getTransform());
|
||||
this.zoomAndPanListener.getCoordTransform().translate(
|
||||
(this.getWidth() - this.width * scale) / 2,
|
||||
(this.getHeight() - this.height * scale) / 2);
|
||||
this.zoomAndPanListener.getCoordTransform().scale(scale, scale);
|
||||
this.zoomAndPanListener.setZoomLevel(0);
|
||||
this.zoomControls.setZoomLevel(0);
|
||||
|
||||
// Repaint
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawGraph(Graph graph, GraphPalette palette) {
|
||||
int repaintModulo = Math.max(1, graph.size() / 100);
|
||||
|
||||
// Initialize the buffered image
|
||||
|
||||
this.initialize(graph);
|
||||
|
||||
// Remove zoom and pan listener
|
||||
this.removeMouseListener(zoomAndPanListener);
|
||||
this.removeMouseMotionListener(zoomAndPanListener);
|
||||
this.removeMouseWheelListener(zoomAndPanListener);
|
||||
|
||||
for (Node node: graph.getNodes()) {
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
// Draw arcs only if there are one-way arcs or if origin is lower than
|
||||
// destination, avoid drawing two-ways arc twice.
|
||||
if (arc.getRoadInformation().isOneWay()
|
||||
|| arc.getOrigin().compareTo(arc.getDestination()) < 0) {
|
||||
drawArc(arc, palette, false);
|
||||
}
|
||||
}
|
||||
if (node.getId() % repaintModulo == 0) {
|
||||
this.repaint();
|
||||
}
|
||||
}
|
||||
this.repaint();
|
||||
|
||||
// Re-add zoom and pan listener
|
||||
this.addMouseListener(zoomAndPanListener);
|
||||
this.addMouseMotionListener(zoomAndPanListener);
|
||||
this.addMouseWheelListener(zoomAndPanListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawGraph(Graph graph) {
|
||||
drawGraph(graph, DEFAULT_PALETTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, Color color, boolean markers) {
|
||||
List<Point> points = new ArrayList<Point>();
|
||||
if (!path.isEmpty()) {
|
||||
points.add(path.getOrigin().getPoint());
|
||||
for (Arc arc: path.getArcs()) {
|
||||
Iterator<Point> itPoint = arc.getPoints().iterator();
|
||||
// Discard origin each time
|
||||
itPoint.next();
|
||||
while (itPoint.hasNext()) {
|
||||
points.add(itPoint.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
BasicMarkerOverlay origin = null, destination = null;
|
||||
if (markers && !path.isEmpty()) {
|
||||
origin = createMarker(path.getOrigin().getPoint(), color, color, AlphaMode.TRANSPARENT);
|
||||
destination = createMarker(path.getDestination().getPoint(), color, color,
|
||||
AlphaMode.TRANSPARENT);
|
||||
}
|
||||
return (PathOverlay) this.overlays
|
||||
.add(new BasicPathOverlay(points, color, origin, destination));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, Color color) {
|
||||
return drawPath(path, color, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path) {
|
||||
return drawPath(path, DEFAULT_PATH_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, boolean markers) {
|
||||
return drawPath(path, DEFAULT_PATH_COLOR, markers);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,521 @@
|
|||
package org.insa.graphs.gui.drawing.components;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.DrawingClickListener;
|
||||
import org.insa.graphs.gui.drawing.GraphPalette;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerAutoScaling;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerUtils;
|
||||
import org.insa.graphs.gui.drawing.overlays.Overlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PathOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
|
||||
import org.insa.graphs.gui.drawing.overlays.PolylineAutoScaling;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.Point;
|
||||
import org.mapsforge.core.graphics.GraphicFactory;
|
||||
import org.mapsforge.core.model.BoundingBox;
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
import org.mapsforge.core.model.MapPosition;
|
||||
import org.mapsforge.core.util.LatLongUtils;
|
||||
import org.mapsforge.core.util.Parameters;
|
||||
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
|
||||
import org.mapsforge.map.awt.util.AwtUtil;
|
||||
import org.mapsforge.map.awt.view.MapView;
|
||||
import org.mapsforge.map.datastore.MapDataStore;
|
||||
import org.mapsforge.map.layer.Layer;
|
||||
import org.mapsforge.map.layer.Layers;
|
||||
import org.mapsforge.map.layer.cache.TileCache;
|
||||
import org.mapsforge.map.layer.hills.HillsRenderConfig;
|
||||
import org.mapsforge.map.layer.overlay.Marker;
|
||||
import org.mapsforge.map.layer.overlay.Polygon;
|
||||
import org.mapsforge.map.layer.renderer.TileRendererLayer;
|
||||
import org.mapsforge.map.model.DisplayModel;
|
||||
import org.mapsforge.map.model.IMapViewPosition;
|
||||
import org.mapsforge.map.model.Model;
|
||||
import org.mapsforge.map.reader.MapFile;
|
||||
import org.mapsforge.map.rendertheme.InternalRenderTheme;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MapViewDrawing extends MapView implements Drawing {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 8606967833704938092L;
|
||||
|
||||
/**
|
||||
* Base Overlay for MapViewDrawing overlays.
|
||||
*
|
||||
*/
|
||||
private abstract class MapViewOverlay implements Overlay {
|
||||
|
||||
// Marker associated.
|
||||
protected Layer[] layers;
|
||||
|
||||
// Current color
|
||||
protected Color color;
|
||||
|
||||
public MapViewOverlay(Layer[] layers, Color color) {
|
||||
this.layers = layers;
|
||||
for (Layer layer: this.layers) {
|
||||
MapViewDrawing.this.getLayerManager().getLayers().add(layer);
|
||||
}
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
for (Layer layer: layers) {
|
||||
layer.setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
if (this.layers.length == 0) {
|
||||
return true;
|
||||
}
|
||||
return this.layers[0].isVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
Layers mlayers = MapViewDrawing.this.getLayerManager().getLayers();
|
||||
for (Layer layer: layers) {
|
||||
mlayers.remove(layer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redraw() {
|
||||
MapViewDrawing.this.getLayerManager().redrawLayers();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MarkerOverlay for MapViewDrawing.
|
||||
*
|
||||
*/
|
||||
private class MapViewMarkerOverlay extends MapViewOverlay implements MarkerOverlay {
|
||||
|
||||
private final AlphaMode alphaMode;
|
||||
private Color innerColor;
|
||||
|
||||
public MapViewMarkerOverlay(Marker marker, Color outer, Color innerColor,
|
||||
AlphaMode alphaMode) {
|
||||
super(new Layer[] { marker }, outer);
|
||||
this.innerColor = innerColor;
|
||||
this.alphaMode = alphaMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPoint() {
|
||||
Marker marker = (Marker) super.layers[0];
|
||||
return new Point((float) marker.getLatLong().getLongitude(),
|
||||
(float) marker.getLatLong().getLatitude());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color outer) {
|
||||
this.innerColor = this.innerColor.equals(this.color) ? outer : this.innerColor;
|
||||
super.setColor(color);
|
||||
MarkerAutoScaling marker = (MarkerAutoScaling) super.layers[0];
|
||||
marker.setImage(MarkerUtils.getMarkerForColor(color, this.innerColor, this.alphaMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(Point point) {
|
||||
MarkerAutoScaling marker = (MarkerAutoScaling) this.layers[0];
|
||||
this.delete();
|
||||
marker = new MarkerAutoScaling(convertPoint(point), marker.getImage());
|
||||
this.layers[0] = marker;
|
||||
MapViewDrawing.this.getLayerManager().getLayers().add(marker);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* PathOverlay for MapViewDrawing.
|
||||
*
|
||||
*/
|
||||
private class MapViewPathOverlay extends MapViewOverlay implements PathOverlay {
|
||||
|
||||
public MapViewPathOverlay(PolylineAutoScaling path, MarkerAutoScaling origin,
|
||||
MarkerAutoScaling destination) {
|
||||
super(new Layer[] { path, origin, destination }, path.getColor());
|
||||
}
|
||||
|
||||
public MapViewPathOverlay(PolylineAutoScaling path) {
|
||||
super(new Layer[] { path }, path.getColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
super.setColor(color);
|
||||
((PolylineAutoScaling) this.layers[0]).setColor(color);
|
||||
((MarkerAutoScaling) this.layers[1])
|
||||
.setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
|
||||
((MarkerAutoScaling) this.layers[2])
|
||||
.setImage(MarkerUtils.getMarkerForColor(color, color, AlphaMode.TRANSPARENT));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* PointSetOverlay for MapViewDrawing - Not currently implemented.
|
||||
*
|
||||
*/
|
||||
private class MapViewPointSetOverlay extends MapViewOverlay implements PointSetOverlay {
|
||||
|
||||
private List<Point> points = new ArrayList<>();
|
||||
private final Polygon polygon;
|
||||
|
||||
private List<Point> convexHull(List<Point> p) {
|
||||
if (p.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
p.sort((p1, p2) -> Float.compare(p1.getLongitude(), p2.getLongitude()));
|
||||
List<Point> h = new ArrayList<>();
|
||||
|
||||
// lower hull
|
||||
for (Point pt: p) {
|
||||
while (h.size() >= 2 && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
|
||||
h.remove(h.size() - 1);
|
||||
}
|
||||
h.add(pt);
|
||||
}
|
||||
|
||||
// upper hull
|
||||
int t = h.size() + 1;
|
||||
for (int i = p.size() - 1; i >= 0; i--) {
|
||||
Point pt = p.get(i);
|
||||
while (h.size() >= t && !ccw(h.get(h.size() - 2), h.get(h.size() - 1), pt)) {
|
||||
h.remove(h.size() - 1);
|
||||
}
|
||||
h.add(pt);
|
||||
}
|
||||
|
||||
h.remove(h.size() - 1);
|
||||
return h;
|
||||
}
|
||||
|
||||
// ccw returns true if the three points make a counter-clockwise turn
|
||||
private boolean ccw(Point a, Point b, Point c) {
|
||||
return ((b.getLongitude() - a.getLongitude())
|
||||
* (c.getLatitude() - a.getLatitude())) > ((b.getLatitude() - a.getLatitude())
|
||||
* (c.getLongitude() - a.getLongitude()));
|
||||
}
|
||||
|
||||
public MapViewPointSetOverlay() {
|
||||
super(new Layer[] { new Polygon(GRAPHIC_FACTORY.createPaint(), null, GRAPHIC_FACTORY) },
|
||||
Color.BLACK);
|
||||
polygon = (Polygon) this.layers[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColor(Color color) {
|
||||
super.setColor(color);
|
||||
polygon.getPaintFill().setColor(GRAPHIC_FACTORY.createColor(100, color.getRed(),
|
||||
color.getGreen(), color.getBlue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidth(int width) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidthAndColor(int width, Color color) {
|
||||
setWidth(width);
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point) {
|
||||
points.add(point);
|
||||
this.points = convexHull(points);
|
||||
polygon.setPoints(this.points.stream().map(MapViewDrawing.this::convertPoint)
|
||||
.collect(Collectors.toList()));
|
||||
polygon.requestRedraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, int width) {
|
||||
setWidth(width);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, Color color) {
|
||||
setColor(color);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPoint(Point point, int width, Color color) {
|
||||
setWidth(width);
|
||||
setColor(color);
|
||||
addPoint(point);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Default path color.
|
||||
public static final Color DEFAULT_PATH_COLOR = new Color(66, 134, 244);
|
||||
|
||||
// Graphic factory.
|
||||
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
|
||||
|
||||
// Default tile size.
|
||||
private static final int DEFAULT_TILE_SIZE = 512;
|
||||
|
||||
// List of listeners.
|
||||
private ArrayList<DrawingClickListener> drawingClickListeners = new ArrayList<>();
|
||||
|
||||
// Tile size
|
||||
private int tileSize;
|
||||
|
||||
// Zoom controls
|
||||
private MapZoomControls zoomControls;
|
||||
|
||||
public MapViewDrawing() {
|
||||
super();
|
||||
Parameters.NUMBER_OF_THREADS = 2;
|
||||
Parameters.SQUARE_FRAME_BUFFER = false;
|
||||
|
||||
getMapScaleBar().setVisible(true);
|
||||
DisplayModel model = getModel().displayModel;
|
||||
this.tileSize = DEFAULT_TILE_SIZE;
|
||||
model.setFixedTileSize(this.tileSize);
|
||||
|
||||
this.setZoomLevelMin((byte) 0);
|
||||
this.setZoomLevelMax((byte) 20);
|
||||
|
||||
// Try...
|
||||
try {
|
||||
this.zoomControls = new MapZoomControls(this, 0, 0, 20);
|
||||
this.zoomControls.addZoomInListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
getModel().mapViewPosition.zoomIn();
|
||||
}
|
||||
});
|
||||
this.zoomControls.addZoomOutListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
getModel().mapViewPosition.zoomOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.mapsforge.map.awt.view.MapView#paint(java.awt.Graphics)
|
||||
*/
|
||||
@Override
|
||||
public void paint(Graphics graphics) {
|
||||
super.paint(graphics);
|
||||
if (this.zoomControls != null) {
|
||||
this.zoomControls.setZoomLevel(this.getModel().mapViewPosition.getZoomLevel());
|
||||
this.zoomControls.draw((Graphics2D) graphics,
|
||||
getWidth() - this.zoomControls.getWidth() - 20,
|
||||
this.getHeight() - this.zoomControls.getHeight() - 10, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.insa.graphics.drawing.Drawing#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
getLayerManager().getLayers().clear();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.insa.graphics.drawing.Drawing#clearOverlays()
|
||||
*/
|
||||
@Override
|
||||
public void clearOverlays() {
|
||||
Layers layers = getLayerManager().getLayers();
|
||||
for (Layer layer: layers) {
|
||||
if (layer instanceof PolylineAutoScaling || layer instanceof MarkerAutoScaling) {
|
||||
getLayerManager().getLayers().remove(layer, false);
|
||||
}
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
protected LatLong convertPoint(Point point) {
|
||||
return new LatLong(point.getLatitude(), point.getLongitude());
|
||||
}
|
||||
|
||||
private TileRendererLayer createTileRendererLayer(TileCache tileCache,
|
||||
MapDataStore mapDataStore, IMapViewPosition mapViewPosition,
|
||||
HillsRenderConfig hillsRenderConfig) {
|
||||
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, mapDataStore,
|
||||
mapViewPosition, false, true, false, GRAPHIC_FACTORY, hillsRenderConfig) {
|
||||
@Override
|
||||
public boolean onTap(LatLong tapLatLong, org.mapsforge.core.model.Point layerXY,
|
||||
org.mapsforge.core.model.Point tapXY) {
|
||||
if (zoomControls.contains(new java.awt.Point((int) tapXY.x, (int) tapXY.y))) {
|
||||
return false;
|
||||
}
|
||||
Point pt = new Point((float) tapLatLong.getLongitude(),
|
||||
(float) tapLatLong.getLatitude());
|
||||
for (DrawingClickListener listener: MapViewDrawing.this.drawingClickListeners) {
|
||||
listener.mouseClicked(pt);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.DEFAULT);
|
||||
return tileRendererLayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDrawingClickListener(DrawingClickListener listener) {
|
||||
this.drawingClickListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDrawingClickListener(DrawingClickListener listener) {
|
||||
this.drawingClickListeners.remove(listener);
|
||||
}
|
||||
|
||||
protected MarkerAutoScaling createMarker(Point point, Color outer, Color inner,
|
||||
AlphaMode mode) {
|
||||
Image image = MarkerUtils.getMarkerForColor(outer, inner, mode);
|
||||
return new MarkerAutoScaling(convertPoint(point), image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkerOverlay drawMarker(Point point, Color outer, Color inner, AlphaMode mode) {
|
||||
return new MapViewMarkerOverlay(createMarker(point, outer, inner, mode), outer, inner,
|
||||
mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointSetOverlay createPointSetOverlay() {
|
||||
return new MapViewPointSetOverlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointSetOverlay createPointSetOverlay(int width, Color color) {
|
||||
PointSetOverlay ps = new MapViewPointSetOverlay();
|
||||
ps.setWidthAndColor(width, color);
|
||||
return ps;
|
||||
}
|
||||
|
||||
public void drawGraph(File file) {
|
||||
|
||||
// Tile cache
|
||||
TileCache tileCache = AwtUtil.createTileCache(tileSize,
|
||||
getModel().frameBufferModel.getOverdrawFactor(), 1024,
|
||||
new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()));
|
||||
|
||||
// Layers
|
||||
Layers layers = getLayerManager().getLayers();
|
||||
|
||||
MapDataStore mapDataStore = new MapFile(file);
|
||||
TileRendererLayer tileRendererLayer = createTileRendererLayer(tileCache, mapDataStore,
|
||||
getModel().mapViewPosition, null);
|
||||
layers.add(tileRendererLayer);
|
||||
BoundingBox boundingBox = mapDataStore.boundingBox();
|
||||
|
||||
final Model model = getModel();
|
||||
if (model.mapViewPosition.getZoomLevel() == 0
|
||||
|| !boundingBox.contains(model.mapViewPosition.getCenter())) {
|
||||
byte zoomLevel = LatLongUtils.zoomForBounds(model.mapViewDimension.getDimension(),
|
||||
boundingBox, model.displayModel.getTileSize());
|
||||
model.mapViewPosition
|
||||
.setMapPosition(new MapPosition(boundingBox.getCenterPoint(), zoomLevel));
|
||||
zoomControls.setZoomLevel(zoomLevel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawGraph(Graph graph, GraphPalette palette) {
|
||||
throw new RuntimeException("Not implemented, use drawGraph(File).");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawGraph(Graph graph) {
|
||||
throw new RuntimeException("Not implemented, use drawGraph(File).");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, Color color, boolean markers) {
|
||||
PolylineAutoScaling line = new PolylineAutoScaling(1, color);
|
||||
ArrayList<Point> points = new ArrayList<>(path.getArcs().size() * 4);
|
||||
for (Arc arc: path.getArcs()) {
|
||||
points.addAll(arc.getPoints());
|
||||
}
|
||||
line.addAll(points);
|
||||
PathOverlay overlay = null;
|
||||
if (markers) {
|
||||
MarkerAutoScaling origin = createMarker(path.getOrigin().getPoint(), color, color,
|
||||
AlphaMode.TRANSPARENT),
|
||||
destination = createMarker(path.getDestination().getPoint(), color, color,
|
||||
AlphaMode.TRANSPARENT);
|
||||
overlay = new MapViewPathOverlay(line, origin, destination);
|
||||
}
|
||||
else {
|
||||
overlay = new MapViewPathOverlay(line);
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, Color color) {
|
||||
return drawPath(path, color, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path) {
|
||||
return drawPath(path, DEFAULT_PATH_COLOR, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathOverlay drawPath(Path path, boolean markers) {
|
||||
return drawPath(path, DEFAULT_PATH_COLOR, markers);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package org.insa.graphs.gui.drawing.components;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public class MapZoomControls {
|
||||
|
||||
// Default ID for action events
|
||||
private static final int ZOOM_IN_ACTION_ID = 0x1;
|
||||
private static final String ZOOM_IN_ACTION_NAME = "ZoomIn";
|
||||
|
||||
private static final int ZOOM_OUT_ACTION_ID = 0x2;
|
||||
private static final String ZOOM_OUT_ACTION_NAME = "ZoomOut";
|
||||
|
||||
// Height
|
||||
private static final int DEFAULT_HEIGHT = 20;
|
||||
|
||||
// Default spacing between mark
|
||||
private static final int DEFAULT_SPACING = 4;
|
||||
|
||||
// Zoom ticks ratio from height (not the current one)
|
||||
private static final double ZOOM_TICK_HEIGHT_RATIO = 0.3;
|
||||
|
||||
// Zoom ticks color
|
||||
private static final Color ZOOM_TICK_COLOR = Color.GRAY;
|
||||
|
||||
// Current zoom ticks ratio from height
|
||||
private static final double CURRENT_ZOOM_TICK_HEIGHT_RATIO = 0.8;
|
||||
|
||||
// Zoom ticks color
|
||||
private static final Color CURRENT_ZOOM_TICK_COLOR = new Color(25, 25, 25);
|
||||
|
||||
// Use half mark or not
|
||||
private boolean halfMark = true;
|
||||
|
||||
private int currentLevel = 0;
|
||||
private final int minLevel, maxLevel;
|
||||
|
||||
// Zoom in/out image and their rectangles.
|
||||
private final Image zoomIn, zoomOut;
|
||||
private final Rectangle zoomInRect = new Rectangle(0, 0, 0, 0),
|
||||
zoomOutRect = new Rectangle(0, 0, 0, 0);
|
||||
|
||||
// List of listeners
|
||||
private final List<ActionListener> zoomInListeners = new ArrayList<>();
|
||||
private final List<ActionListener> zoomOutListeners = new ArrayList<>();
|
||||
|
||||
public MapZoomControls(Component component, final int defaultZoom, final int minZoom,
|
||||
final int maxZoom) throws IOException {
|
||||
|
||||
zoomIn = ImageIO.read(getClass().getResourceAsStream("/zoomIn.png"))
|
||||
.getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
|
||||
zoomOut = ImageIO.read(getClass().getResourceAsStream("/zoomOut.png"))
|
||||
.getScaledInstance(DEFAULT_HEIGHT, DEFAULT_HEIGHT, Image.SCALE_SMOOTH);
|
||||
|
||||
this.currentLevel = defaultZoom;
|
||||
this.minLevel = minZoom;
|
||||
this.maxLevel = maxZoom;
|
||||
|
||||
component.addMouseMotionListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
if (zoomInRect.contains(e.getPoint()) || zoomOutRect.contains(e.getPoint())) {
|
||||
component.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
else {
|
||||
component.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
component.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (zoomInRect.contains(e.getPoint()) && currentLevel < maxLevel) {
|
||||
currentLevel += 1;
|
||||
for (ActionListener al: zoomInListeners) {
|
||||
al.actionPerformed(
|
||||
new ActionEvent(this, ZOOM_IN_ACTION_ID, ZOOM_IN_ACTION_NAME));
|
||||
}
|
||||
}
|
||||
else if (zoomOutRect.contains(e.getPoint()) && currentLevel > minLevel) {
|
||||
currentLevel -= 1;
|
||||
for (ActionListener al: zoomOutListeners) {
|
||||
al.actionPerformed(
|
||||
new ActionEvent(this, ZOOM_OUT_ACTION_ID, ZOOM_OUT_ACTION_NAME));
|
||||
}
|
||||
}
|
||||
component.repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a zoom-in listener.
|
||||
*
|
||||
* @param listener Zoom-in listener to add to this MapZoomControls instance.
|
||||
*/
|
||||
public void addZoomInListener(ActionListener listener) {
|
||||
this.zoomInListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a zoom-out listener.
|
||||
*
|
||||
* @param listener Zoom-out listener to add to this MapZoomControls instance.
|
||||
*/
|
||||
public void addZoomOutListener(ActionListener listener) {
|
||||
this.zoomOutListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current zoom level.
|
||||
*/
|
||||
public int getZoomLevel() {
|
||||
return this.currentLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current zoom level without requesting a redraw.
|
||||
*
|
||||
* @param level Zoom level to set.
|
||||
*/
|
||||
public void setZoomLevel(int level) {
|
||||
this.currentLevel = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Height of this "component" when drawn.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Width of this "component" when drawn.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return DEFAULT_HEIGHT + 2 + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2
|
||||
+ DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a point is contained inside an element of this zoom controls, useful
|
||||
* to avoid spurious click listeners.
|
||||
*
|
||||
* @param point Point to check.
|
||||
*
|
||||
* @return true if the given point correspond to an element of this zoom
|
||||
* controls.
|
||||
*/
|
||||
public boolean contains(Point point) {
|
||||
return zoomInRect.contains(point) || zoomOutRect.contains(point);
|
||||
}
|
||||
|
||||
protected void draw(Graphics2D g, int xoffset, int yoffset, ImageObserver observer) {
|
||||
|
||||
int height = getHeight();
|
||||
|
||||
// Draw icon
|
||||
g.drawImage(zoomOut, xoffset, yoffset, observer);
|
||||
zoomOutRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
|
||||
|
||||
g.setStroke(new BasicStroke(1));
|
||||
|
||||
// Draw ticks
|
||||
xoffset += DEFAULT_HEIGHT + 2;
|
||||
g.setColor(ZOOM_TICK_COLOR);
|
||||
g.drawLine(xoffset, yoffset + height / 2,
|
||||
xoffset + (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1,
|
||||
yoffset + height / 2);
|
||||
for (int i = 0; i <= (this.maxLevel - this.minLevel); i += halfMark ? 2 : 1) {
|
||||
g.drawLine(xoffset + i * DEFAULT_SPACING,
|
||||
yoffset + (int) (height * (1 - ZOOM_TICK_HEIGHT_RATIO) / 2),
|
||||
xoffset + i * DEFAULT_SPACING,
|
||||
yoffset + (int) (height * (1 + ZOOM_TICK_HEIGHT_RATIO) / 2));
|
||||
}
|
||||
|
||||
// Draw current ticks
|
||||
g.setColor(CURRENT_ZOOM_TICK_COLOR);
|
||||
g.drawLine(xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
|
||||
yoffset + (int) (height * (1 - CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2),
|
||||
xoffset + (currentLevel - this.minLevel) * DEFAULT_SPACING,
|
||||
yoffset + (int) (height * (1 + CURRENT_ZOOM_TICK_HEIGHT_RATIO) / 2));
|
||||
|
||||
xoffset += (this.maxLevel - this.minLevel) * DEFAULT_SPACING + 1 + 2;
|
||||
|
||||
g.drawImage(zoomIn, xoffset, yoffset, observer);
|
||||
zoomInRect.setBounds(xoffset, yoffset, DEFAULT_HEIGHT, DEFAULT_HEIGHT);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package org.insa.graphs.gui.drawing.components;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.NoninvertibleTransformException;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener {
|
||||
public static final int DEFAULT_MIN_ZOOM_LEVEL = -20;
|
||||
public static final int DEFAULT_MAX_ZOOM_LEVEL = 10;
|
||||
public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2;
|
||||
|
||||
private Component targetComponent;
|
||||
|
||||
private int zoomLevel = 0;
|
||||
private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL;
|
||||
private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL;
|
||||
private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR;
|
||||
|
||||
private Point dragStartScreen;
|
||||
private Point dragEndScreen;
|
||||
private AffineTransform coordTransform = new AffineTransform();
|
||||
|
||||
public ZoomAndPanListener(Component targetComponent) {
|
||||
this.targetComponent = targetComponent;
|
||||
}
|
||||
|
||||
public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel,
|
||||
double zoomMultiplicationFactor) {
|
||||
this.targetComponent = targetComponent;
|
||||
this.minZoomLevel = minZoomLevel;
|
||||
this.maxZoomLevel = maxZoomLevel;
|
||||
this.zoomMultiplicationFactor = zoomMultiplicationFactor;
|
||||
}
|
||||
|
||||
public void translate(double dx, double dy) {
|
||||
coordTransform.translate(dx, dy);
|
||||
targetComponent.repaint();
|
||||
}
|
||||
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mousePressed(MouseEvent e) {
|
||||
dragStartScreen = e.getPoint();
|
||||
dragEndScreen = null;
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mouseExited(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
}
|
||||
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
moveCamera(e);
|
||||
}
|
||||
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
zoomCamera(e);
|
||||
}
|
||||
|
||||
private void moveCamera(MouseEvent e) {
|
||||
try {
|
||||
dragEndScreen = e.getPoint();
|
||||
Point2D.Float dragStart = transformPoint(dragStartScreen);
|
||||
Point2D.Float dragEnd = transformPoint(dragEndScreen);
|
||||
double dx = dragEnd.getX() - dragStart.getX();
|
||||
double dy = dragEnd.getY() - dragStart.getY();
|
||||
coordTransform.translate(dx, dy);
|
||||
dragStartScreen = dragEndScreen;
|
||||
dragEndScreen = null;
|
||||
targetComponent.repaint();
|
||||
}
|
||||
catch (NoninvertibleTransformException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void zoomCamera(MouseWheelEvent e) {
|
||||
try {
|
||||
int wheelRotation = e.getWheelRotation();
|
||||
Point p = e.getPoint();
|
||||
if (wheelRotation < 0) {
|
||||
if (zoomLevel < maxZoomLevel) {
|
||||
zoomLevel++;
|
||||
Point2D p1 = transformPoint(p);
|
||||
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
|
||||
Point2D p2 = transformPoint(p);
|
||||
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
|
||||
targetComponent.repaint();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (zoomLevel > minZoomLevel) {
|
||||
zoomLevel--;
|
||||
Point2D p1 = transformPoint(p);
|
||||
coordTransform.scale(1 / zoomMultiplicationFactor,
|
||||
1 / zoomMultiplicationFactor);
|
||||
Point2D p2 = transformPoint(p);
|
||||
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
|
||||
targetComponent.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoninvertibleTransformException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
|
||||
|
||||
AffineTransform inverse = coordTransform.createInverse();
|
||||
|
||||
Point2D.Float p2 = new Point2D.Float();
|
||||
inverse.transform(p1, p2);
|
||||
return p2;
|
||||
}
|
||||
|
||||
public int getZoomLevel() {
|
||||
return zoomLevel;
|
||||
}
|
||||
|
||||
public void setZoomLevel(int zoomLevel) {
|
||||
this.zoomLevel = zoomLevel;
|
||||
}
|
||||
|
||||
public void zoomIn() {
|
||||
try {
|
||||
Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
|
||||
zoomLevel++;
|
||||
Point2D p1 = transformPoint(p);
|
||||
coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
|
||||
Point2D p2 = transformPoint(p);
|
||||
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
|
||||
targetComponent.repaint();
|
||||
}
|
||||
catch (NoninvertibleTransformException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void zoomOut() {
|
||||
try {
|
||||
Point p = new Point(targetComponent.getWidth() / 2, targetComponent.getHeight() / 2);
|
||||
zoomLevel--;
|
||||
Point2D p1 = transformPoint(p);
|
||||
coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
|
||||
Point2D p2 = transformPoint(p);
|
||||
coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
|
||||
targetComponent.repaint();
|
||||
}
|
||||
catch (NoninvertibleTransformException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public AffineTransform getCoordTransform() {
|
||||
return coordTransform;
|
||||
}
|
||||
|
||||
public void setCoordTransform(AffineTransform coordTransform) {
|
||||
this.coordTransform = coordTransform;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import org.mapsforge.core.graphics.Canvas;
|
||||
import org.mapsforge.core.model.BoundingBox;
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
import org.mapsforge.core.model.Point;
|
||||
import org.mapsforge.map.awt.graphics.AwtBitmap;
|
||||
import org.mapsforge.map.layer.overlay.Marker;
|
||||
|
||||
/**
|
||||
* Class extending the default Mapsforge's {@link Marker} with auto-scaling.
|
||||
*
|
||||
* Mapsforge's Markers do not scale with zoom level, this class aims at
|
||||
* correcting this. Internally, this image stores an {@link Image} instance and
|
||||
* scale it when a redraw is requested.
|
||||
*
|
||||
* @see MarkerUtils#getMarkerForColor(java.awt.Color, java.awt.Color,
|
||||
* org.insa.graphics.drawing.Drawing.AlphaMode)
|
||||
* @see PaintUtils#getStrokeWidth(int, byte)
|
||||
*/
|
||||
public class MarkerAutoScaling extends Marker {
|
||||
|
||||
// Original image.
|
||||
private Image image;
|
||||
|
||||
/**
|
||||
* Create a new MarkerAutoScaling at the given position with the given image.
|
||||
*
|
||||
* @param latLong Initial position of the marker.
|
||||
* @param image Image for this marker.
|
||||
*/
|
||||
public MarkerAutoScaling(LatLong latLong, Image image) {
|
||||
super(latLong, null, 0, 0);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new image for this marker overlay
|
||||
*
|
||||
* @param image New image to set.
|
||||
*/
|
||||
public void setImage(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current image (marker) of this overlay.
|
||||
*/
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
|
||||
Point topLeftPoint) {
|
||||
int width = (int) PaintUtils.getStrokeWidth(8, zoomLevel),
|
||||
height = (int) PaintUtils.getStrokeWidth(16, zoomLevel);
|
||||
BufferedImage bfd = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D g = bfd.createGraphics();
|
||||
g.drawImage(
|
||||
this.image.getScaledInstance(bfd.getWidth(), bfd.getHeight(), Image.SCALE_SMOOTH),
|
||||
0, 0, null);
|
||||
setBitmap(new AwtBitmap(bfd));
|
||||
|
||||
setVerticalOffset(-height / 2);
|
||||
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
|
||||
g.dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public interface MarkerOverlay extends Overlay {
|
||||
|
||||
/**
|
||||
* @return The current position of this marker.
|
||||
*/
|
||||
public Point getPoint();
|
||||
|
||||
/**
|
||||
* Move this marker to the specified location.
|
||||
*
|
||||
* @param point New position for the marker.
|
||||
*/
|
||||
public void moveTo(Point point);
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.DataInputStream;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
|
||||
|
||||
public class MarkerUtils {
|
||||
|
||||
/**
|
||||
* Create an image to represent a marker using the given color for the outer and
|
||||
* inner part, and the given mode for the inner part.
|
||||
*
|
||||
* @param outer Outer color of the marker.
|
||||
* @param inner Inner color of the marker.
|
||||
* @param mode Mode to use to fill the inner part of the marker.
|
||||
*
|
||||
* @return An image representing a marker.
|
||||
*/
|
||||
public static Image getMarkerForColor(Color outer, Color inner, AlphaMode mode) {
|
||||
// create image
|
||||
int[][] mask = readMarkerMask();
|
||||
BufferedImage image = new BufferedImage(mask[0].length, mask.length,
|
||||
BufferedImage.TYPE_4BYTE_ABGR);
|
||||
|
||||
// Color[] map = getColorMapping(color);
|
||||
int outerRGB = outer.getRGB() & 0x00ffffff;
|
||||
for (int i = 0; i < image.getHeight(); ++i) {
|
||||
for (int j = 0; j < image.getWidth(); ++j) {
|
||||
|
||||
// If we are in the "inner" part of the marker...
|
||||
if (i >= MIN_Y_CENTER && i < MAX_Y_CENTER && j >= MIN_X_CENTER && j < MAX_X_CENTER
|
||||
&& mask[i][j] != MAXIMUM_INNER_MASK_VALUE) {
|
||||
// Don't ask... https://stackoverflow.com/a/29321264/2666289
|
||||
// Basically, this compute a "middle" color between outer and inner depending on
|
||||
// the current mask value.
|
||||
double t = 1 - (mask[i][j] - MINIMUM_INNER_MASK_VALUE)
|
||||
/ (double) (MAXIMUM_INNER_MASK_VALUE - MINIMUM_INNER_MASK_VALUE);
|
||||
int r = (int) Math.sqrt((1 - t) * outer.getRed() * outer.getRed()
|
||||
+ t * inner.getRed() * inner.getRed());
|
||||
int g = (int) Math.sqrt((1 - t) * outer.getGreen() * outer.getGreen()
|
||||
+ t * inner.getGreen() * inner.getGreen());
|
||||
int b = (int) Math.sqrt((1 - t) * outer.getBlue() * outer.getBlue()
|
||||
+ t * inner.getBlue() * inner.getBlue());
|
||||
int a = mode == AlphaMode.OPAQUE ? MAXIMUM_INNER_MASK_VALUE : mask[i][j];
|
||||
image.setRGB(j, i, (a << 24) | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
// Otherwize, just fill with the outer color and set the alpha value properly.
|
||||
else {
|
||||
image.setRGB(j, i, outerRGB | (mask[i][j] << 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// Mask cache
|
||||
private static int[][] MASK_CACHE = null;
|
||||
|
||||
// Hand-made... These defines the "center" of the marker, that can be filled
|
||||
// with a different color.
|
||||
private static final int MIN_X_CENTER = 40, MAX_X_CENTER = 101, MIN_Y_CENTER = 40,
|
||||
MAX_Y_CENTER = 100;
|
||||
private static final int MINIMUM_INNER_MASK_VALUE = 116, MAXIMUM_INNER_MASK_VALUE = 249;
|
||||
|
||||
/**
|
||||
* @return Retrieve the mask from the mask file or from the cache.
|
||||
*/
|
||||
private static int[][] readMarkerMask() {
|
||||
if (MASK_CACHE == null) {
|
||||
try {
|
||||
DataInputStream dis = new DataInputStream(
|
||||
MarkerUtils.class.getResourceAsStream("/marker_mask.bin"));
|
||||
|
||||
int nrows = dis.readInt();
|
||||
int ncols = dis.readInt();
|
||||
|
||||
MASK_CACHE = new int[nrows][ncols];
|
||||
for (int i = 0; i < nrows; ++i) {
|
||||
for (int j = 0; j < ncols; ++j) {
|
||||
MASK_CACHE[i][j] = dis.readUnsignedByte();
|
||||
}
|
||||
}
|
||||
dis.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
MASK_CACHE = null;
|
||||
}
|
||||
}
|
||||
return MASK_CACHE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public interface Overlay {
|
||||
|
||||
/**
|
||||
* Set the color of this overlay.
|
||||
*
|
||||
* @param color New color for the overlay.
|
||||
*/
|
||||
public void setColor(Color color);
|
||||
|
||||
/**
|
||||
* @return The current color of this overlay.
|
||||
*/
|
||||
public Color getColor();
|
||||
|
||||
/**
|
||||
* Show or hide this marker - A marker should be visible when created.
|
||||
*
|
||||
* @param visible true to show the marker, false to hide.
|
||||
*/
|
||||
public void setVisible(boolean visible);
|
||||
|
||||
/**
|
||||
* @return true if this overlay is visible.
|
||||
*/
|
||||
public boolean isVisible();
|
||||
|
||||
/**
|
||||
* Delete this marker.
|
||||
*/
|
||||
public void delete();
|
||||
|
||||
/**
|
||||
* Request a redraw of this overlay - This can start a full redraw of the inner
|
||||
* drawing.
|
||||
*/
|
||||
public void redraw();
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.mapsforge.core.graphics.GraphicFactory;
|
||||
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
|
||||
import org.mapsforge.map.layer.overlay.Marker;
|
||||
import org.mapsforge.map.layer.overlay.Polyline;
|
||||
|
||||
public class PaintUtils {
|
||||
|
||||
// Graphic factory.
|
||||
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
|
||||
|
||||
/**
|
||||
* Convert the given AWT color to a mapsforge compatible color.
|
||||
*
|
||||
* @param color AWT color to convert.
|
||||
*
|
||||
* @return Integer value representing a corresponding mapsforge color.
|
||||
*/
|
||||
public static int convertColor(Color color) {
|
||||
return GRAPHIC_FACTORY.createColor(color.getAlpha(), color.getRed(), color.getGreen(),
|
||||
color.getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given mapsforge color to an AWT Color.
|
||||
*
|
||||
* @param color Integer value representing a mapsforge color.
|
||||
*
|
||||
* @return AWT color corresponding to the given value.
|
||||
*/
|
||||
public static Color convertColor(int color) {
|
||||
return new Color(color, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute an updated value for the given width at the given zoom level. This
|
||||
* function can be used to automatically scale {@link Polyline} or
|
||||
* {@link Marker} when zooming (which is not done by default in Mapsforge).
|
||||
*
|
||||
* @param width Original width to convert.
|
||||
* @param zoomLevel Zoom level for which the width should be computed.
|
||||
*
|
||||
* @return Actual width at the given zoom level.
|
||||
*/
|
||||
public static float getStrokeWidth(int width, byte zoomLevel) {
|
||||
float mul = 1;
|
||||
if (zoomLevel < 6) {
|
||||
mul = 1;
|
||||
}
|
||||
else if (zoomLevel < 10) {
|
||||
mul += (zoomLevel - 5) * 0.5;
|
||||
}
|
||||
else if (zoomLevel < 13) {
|
||||
mul = 3.5f;
|
||||
}
|
||||
else {
|
||||
mul += 2 * (zoomLevel - 8) / 3;
|
||||
}
|
||||
return width * mul;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
public interface PathOverlay extends Overlay {
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public interface PointSetOverlay extends Overlay {
|
||||
|
||||
/**
|
||||
* Set the width of this overlay for future addPoint().
|
||||
*
|
||||
* @param width New default width for this overlay.
|
||||
*/
|
||||
public void setWidth(int width);
|
||||
|
||||
/**
|
||||
* Set color and width for this overlay for future addPoint().
|
||||
*
|
||||
* @param width New default width for this overlay.
|
||||
* @param color New default color for this overlay.
|
||||
*/
|
||||
public void setWidthAndColor(int width, Color color);
|
||||
|
||||
/**
|
||||
* Add a new point using the current width and color.
|
||||
*
|
||||
* @param point Position of the point to add.
|
||||
*
|
||||
* @see #setWidth(int)
|
||||
* @see #setColor(Color)
|
||||
*/
|
||||
public void addPoint(Point point);
|
||||
|
||||
/**
|
||||
* Set the current width and then add a new point.
|
||||
*
|
||||
* @param point Position of the point to add.
|
||||
* @param width New default width for this overlay.
|
||||
*
|
||||
* @see #setWidth(int)
|
||||
* @see PointSetOverlay#addPoint(Point)
|
||||
*/
|
||||
public void addPoint(Point point, int width);
|
||||
|
||||
/**
|
||||
* Set the current color and then add a new point.
|
||||
*
|
||||
* @param point Position of the point to add.
|
||||
* @param color New default color for this overlay.
|
||||
*
|
||||
* @see #setColor(Color)
|
||||
* @see PointSetOverlay#addPoint(Point)
|
||||
*/
|
||||
public void addPoint(Point point, Color color);
|
||||
|
||||
/**
|
||||
* Add a new point at the given location, with the given color and width, and
|
||||
* update the current width and color.
|
||||
*
|
||||
* @param point Position of the point to add.
|
||||
* @param width New default width for this overlay.
|
||||
* @param color New default color for this overlay.
|
||||
*
|
||||
* @see #setWidth(int)
|
||||
* @see #setColor(Color)
|
||||
* @see PointSetOverlay#addPoint(Point)
|
||||
*/
|
||||
public void addPoint(Point point, int width, Color color);
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package org.insa.graphs.gui.drawing.overlays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.insa.graphs.model.Point;
|
||||
import org.mapsforge.core.graphics.Canvas;
|
||||
import org.mapsforge.core.graphics.GraphicFactory;
|
||||
import org.mapsforge.core.graphics.Style;
|
||||
import org.mapsforge.core.model.BoundingBox;
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
import org.mapsforge.map.awt.graphics.AwtGraphicFactory;
|
||||
import org.mapsforge.map.layer.overlay.Polyline;
|
||||
|
||||
/**
|
||||
* Class extending the default Mapsforge's {@link Polyline} with auto-scaling.
|
||||
*
|
||||
* Mapsforge's Polylines do not scale with zoom level, this class aims at
|
||||
* correcting this. When a redraw is requested, the width of the line is
|
||||
* recomputed for the current zoom level.
|
||||
*
|
||||
* @see PaintUtils#getStrokeWidth(int, byte)
|
||||
*/
|
||||
public class PolylineAutoScaling extends Polyline {
|
||||
|
||||
// Graphic factory.
|
||||
private static final GraphicFactory GRAPHIC_FACTORY = AwtGraphicFactory.INSTANCE;
|
||||
|
||||
// Original width of the polyline.
|
||||
private final int width;
|
||||
|
||||
/**
|
||||
* Create a new PolylineAutoScaling with the given width and color.
|
||||
*
|
||||
* @param width Original width of the line (independent of the zoom level).
|
||||
* @param color Color of the line.
|
||||
*
|
||||
* @see PaintUtils#getStrokeWidth(int, byte)
|
||||
*/
|
||||
public PolylineAutoScaling(int width, Color color) {
|
||||
super(GRAPHIC_FACTORY.createPaint(), GRAPHIC_FACTORY);
|
||||
getPaintStroke().setColor(PaintUtils.convertColor(color));
|
||||
getPaintStroke().setStyle(Style.STROKE);
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color for this polyline.
|
||||
*
|
||||
* @param color New color for this polyline.
|
||||
*/
|
||||
public void setColor(Color color) {
|
||||
getPaintStroke().setColor(PaintUtils.convertColor(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Color of this polyline.
|
||||
*/
|
||||
public Color getColor() {
|
||||
return PaintUtils.convertColor(getPaintStroke().getColor());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param point Point to add to this line.
|
||||
*/
|
||||
public void add(Point point) {
|
||||
getLatLongs().add(new LatLong(point.getLatitude(), point.getLongitude()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param points Points to add to this line.
|
||||
*/
|
||||
public void addAll(Collection<? extends Point> points) {
|
||||
ArrayList<LatLong> latlongs = new ArrayList<>(points.size());
|
||||
for (Point point: points) {
|
||||
latlongs.add(new LatLong(point.getLatitude(), point.getLongitude()));
|
||||
}
|
||||
getLatLongs().addAll(latlongs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas,
|
||||
org.mapsforge.core.model.Point topLeftPoint) {
|
||||
|
||||
// Update paint stroke with width for level
|
||||
this.getPaintStroke().setStrokeWidth(PaintUtils.getStrokeWidth(width, zoomLevel));
|
||||
|
||||
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.insa.graphs.gui.observers;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathObserver;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class ShortestPathGraphicObserver implements ShortestPathObserver {
|
||||
|
||||
// Drawing and Graph drawing
|
||||
protected Drawing drawing;
|
||||
protected PointSetOverlay psOverlay1, psOverlay2;
|
||||
|
||||
public ShortestPathGraphicObserver(Drawing drawing) {
|
||||
this.drawing = drawing;
|
||||
psOverlay1 = drawing.createPointSetOverlay(1, Color.CYAN);
|
||||
psOverlay2 = drawing.createPointSetOverlay(1, Color.BLUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyOriginProcessed(Node node) {
|
||||
// drawing.drawMarker(node.getPoint(), Color.RED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeReached(Node node) {
|
||||
psOverlay1.addPoint(node.getPoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeMarked(Node node) {
|
||||
psOverlay2.addPoint(node.getPoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDestinationReached(Node node) {
|
||||
// drawing.drawMarker(node.getPoint(), Color.RED);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.insa.graphs.gui.observers;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentObserver;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.overlays.PointSetOverlay;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentGraphicObserver implements WeaklyConnectedComponentObserver {
|
||||
|
||||
private static final Color[] COLORS = { Color.BLUE, Color.ORANGE, Color.GREEN, Color.YELLOW,
|
||||
Color.RED };
|
||||
|
||||
// Drawing + Graph drawing
|
||||
private PointSetOverlay grPoints;
|
||||
|
||||
// Current index color
|
||||
private int cindex = 0;
|
||||
|
||||
public WeaklyConnectedComponentGraphicObserver(Drawing drawing) {
|
||||
this.grPoints = drawing.createPointSetOverlay();
|
||||
this.grPoints.setWidth(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartComponent(Node curNode) {
|
||||
this.grPoints.setColor(COLORS[cindex]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewNodeInComponent(Node node) {
|
||||
this.grPoints.addPoint(node.getPoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEndComponent(ArrayList<Node> nodes) {
|
||||
cindex = (cindex + 1) % COLORS.length;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package org.insa.graphs.gui.simple;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.components.BasicDrawing;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.io.BinaryGraphReader;
|
||||
import org.insa.graphs.model.io.GraphReader;
|
||||
import org.insa.graphs.model.io.PathReader;
|
||||
|
||||
public class Launch {
|
||||
|
||||
/**
|
||||
* Create a new Drawing inside a JFrame an return it.
|
||||
*
|
||||
* @return The created drawing.
|
||||
*
|
||||
* @throws Exception if something wrong happens when creating the graph.
|
||||
*/
|
||||
public static Drawing createDrawing() throws Exception {
|
||||
BasicDrawing basicDrawing = new BasicDrawing();
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JFrame frame = new JFrame("BE Graphes - Launch");
|
||||
frame.setLayout(new BorderLayout());
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
frame.setVisible(true);
|
||||
frame.setSize(new Dimension(800, 600));
|
||||
frame.setContentPane(basicDrawing);
|
||||
frame.validate();
|
||||
}
|
||||
});
|
||||
return basicDrawing;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
// Visit these directory to see the list of available files on Commetud.
|
||||
final String mapName = "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/insa.mapgr";
|
||||
final String pathName = "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Paths/path_fr31insa_rangueil_r2.path";
|
||||
|
||||
// Create a graph reader.
|
||||
final GraphReader reader = new BinaryGraphReader(
|
||||
new DataInputStream(new BufferedInputStream(new FileInputStream(mapName))));
|
||||
|
||||
// TODO: Read the graph.
|
||||
final Graph graph = null;
|
||||
|
||||
// Create the drawing:
|
||||
final Drawing drawing = createDrawing();
|
||||
|
||||
// TODO: Draw the graph on the drawing.
|
||||
|
||||
// TODO: Create a PathReader.
|
||||
final PathReader pathReader = null;
|
||||
|
||||
// TODO: Read the path.
|
||||
final Path path = null;
|
||||
|
||||
// TODO: Draw the path.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.insa.graphs.gui.utils;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class ColorUtils {
|
||||
|
||||
private static final Color[] COLORS = { // List of available colors
|
||||
new Color(57, 172, 115), // Forest (Green)
|
||||
new Color(246, 67, 63), // Red
|
||||
new Color(110, 56, 172), // Purple
|
||||
new Color(53, 191, 179), // Cyan
|
||||
new Color(219, 136, 48), // Orange / Brown
|
||||
new Color(110, 110, 110), // Gray
|
||||
new Color(56, 104, 172) // Blue
|
||||
};
|
||||
|
||||
public static Color getColor(int i) {
|
||||
return COLORS[i % COLORS.length];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package org.insa.graphs.gui.utils;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
// Preferences
|
||||
private static Preferences preferences = Preferences.userRoot().node(FileUtils.class.getName());
|
||||
|
||||
/**
|
||||
* Type of folder with associated preferred folder and path filters.
|
||||
*
|
||||
*/
|
||||
public enum FolderType {
|
||||
|
||||
/**
|
||||
* Folder type for graph files input (*.mapgr).
|
||||
*/
|
||||
Map,
|
||||
|
||||
/**
|
||||
* Folder type for path inputs (*.path).
|
||||
*/
|
||||
PathInput,
|
||||
|
||||
/**
|
||||
* Folder type for path outputs (*.path).
|
||||
*/
|
||||
PathOutput
|
||||
}
|
||||
|
||||
private static class PreferencesEntry {
|
||||
public String key;
|
||||
public String value;
|
||||
|
||||
public PreferencesEntry(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Map folder type -> PreferencesEntry
|
||||
private static final Map<FolderType, PreferencesEntry> folderToEntry = new EnumMap<>(
|
||||
FolderType.class);
|
||||
|
||||
// Map folder type -> File Filter
|
||||
private static final Map<FolderType, FileFilter> folderToFilter = new EnumMap<>(
|
||||
FolderType.class);
|
||||
|
||||
static {
|
||||
// Populate folderToEntry
|
||||
folderToEntry.put(FolderType.Map, new PreferencesEntry("DefaultMapFolder",
|
||||
"/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps"));
|
||||
folderToEntry.put(FolderType.PathInput, new PreferencesEntry("DefaultPathInputFolder",
|
||||
"/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Paths"));
|
||||
folderToEntry.put(FolderType.PathOutput,
|
||||
new PreferencesEntry("DefaultPathOutputsFolder", "paths"));
|
||||
|
||||
// Populate folderToFilter
|
||||
folderToFilter.put(FolderType.Map, new FileNameExtensionFilter("Graph files", "mapgr"));
|
||||
folderToFilter.put(FolderType.PathInput, new FileNameExtensionFilter("Path files", "path"));
|
||||
folderToFilter.put(FolderType.PathOutput,
|
||||
new FileNameExtensionFilter("Path files", "path"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param folderType Type of folder to retrieve.
|
||||
*
|
||||
* @return A File instance pointing to the preferred folder for the given type.
|
||||
*
|
||||
* @see FolderType
|
||||
*/
|
||||
public static File getPreferredFolder(FolderType folderType) {
|
||||
PreferencesEntry entry = folderToEntry.get(folderType);
|
||||
File folder = new File(preferences.get(entry.key, entry.value));
|
||||
if (!folder.exists()) {
|
||||
folder = new File(System.getProperty("user.dir"));
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param folderType Type of folder to update.
|
||||
* @param newPreferredFolder New preferred folder.
|
||||
*/
|
||||
public static void updatePreferredFolder(FolderType folderType, File newPreferredFolder) {
|
||||
PreferencesEntry entry = folderToEntry.get(folderType);
|
||||
preferences.put(entry.key, newPreferredFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param folderType Type of folder for which the filter should be retrieved.
|
||||
*
|
||||
* @return A FileFilter corresponding to input graph files.
|
||||
*/
|
||||
public static FileFilter getFileFilter(FolderType folderType) {
|
||||
return folderToFilter.get(folderType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param folderType Type of folder for which a file chooser should be created.
|
||||
* @param defaultFileName Default file name to show, or null to not show any
|
||||
* file.
|
||||
*
|
||||
* @return A new JFileChooser pointing to the preferred folder for the given
|
||||
* folderType, with the given default file selected (if given).
|
||||
*/
|
||||
public static JFileChooser createFileChooser(FolderType folderType, String defaultFileName) {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setCurrentDirectory(getPreferredFolder(folderType));
|
||||
if (defaultFileName != null) {
|
||||
chooser.setSelectedFile(new File(chooser.getCurrentDirectory().getAbsolutePath()
|
||||
+ File.separator + defaultFileName));
|
||||
}
|
||||
chooser.setFileFilter(getFileFilter(folderType));
|
||||
chooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (e.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {
|
||||
if (chooser.getSelectedFile().exists()) {
|
||||
updatePreferredFolder(folderType,
|
||||
chooser.getSelectedFile().getParentFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return chooser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param folderType Type of folder for which a file chooser should be created.
|
||||
*
|
||||
* @return A new JFileChooser pointing to the preferred folder for the given
|
||||
* folderType.
|
||||
*
|
||||
* @see #createFileChooser(FolderType, String)
|
||||
*/
|
||||
public static JFileChooser createFileChooser(FolderType folderType) {
|
||||
return createFileChooser(folderType, null);
|
||||
}
|
||||
|
||||
}
|
BIN
be-graphes-gui/src/main/resources/delete-icon.png
Normal file
BIN
be-graphes-gui/src/main/resources/delete-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
be-graphes-gui/src/main/resources/marker_mask.bin
Normal file
BIN
be-graphes-gui/src/main/resources/marker_mask.bin
Normal file
Binary file not shown.
BIN
be-graphes-gui/src/main/resources/save-icon.png
Normal file
BIN
be-graphes-gui/src/main/resources/save-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
BIN
be-graphes-gui/src/main/resources/zoomIn.png
Normal file
BIN
be-graphes-gui/src/main/resources/zoomIn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
be-graphes-gui/src/main/resources/zoomOut.png
Normal file
BIN
be-graphes-gui/src/main/resources/zoomOut.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
17
be-graphes-model/pom.xml
Normal file
17
be-graphes-model/pom.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-all</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>be-graphes-model</artifactId>
|
||||
<name>be-graphes-model</name>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,236 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class containing access restrictions for roads/arcs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class maps transport modes to their restriction and provide interface
|
||||
* based on EnumSet to query restrictions.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To each transport is associated at most one restriction per road (no
|
||||
* restriction corresponds to {@link AccessRestriction#UNKNOWN} but a road can
|
||||
* have different restrictions for different modes.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class AccessRestrictions {
|
||||
|
||||
/**
|
||||
* Enumeration representing the available transport modes.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
|
||||
* reference for access modes.</a>
|
||||
*/
|
||||
public enum AccessMode {
|
||||
|
||||
/**
|
||||
* Access mode corresponding to pedestrians.
|
||||
*/
|
||||
FOOT,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to bicycles (non-motorized).
|
||||
*/
|
||||
BICYCLE,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to small motorcycles (limited speed).
|
||||
*/
|
||||
SMALL_MOTORCYCLE,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to agricultural vehicles.
|
||||
*/
|
||||
AGRICULTURAL,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to motorcycles.
|
||||
*/
|
||||
MOTORCYCLE,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to motorcars.
|
||||
*/
|
||||
MOTORCAR,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to heavy transportation vehicles.
|
||||
*/
|
||||
HEAVY_GOODS,
|
||||
|
||||
/**
|
||||
* Access mode corresponding to public transport vehicles.
|
||||
*/
|
||||
PUBLIC_TRANSPORT;
|
||||
|
||||
/**
|
||||
* {@code EnumSet} containing all possible transport modes.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public static final EnumSet<AccessMode> ALL = EnumSet.allOf(AccessMode.class);
|
||||
|
||||
/**
|
||||
* {@code EnumSet} containing all vehicle transport modes.
|
||||
*
|
||||
*/
|
||||
public static final EnumSet<AccessMode> VEHICLE = EnumSet.range(AccessMode.BICYCLE,
|
||||
AccessMode.PUBLIC_TRANSPORT);
|
||||
|
||||
/**
|
||||
* {@code EnumSet} containing all motorized vehicle transport modes.
|
||||
*
|
||||
*/
|
||||
public static final EnumSet<AccessMode> MOTOR_VEHICLE = EnumSet
|
||||
.range(AccessMode.SMALL_MOTORCYCLE, AccessMode.PUBLIC_TRANSPORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible restrictions for the roads/arcs.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">OpenStreetMap
|
||||
* reference for access restrictions.</a>
|
||||
*/
|
||||
public enum AccessRestriction {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ALLOWED,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
FORBIDDEN,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
PRIVATE,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
DESTINATION,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
DELIVERY,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
CUSTOMERS,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
FORESTRY,
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* {@code EnumSet} corresponding to restrictions that are not totally private.
|
||||
*
|
||||
*/
|
||||
public static final EnumSet<AccessRestriction> ALLOWED_FOR_SOMETHING = EnumSet.of(
|
||||
AccessRestriction.ALLOWED, AccessRestriction.DESTINATION,
|
||||
AccessRestriction.DESTINATION, AccessRestriction.DELIVERY,
|
||||
AccessRestriction.CUSTOMERS, AccessRestriction.FORESTRY);
|
||||
|
||||
}
|
||||
|
||||
// Map mode -> restriction
|
||||
private final EnumMap<AccessMode, AccessRestriction> restrictions;
|
||||
|
||||
/**
|
||||
* Create new AccessRestrictions instances with unknown restrictions.
|
||||
*/
|
||||
public AccessRestrictions() {
|
||||
this.restrictions = new EnumMap<>(AccessMode.class);
|
||||
for (AccessMode mode: AccessMode.values()) {
|
||||
this.restrictions.put(mode, AccessRestriction.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new AccessRestrictions instances with the given restrictions.
|
||||
*
|
||||
* @param restrictions Map of restrictions for this instance of
|
||||
* AccessRestrictions.
|
||||
*/
|
||||
public AccessRestrictions(EnumMap<AccessMode, AccessRestriction> restrictions) {
|
||||
this.restrictions = restrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the restriction corresponding to the given mode.
|
||||
*
|
||||
* @param mode Mode for which the restriction should be retrieved.
|
||||
*
|
||||
* @return Restriction for the given mode.
|
||||
*/
|
||||
public AccessRestriction getRestrictionFor(AccessMode mode) {
|
||||
return restrictions.getOrDefault(mode, AccessRestriction.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the restriction associated with the given mode is one of the given
|
||||
* restrictions.
|
||||
*
|
||||
* @param mode Mode for which to check the restrictions.
|
||||
* @param restrictions List of queried restrictions for the mode.
|
||||
*
|
||||
* @return {@code true} if the restriction of the given mode is one of the given
|
||||
* restrictions.
|
||||
*/
|
||||
public boolean isAllowedForAny(AccessMode mode, EnumSet<AccessRestriction> restrictions) {
|
||||
return restrictions.contains(getRestrictionFor(mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the restriction for the given mode corresponds to the given
|
||||
* restriction.
|
||||
*
|
||||
* @param mode Mode for which the restriction should be checked.
|
||||
* @param restriction Restriction to check against.
|
||||
*
|
||||
* @return {@code true} if the restriction of the given mode corresponds to the
|
||||
* given restriction.
|
||||
*/
|
||||
public boolean isAllowedFor(AccessMode mode, AccessRestriction restriction) {
|
||||
return getRestrictionFor(mode).equals(restriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the restriction associated to each given mode is one of the
|
||||
* restrictions. The restriction may not be the same for all modes.
|
||||
*
|
||||
* @param modes Modes for which restrictions should be checked.
|
||||
* @param restrictions Set of wanted restrictions for the modes.
|
||||
*
|
||||
* @return {@code true} if all the given modes are allowed for any of the given
|
||||
* restrictions.
|
||||
*/
|
||||
public boolean areAllAllowedForAny(EnumSet<AccessMode> modes,
|
||||
EnumSet<AccessRestriction> restrictions) {
|
||||
boolean allowed = true;
|
||||
for (AccessMode mode: modes) {
|
||||
allowed = allowed && isAllowedForAny(mode, restrictions);
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Interface representing an arc in the graph. {@code Arc} is an interface and
|
||||
* not a class to allow us to represent two-ways roads in a memory efficient
|
||||
* manner (without having to duplicate attributes).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Arc should never be created manually but always using the
|
||||
* {@link Node#linkNodes(Node, Node, float, RoadInformation, java.util.ArrayList)}
|
||||
* method to ensure proper instantiation of the {@link ArcForward} and
|
||||
* {@link ArcBackward} classes.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public abstract class Arc {
|
||||
|
||||
/**
|
||||
* @return Origin node of this arc.
|
||||
*/
|
||||
public abstract Node getOrigin();
|
||||
|
||||
/**
|
||||
* @return Destination node of this arc.
|
||||
*/
|
||||
public abstract Node getDestination();
|
||||
|
||||
/**
|
||||
* @return Length of this arc, in meters.
|
||||
*/
|
||||
public abstract float getLength();
|
||||
|
||||
/**
|
||||
* Compute the time required to travel this arc if moving at the given speed.
|
||||
*
|
||||
* @param speed Speed to compute the travel time.
|
||||
*
|
||||
* @return Time (in seconds) required to travel this arc at the given speed (in
|
||||
* kilometers-per-hour).
|
||||
*/
|
||||
public double getTravelTime(double speed) {
|
||||
return getLength() * 3600.0 / (speed * 1000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return the minimum time required to travel this arc, or the time
|
||||
* required to travel this arc at the maximum speed allowed.
|
||||
*
|
||||
* @return Minimum time required to travel this arc, in seconds.
|
||||
*
|
||||
* @see Arc#getTravelTime(double)
|
||||
*/
|
||||
public double getMinimumTravelTime() {
|
||||
return getTravelTime(getRoadInformation().getMaximumSpeed());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Road information for this arc.
|
||||
*/
|
||||
public abstract RoadInformation getRoadInformation();
|
||||
|
||||
/**
|
||||
* @return Points representing segments of this arc.
|
||||
*/
|
||||
public abstract List<Point> getPoints();
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of Arc that represents a "backward" arc in a graph, i.e., an
|
||||
* arc that is the reverse of another one. This arc only holds a reference to
|
||||
* the original arc.
|
||||
*
|
||||
*/
|
||||
class ArcBackward extends Arc {
|
||||
|
||||
// Original arc
|
||||
private final Arc originalArc;
|
||||
|
||||
/**
|
||||
* Create a new backward arc which corresponds to the reverse arc of the given
|
||||
* arc.
|
||||
*
|
||||
* @param originalArc Original forwarc arc corresponding to this backward arc.
|
||||
*/
|
||||
protected ArcBackward(Arc originalArc) {
|
||||
this.originalArc = originalArc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getOrigin() {
|
||||
return this.originalArc.getDestination();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getDestination() {
|
||||
return this.originalArc.getOrigin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getLength() {
|
||||
return this.originalArc.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoadInformation getRoadInformation() {
|
||||
return this.originalArc.getRoadInformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point> getPoints() {
|
||||
List<Point> pts = new ArrayList<>(this.originalArc.getPoints());
|
||||
Collections.reverse(pts);
|
||||
return pts;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of Arc that represents a "forward" arc in a graph, this is the
|
||||
* arc implementation that stores data relative to the arc.
|
||||
*
|
||||
*/
|
||||
class ArcForward extends Arc {
|
||||
|
||||
// Destination node.
|
||||
private final Node origin, destination;
|
||||
|
||||
// Length of the road (in meters).
|
||||
private final float length;
|
||||
|
||||
// Road information.
|
||||
private final RoadInformation info;
|
||||
|
||||
// Segments.
|
||||
private final List<Point> points;
|
||||
|
||||
/**
|
||||
* Create a new ArcForward with the given attributes.
|
||||
*
|
||||
* @param origin Origin of this arc.
|
||||
* @param dest Destination of this arc.
|
||||
* @param length Length of this arc (in meters).
|
||||
* @param roadInformation Road information for this arc.
|
||||
* @param points Points representing this arc.
|
||||
*/
|
||||
protected ArcForward(Node origin, Node dest, float length, RoadInformation roadInformation,
|
||||
List<Point> points) {
|
||||
this.origin = origin;
|
||||
this.destination = dest;
|
||||
this.length = length;
|
||||
this.info = roadInformation;
|
||||
this.points = points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoadInformation getRoadInformation() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Point> getPoints() {
|
||||
return Collections.unmodifiableList(points);
|
||||
}
|
||||
|
||||
}
|
131
be-graphes-model/src/main/java/org/insa/graphs/model/Graph.java
Normal file
131
be-graphes-model/src/main/java/org/insa/graphs/model/Graph.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Main graph class.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class acts as a object-oriented <b>adjacency list</b> for a graph, i.e.,
|
||||
* it holds a list of nodes and each node holds a list of its successors.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public final class Graph {
|
||||
|
||||
// Map identifier.
|
||||
private final String mapId;
|
||||
|
||||
// Map name
|
||||
private final String mapName;
|
||||
|
||||
// Nodes of the graph.
|
||||
private final List<Node> nodes;
|
||||
|
||||
// Graph information of this graph.
|
||||
private final GraphStatistics graphStatistics;
|
||||
|
||||
/**
|
||||
* Create a new graph with the given ID, name, nodes and information.
|
||||
*
|
||||
* @param mapId ID of the map corresponding to this graph.
|
||||
* @param mapName Name of the map corresponding to this graph.
|
||||
* @param nodes List of nodes for this graph.
|
||||
* @param graphStatistics Information for this graph.
|
||||
*/
|
||||
public Graph(String mapId, String mapName, List<Node> nodes, GraphStatistics graphStatistics) {
|
||||
this.mapId = mapId;
|
||||
this.mapName = mapName;
|
||||
this.nodes = Collections.unmodifiableList(nodes);
|
||||
this.graphStatistics = graphStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The GraphStatistics instance associated with this graph.
|
||||
*/
|
||||
public GraphStatistics getGraphInformation() {
|
||||
return this.graphStatistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the node with the given ID.
|
||||
*
|
||||
* Complexity: O(1).
|
||||
*
|
||||
* @param id ID of the node to fetch.
|
||||
*
|
||||
* @return Node with the given ID.
|
||||
*/
|
||||
public Node get(int id) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of nodes in this graph.
|
||||
*/
|
||||
public int size() {
|
||||
return this.nodes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of nodes in this graph (unmodifiable).
|
||||
*
|
||||
* @see Collections#unmodifiableList(List)
|
||||
*/
|
||||
public List<Node> getNodes() {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ID of the map associated with this graph.
|
||||
*/
|
||||
public String getMapId() {
|
||||
return mapId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name of the map associated with this graph.
|
||||
*/
|
||||
public String getMapName() {
|
||||
return mapName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Transpose graph of this graph.
|
||||
*/
|
||||
public Graph transpose() {
|
||||
ArrayList<Node> trNodes = new ArrayList<>(nodes.size());
|
||||
for (Node node: nodes) {
|
||||
trNodes.add(new Node(node.getId(), node.getPoint()));
|
||||
}
|
||||
for (Node node: nodes) {
|
||||
Node orig = trNodes.get(node.getId());
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
if (arc.getRoadInformation().isOneWay()) {
|
||||
Node dest = trNodes.get(arc.getDestination().getId());
|
||||
dest.addSuccessor(new ArcBackward(new ArcForward(orig, dest, arc.getLength(),
|
||||
arc.getRoadInformation(), arc.getPoints())));
|
||||
}
|
||||
else if (arc instanceof ArcForward) {
|
||||
Node dest = trNodes.get(arc.getDestination().getId());
|
||||
Arc newArc = new ArcForward(orig, dest, arc.getLength(),
|
||||
arc.getRoadInformation(), arc.getPoints());
|
||||
dest.addSuccessor(new ArcBackward(newArc));
|
||||
orig.addSuccessor(newArc);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Graph("R/" + mapId, mapName, trNodes, graphStatistics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s[id=%s, name=%s, #nodes=%d]", getClass().getCanonicalName(),
|
||||
getMapId(), getMapName(), size());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Utility class that stores some statistics of graphs that are not easy to
|
||||
* access.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class is used to provide constant ({@code O(1)}) access to information
|
||||
* in graph that do not change, and that usually require linear complexity to
|
||||
* compute.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class GraphStatistics {
|
||||
|
||||
/**
|
||||
* Special value used to indicate that the graph has no maximum speed limit
|
||||
* (some roads are not limited).
|
||||
*
|
||||
*/
|
||||
public static final int NO_MAXIMUM_SPEED = -1;
|
||||
|
||||
/**
|
||||
* Class representing a bounding box for a graph (a rectangle that contains all
|
||||
* nodes in the graph).
|
||||
*
|
||||
*/
|
||||
public static class BoundingBox {
|
||||
|
||||
private final Point topLeft, bottomRight;
|
||||
|
||||
/**
|
||||
* Create a new BoundingBox represented by the given top-left and bottom-right
|
||||
* points.
|
||||
*
|
||||
* @param topLeft Top left corner of the bounding box.
|
||||
* @param bottomRight Bottom right corner of the bounding box.
|
||||
*/
|
||||
public BoundingBox(Point topLeft, Point bottomRight) {
|
||||
this.topLeft = topLeft;
|
||||
this.bottomRight = bottomRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Bottom-right point of this bounding box.
|
||||
*/
|
||||
public Point getBottomRightPoint() {
|
||||
return bottomRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Top-left point of this bounding box.
|
||||
*/
|
||||
public Point getTopLeftPoint() {
|
||||
return topLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bounding box by extending the current one according to the given
|
||||
* value for each side.
|
||||
*
|
||||
* @param left Extra size to add to the left of the box.
|
||||
* @param top Extra size to add to the top of the box.
|
||||
* @param right Extra size to add to the right of the box.
|
||||
* @param bottom Extra size to add to the bottom of the box.
|
||||
*
|
||||
* @return New bounding box corresponding to an extension of the current one.
|
||||
*/
|
||||
public BoundingBox extend(float left, float top, float right, float bottom) {
|
||||
return new BoundingBox(
|
||||
new Point(this.topLeft.getLongitude() - left, this.topLeft.getLatitude() + top),
|
||||
new Point(this.bottomRight.getLongitude() + right,
|
||||
this.bottomRight.getLatitude() - bottom));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new bounding box by extending the current one according by the given
|
||||
* value on each side.
|
||||
*
|
||||
* @param size Extra size to add to each side of this box.
|
||||
*
|
||||
* @return New bounding box corresponding to an extension of the current one.
|
||||
*/
|
||||
public BoundingBox extend(float size) {
|
||||
return this.extend(size, size, size, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param point Point to check
|
||||
*
|
||||
* @return true if this box contains the given point.
|
||||
*/
|
||||
public boolean contains(Point point) {
|
||||
return this.bottomRight.getLatitude() <= point.getLatitude()
|
||||
&& this.topLeft.getLatitude() >= point.getLatitude()
|
||||
&& this.topLeft.getLongitude() <= point.getLongitude()
|
||||
&& this.bottomRight.getLongitude() >= point.getLongitude();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param other Box to intersect.
|
||||
*
|
||||
* @return true if this box contains the given box.
|
||||
*/
|
||||
public boolean contains(BoundingBox other) {
|
||||
return this.contains(other.bottomRight) && this.contains(other.topLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BoundingBox(topLeft=" + this.topLeft + ", bottomRight=" + this.bottomRight
|
||||
+ ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Bounding box for this graph.
|
||||
private final BoundingBox boundingBox;
|
||||
|
||||
// Number of roads
|
||||
private final int nbRoadOneWay, nbRoadTwoWays;
|
||||
|
||||
// Maximum speed on this graph (in kmph).
|
||||
private final int maximumSpeed;
|
||||
|
||||
// Maximum length of any arc on this graph.
|
||||
private final float maximumLength;
|
||||
|
||||
/**
|
||||
* Create a new GraphStatistics instance with the given value.
|
||||
*
|
||||
* @param boundingBox Bounding-box for the graph.
|
||||
* @param nbRoadOneWay Number of one-way roads in the graph.
|
||||
* @param nbRoadTwoWays Number of two-ways roads in the graph.
|
||||
* @param maximumSpeed Maximum speed of any road of the graph. You can use
|
||||
* {@link #NO_MAXIMUM_SPEED} to indicate that the graph has no maximum
|
||||
* speed limit.
|
||||
* @param maximumLength Maximum length of any arc of the graph.
|
||||
*/
|
||||
public GraphStatistics(BoundingBox boundingBox, int nbRoadOneWay, int nbRoadTwoWays,
|
||||
int maximumSpeed, float maximumLength) {
|
||||
this.boundingBox = boundingBox;
|
||||
this.nbRoadOneWay = nbRoadOneWay;
|
||||
this.nbRoadTwoWays = nbRoadTwoWays;
|
||||
this.maximumLength = maximumLength;
|
||||
this.maximumSpeed = maximumSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The bounding box for this graph.
|
||||
*/
|
||||
public BoundingBox getBoundingBox() {
|
||||
return this.boundingBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Amount of one-way roads in this graph.
|
||||
*/
|
||||
public int getOneWayRoadCount() {
|
||||
return this.nbRoadOneWay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Amount of two-ways roads in this graph.
|
||||
*/
|
||||
public int getTwoWaysRoadCount() {
|
||||
return this.nbRoadTwoWays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of arcs in this graph.
|
||||
*
|
||||
* @see #getOneWayRoadCount()
|
||||
* @see #getTwoWaysRoadCount()
|
||||
*/
|
||||
public int getArcCount() {
|
||||
return getOneWayRoadCount() + 2 * getTwoWaysRoadCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this graph has a maximum speed limit, false otherwise.
|
||||
*/
|
||||
public boolean hasMaximumSpeed() {
|
||||
return this.maximumLength != NO_MAXIMUM_SPEED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Maximum speed of any arc in the graph, or {@link #NO_MAXIMUM_SPEED}
|
||||
* if some roads have no speed limitation.
|
||||
*/
|
||||
public int getMaximumSpeed() {
|
||||
return this.maximumSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Maximum length of any arc in the graph.
|
||||
*/
|
||||
public float getMaximumLength() {
|
||||
return this.maximumLength;
|
||||
}
|
||||
|
||||
}
|
159
be-graphes-model/src/main/java/org/insa/graphs/model/Node.java
Normal file
159
be-graphes-model/src/main/java/org/insa/graphs/model/Node.java
Normal file
|
@ -0,0 +1,159 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class representing a Node in a {@link Graph}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class holds information regarding nodes in the graph together with the
|
||||
* successors associated to the nodes.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Nodes are comparable based on their ID.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public final class Node implements Comparable<Node> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Link the two given nodes with one or two arcs (depending on roadInformation),
|
||||
* with the given attributes.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If {@code roadInformation.isOneWay()} is {@code true}, only a forward arc is
|
||||
* created (origin to destination) and added to origin. Otherwise, a
|
||||
* corresponding backward arc is created and add to destination.
|
||||
* </p>
|
||||
*
|
||||
* @param origin Origin of the arc.
|
||||
* @param destination Destination of the arc.
|
||||
* @param length Length of the arc.
|
||||
* @param roadInformation Information corresponding to the arc.
|
||||
* @param points Points for the arc.
|
||||
*
|
||||
* @return The newly created forward arc (origin to destination).
|
||||
*/
|
||||
public static Arc linkNodes(Node origin, Node destination, float length,
|
||||
RoadInformation roadInformation, ArrayList<Point> points) {
|
||||
Arc arc = null;
|
||||
if (roadInformation.isOneWay()) {
|
||||
arc = new ArcForward(origin, destination, length, roadInformation, points);
|
||||
origin.addSuccessor(arc);
|
||||
}
|
||||
else {
|
||||
Arc d2o;
|
||||
if (origin.getId() < destination.getId()) {
|
||||
arc = new ArcForward(origin, destination, length, roadInformation, points);
|
||||
d2o = new ArcBackward(arc);
|
||||
}
|
||||
else {
|
||||
Collections.reverse(points);
|
||||
d2o = new ArcForward(destination, origin, length, roadInformation, points);
|
||||
arc = new ArcBackward(d2o);
|
||||
}
|
||||
origin.addSuccessor(arc);
|
||||
destination.addSuccessor(d2o);
|
||||
}
|
||||
return arc;
|
||||
}
|
||||
|
||||
// ID of the node.
|
||||
private final int id;
|
||||
|
||||
// Point of this graph.
|
||||
private final Point point;
|
||||
|
||||
// Successors.
|
||||
private final ArrayList<Arc> successors;
|
||||
|
||||
/**
|
||||
* Create a new Node with the given ID corresponding to the given Point with an
|
||||
* empty list of successors.
|
||||
*
|
||||
* @param id ID of the node.
|
||||
* @param point Position of the node.
|
||||
*/
|
||||
public Node(int id, Point point) {
|
||||
this.id = id;
|
||||
this.point = point;
|
||||
this.successors = new ArrayList<Arc>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a successor to this node.
|
||||
*
|
||||
* @param arc Arc to the successor.
|
||||
*/
|
||||
protected void addSuccessor(Arc arc) {
|
||||
successors.add(arc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ID of this node.
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Number of successors of this node.
|
||||
*/
|
||||
public int getNumberOfSuccessors() {
|
||||
return this.successors.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this node has at least one successor.
|
||||
*/
|
||||
public boolean hasSuccessors() {
|
||||
return !this.successors.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of successors of this node (unmodifiable list).
|
||||
*
|
||||
* @see Collections#unmodifiableList(List)
|
||||
*/
|
||||
public List<Arc> getSuccessors() {
|
||||
return Collections.unmodifiableList(this.successors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Location of this node.
|
||||
*/
|
||||
public Point getPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (other instanceof Node) {
|
||||
return getId() == ((Node) other).getId();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the ID of this node with the ID of the given node.
|
||||
*
|
||||
* @param other Node to compare this node with.
|
||||
*
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Node other) {
|
||||
return Integer.compare(getId(), other.getId());
|
||||
}
|
||||
|
||||
}
|
248
be-graphes-model/src/main/java/org/insa/graphs/model/Path.java
Normal file
248
be-graphes-model/src/main/java/org/insa/graphs/model/Path.java
Normal file
|
@ -0,0 +1,248 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class representing a path between nodes in a graph.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A path is represented as a list of {@link Arc} with an origin and not a list
|
||||
* of {@link Node} due to the multi-graph nature (multiple arcs between two
|
||||
* nodes) of the considered graphs.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class Path {
|
||||
|
||||
/**
|
||||
* Create a new path that goes through the given list of nodes (in order),
|
||||
* choosing the fastest route if multiple are available.
|
||||
*
|
||||
* @param graph Graph containing the nodes in the list.
|
||||
* @param nodes List of nodes to build the path.
|
||||
*
|
||||
* @return A path that goes through the given list of nodes.
|
||||
*
|
||||
* @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
|
||||
* consecutive nodes in the list are not connected in the graph.
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes)
|
||||
throws IllegalArgumentException {
|
||||
List<Arc> arcs = new ArrayList<Arc>();
|
||||
// TODO:
|
||||
return new Path(graph, arcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new path that goes through the given list of nodes (in order),
|
||||
* choosing the shortest route if multiple are available.
|
||||
*
|
||||
* @param graph Graph containing the nodes in the list.
|
||||
* @param nodes List of nodes to build the path.
|
||||
*
|
||||
* @return A path that goes through the given list of nodes.
|
||||
*
|
||||
* @throws IllegalArgumentException If the list of nodes is not valid, i.e. two
|
||||
* consecutive nodes in the list are not connected in the graph.
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes)
|
||||
throws IllegalArgumentException {
|
||||
List<Arc> arcs = new ArrayList<Arc>();
|
||||
// TODO:
|
||||
return new Path(graph, arcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate the given paths.
|
||||
*
|
||||
* @param paths Array of paths to concatenate.
|
||||
*
|
||||
* @return Concatenated path.
|
||||
*
|
||||
* @throws IllegalArgumentException if the paths cannot be concatenated (IDs of
|
||||
* map do not match, or the end of a path is not the beginning of the
|
||||
* next).
|
||||
*/
|
||||
public static Path concatenate(Path... paths) throws IllegalArgumentException {
|
||||
if (paths.length == 0) {
|
||||
throw new IllegalArgumentException("Cannot concatenate an empty list of paths.");
|
||||
}
|
||||
final String mapId = paths[0].getGraph().getMapId();
|
||||
for (int i = 1; i < paths.length; ++i) {
|
||||
if (!paths[i].getGraph().getMapId().equals(mapId)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot concatenate paths from different graphs.");
|
||||
}
|
||||
}
|
||||
ArrayList<Arc> arcs = new ArrayList<>();
|
||||
for (Path path: paths) {
|
||||
arcs.addAll(path.getArcs());
|
||||
}
|
||||
Path path = new Path(paths[0].getGraph(), arcs);
|
||||
if (!path.isValid()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot concatenate paths that do not form a single path.");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Graph containing this path.
|
||||
private final Graph graph;
|
||||
|
||||
// Origin of the path
|
||||
private final Node origin;
|
||||
|
||||
// List of arcs in this path.
|
||||
private final List<Arc> arcs;
|
||||
|
||||
/**
|
||||
* Create an empty path corresponding to the given graph.
|
||||
*
|
||||
* @param graph Graph containing the path.
|
||||
*/
|
||||
public Path(Graph graph) {
|
||||
this.graph = graph;
|
||||
this.origin = null;
|
||||
this.arcs = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new path containing a single node.
|
||||
*
|
||||
* @param graph Graph containing the path.
|
||||
* @param node Single node of the path.
|
||||
*/
|
||||
public Path(Graph graph, Node node) {
|
||||
this.graph = graph;
|
||||
this.origin = node;
|
||||
this.arcs = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new path with the given list of arcs.
|
||||
*
|
||||
* @param graph Graph containing the path.
|
||||
* @param arcs Arcs to construct the path.
|
||||
*/
|
||||
public Path(Graph graph, List<Arc> arcs) {
|
||||
this.graph = graph;
|
||||
this.arcs = arcs;
|
||||
this.origin = arcs.size() > 0 ? arcs.get(0).getOrigin() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Graph containing the path.
|
||||
*/
|
||||
public Graph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return First node of the path.
|
||||
*/
|
||||
public Node getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Last node of the path.
|
||||
*/
|
||||
public Node getDestination() {
|
||||
return arcs.get(arcs.size() - 1).getDestination();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of arcs in the path.
|
||||
*/
|
||||
public List<Arc> getArcs() {
|
||||
return Collections.unmodifiableList(arcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this path is empty (it does not contain any node).
|
||||
*
|
||||
* @return true if this path is empty, false otherwise.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.origin == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of <b>nodes</b> in this path.
|
||||
*
|
||||
* @return Number of nodes in this path.
|
||||
*/
|
||||
public int size() {
|
||||
return isEmpty() ? 0 : 1 + this.arcs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this path is valid.
|
||||
*
|
||||
* A path is valid if any of the following is true:
|
||||
* <ul>
|
||||
* <li>it is empty;</li>
|
||||
* <li>it contains a single node (without arcs);</li>
|
||||
* <li>the first arc has for origin the origin of the path and, for two
|
||||
* consecutive arcs, the destination of the first one is the origin of the
|
||||
* second one.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return true if the path is valid, false otherwise.
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
// TODO:
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the length of this path (in meters).
|
||||
*
|
||||
* @return Total length of the path (in meters).
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public float getLength() {
|
||||
// TODO:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the time required to travel this path if moving at the given speed.
|
||||
*
|
||||
* @param speed Speed to compute the travel time.
|
||||
*
|
||||
* @return Time (in seconds) required to travel this path at the given speed (in
|
||||
* kilometers-per-hour).
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public double getTravelTime(double speed) {
|
||||
// TODO:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the time to travel this path if moving at the maximum allowed speed
|
||||
* on every arc.
|
||||
*
|
||||
* @return Minimum travel time to travel this path (in seconds).
|
||||
*
|
||||
* @deprecated Need to be implemented.
|
||||
*/
|
||||
public double getMinimumTravelTime() {
|
||||
// TODO:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
/**
|
||||
* Class representing a point (position) on Earth.
|
||||
*
|
||||
*/
|
||||
public final class Point {
|
||||
|
||||
/**
|
||||
* Approximated Earth radius (in meters).
|
||||
*/
|
||||
public static final double EARTH_RADIUS = 6378137.0;
|
||||
|
||||
/**
|
||||
* Compute the distance in meters between the two given points.
|
||||
*
|
||||
* @param p1 First point.
|
||||
* @param p2 second point.
|
||||
*
|
||||
* @return Distance between the two given points (in meters).
|
||||
*/
|
||||
public static double distance(Point p1, Point p2) {
|
||||
double sinLat = Math.sin(Math.toRadians(p1.getLatitude()))
|
||||
* Math.sin(Math.toRadians(p2.getLatitude()));
|
||||
double cosLat = Math.cos(Math.toRadians(p1.getLatitude()))
|
||||
* Math.cos(Math.toRadians(p2.getLatitude()));
|
||||
double cosLong = Math.cos(Math.toRadians(p2.getLongitude() - p1.getLongitude()));
|
||||
return EARTH_RADIUS * Math.acos(sinLat + cosLat * cosLong);
|
||||
}
|
||||
|
||||
// Longitude and latitude of the point.
|
||||
private final float longitude, latitude;
|
||||
|
||||
/**
|
||||
* Create a new point corresponding to the given (longitude, latitude) position.
|
||||
*
|
||||
* @param longitude Longitude of the point (in degrees).
|
||||
* @param latitude Latitude of the point (in degrees).
|
||||
*/
|
||||
public Point(float longitude, float latitude) {
|
||||
this.longitude = longitude;
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Longitude of this point (in degrees).
|
||||
*/
|
||||
public float getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Latitude of this point (in degrees).
|
||||
*/
|
||||
public float getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the distance from this point to the given point
|
||||
*
|
||||
* @param target Target point to compute distance to.
|
||||
*
|
||||
* @return Distance between this point and the target point, in meters.
|
||||
*/
|
||||
public double distanceTo(Point target) {
|
||||
return distance(this, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Point(%f, %f)", getLongitude(), getLatitude());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package org.insa.graphs.model;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class containing information for road that may be shared by multiple arcs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Sharing information between arcs reduces memory footprints of the program (a
|
||||
* long road is often split into multiple arcs at each intersection).
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class RoadInformation {
|
||||
|
||||
/**
|
||||
* Enumeration for road types.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://wiki.openstreetmap.org/wiki/Key:highway#Values">OpenStreetMap
|
||||
* reference for road types.</a>
|
||||
*/
|
||||
public enum RoadType {
|
||||
MOTORWAY,
|
||||
TRUNK,
|
||||
PRIMARY,
|
||||
SECONDARY,
|
||||
MOTORWAY_LINK,
|
||||
TRUNK_LINK,
|
||||
PRIMARY_LINK,
|
||||
SECONDARY_LINK,
|
||||
TERTIARY,
|
||||
TRACK,
|
||||
RESIDENTIAL,
|
||||
UNCLASSIFIED,
|
||||
LIVING_STREET,
|
||||
SERVICE,
|
||||
ROUNDABOUT,
|
||||
PEDESTRIAN,
|
||||
CYCLEWAY,
|
||||
COASTLINE
|
||||
}
|
||||
|
||||
// Type of the road (see above).
|
||||
private final RoadType type;
|
||||
|
||||
// Access information
|
||||
private final AccessRestrictions access;
|
||||
|
||||
// One way road?
|
||||
private final boolean oneway;
|
||||
|
||||
// Max speed in kilometers per hour.
|
||||
private final int maxSpeed;
|
||||
|
||||
// Name of the road.
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a new RoadInformation instance containing the given parameters.
|
||||
*
|
||||
* @param roadType Type of the road (see {@link RoadType}).
|
||||
* @param access Access restrictions for the road (see
|
||||
* {@link AccessRestrictions}).
|
||||
* @param isOneWay true if this road is a one way road, false otherwise.
|
||||
* @param maxSpeed Maximum speed for the road (in kilometers-per-hour).
|
||||
* @param name Name of the road.
|
||||
*/
|
||||
public RoadInformation(RoadType roadType, AccessRestrictions access, boolean isOneWay,
|
||||
int maxSpeed, String name) {
|
||||
this.type = roadType;
|
||||
this.access = access;
|
||||
this.oneway = isOneWay;
|
||||
this.maxSpeed = maxSpeed;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Access restrictions for this road.
|
||||
*/
|
||||
public AccessRestrictions getAccessRestrictions() {
|
||||
return this.access;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type of the road.
|
||||
*/
|
||||
public RoadType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the road is a one-way road.
|
||||
*/
|
||||
public boolean isOneWay() {
|
||||
return oneway;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Maximum speed for this road (in kilometers-per-hour).
|
||||
*/
|
||||
public int getMaximumSpeed() {
|
||||
return maxSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name of the road.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String typeAsString = "road";
|
||||
if (getType() == RoadType.COASTLINE) {
|
||||
typeAsString = "coast";
|
||||
}
|
||||
if (getType() == RoadType.MOTORWAY) {
|
||||
typeAsString = "highway";
|
||||
}
|
||||
return typeAsString + " : " + getName() + " " + (isOneWay() ? " (oneway) " : "") + maxSpeed
|
||||
+ " km/h (max.)";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.insa.graphs.model.io;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception thrown when a format-error is detected when reading a graph (e.g.,
|
||||
* non-matching check bytes).
|
||||
*
|
||||
*/
|
||||
public class BadFormatException extends IOException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public BadFormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new format exception with the given message.
|
||||
*
|
||||
* @param message Message for the exception.
|
||||
*/
|
||||
public BadFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.insa.graphs.model.io;
|
||||
|
||||
/**
|
||||
* Exception thrown when there is a mismatch between expected and actual magic
|
||||
* number.
|
||||
*
|
||||
*/
|
||||
public class BadMagicNumberException extends BadFormatException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -2176603967548838864L;
|
||||
|
||||
// Actual and expected magic numbers.
|
||||
private int actualNumber, expectedNumber;
|
||||
|
||||
/**
|
||||
* Create a new BadMagicNumberException with the given expected and actual magic
|
||||
* number.
|
||||
*
|
||||
* @param actualNumber Actual magic number (read from a file).
|
||||
* @param expectedNumber Expected magic number.
|
||||
*/
|
||||
public BadMagicNumberException(int actualNumber, int expectedNumber) {
|
||||
super(String.format("Magic number mismatch, expected %#X, got %#X.", expectedNumber,
|
||||
actualNumber));
|
||||
this.actualNumber = actualNumber;
|
||||
this.expectedNumber = expectedNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The actual magic number.
|
||||
*/
|
||||
public int getActualMagicNumber() {
|
||||
return actualNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The expected magic number.
|
||||
*/
|
||||
public int getExpectedMagicNumber() {
|
||||
return expectedNumber;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.insa.graphs.model.io;
|
||||
|
||||
/**
|
||||
* Exception thrown when the version of the file is not at least the expected
|
||||
* one.
|
||||
*
|
||||
*/
|
||||
public class BadVersionException extends BadFormatException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 7776317018302386042L;
|
||||
|
||||
// Actual and expected version..
|
||||
private int actualVersion, expectedVersion;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param actualVersion Actual version of the file.
|
||||
* @param expectedVersion Expected version of the file.
|
||||
*/
|
||||
public BadVersionException(int actualVersion, int expectedVersion) {
|
||||
super();
|
||||
this.actualVersion = actualVersion;
|
||||
this.expectedVersion = expectedVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Actual version of the file.
|
||||
*/
|
||||
public int getActualVersion() {
|
||||
return actualVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Expected (minimal) version of the file.
|
||||
*/
|
||||
public int getExpectedVersion() {
|
||||
return expectedVersion;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue