Merge branch 'dev' into 'gui'

# Conflicts:
#   src/main/java/fr/u_paris/gla/project/itinerary/Connection.java
#   src/main/java/fr/u_paris/gla/project/itinerary/Finder.java
#   src/main/java/fr/u_paris/gla/project/itinerary/ItineraryCalculator.java
#   src/main/java/fr/u_paris/gla/project/itinerary/Stop.java
#   src/main/java/fr/u_paris/gla/project/utils/CSVTools.java
This commit is contained in:
RODRIGUEZ lucas 2024-04-28 15:36:43 +02:00
commit 9b9cb38d35
42 changed files with 76471 additions and 349 deletions

8
.gitignore vendored
View file

@ -1,7 +1,15 @@
# Maven # Maven
target/ target/
# Files
.csv
# IDEs
# Eclipse # Eclipse
.settings/ .settings/
.project .project
.classpath .classpath
# VSCode/VSCodium
.vscode/
# IntelliJ
.idea/

View file

@ -6,14 +6,14 @@
## Etudiant ## Etudiant
Vous ne devez pas contribuer directement à ce projet mais devez en effectuer un fork. Une fois cela effectué vous devez: Vous ne devez pas contribuer directement à ce projet mais devez en effectuer un fork. Une fois cela effectué vous devez:
- [ ] Ajouter votre identifiant de groupe au champs `groupId` du fichier [pom.xml](pom.xml) sous la forme de `fr.u-paris.gla.votreequipe` - [X] Ajouter votre identifiant de groupe au champs `groupId` du fichier [pom.xml](pom.xml) sous la forme de `fr.u-paris.gla.votreequipe`
- [ ] Modifier le package principal afin de refleter le nouveau nom de groupe. - [ ] Modifier le package principal afin de refleter le nouveau nom de groupe.
- [ ] Adapter le fichier [README](README.md) au contenu de votre projet specifique - [ ] Adapter le fichier [README](README.md) au contenu de votre projet specifique
- [ ] Adapter ce fichier (CONTRIBUTING.md) à vos propres instructions de contribution, notamment: - [ ] Adapter ce fichier (CONTRIBUTING.md) à vos propres instructions de contribution, notamment:
- [ ] Convention de style de codage - [ ] Convention de style de codage
- [ ] Convention d'utilisation de git - [ ] Convention d'utilisation de git
- [ ] Lien avec d'autres projets et d'autres dépôts. - [ ] Lien avec d'autres projets et d'autres dépôts.
- [ ] Modifier le fichier `application.properties` au besoin. - [X] Modifier le fichier `application.properties` au besoin.
## Enseignant ## Enseignant

View file

@ -1,27 +1,39 @@
# Projet de GLA # Projet de GLA
Version 2024 Version 2024
## Description ## Description
Ceci est l'archetype de projet de Génie Logiciel Avancé (GLA).
Il s'agit d'un projet Java. Ce dépôt définit un système de build et une application simple. Il est nécéssaire de consulter le fichier [CONTRIBUTING.md](CONTRIBUTING.md) pour utiliser ce dépôt. Ceci est l'archétype de projet de Génie Logiciel Avancé (GLA).
Il s'agit d'un projet Java. Ce dépôt définit un système de build et une application simple. Il est nécessaire de consulter le fichier [CONTRIBUTING.md](CONTRIBUTING.md) pour utiliser ce dépôt.
## Lancement du programme ## Lancement du programme
Ce projet utilise [maven](https://maven.apache.org/) de Apache pour la gestion de construction.
Afin de compiler et lancer les tests, éxecutez simplement Ce projet utilise [maven](https://maven.apache.org/) d'Apache pour la gestion de construction.
```
Afin de compiler et lancer les tests, exécutez simplement
```bash
mvn verify mvn verify
``` ```
Afin de vérifier les tests via JaCoCo.
Les résultats du test sont dans `target/site/jacoco/index.html`.
```bash
mvn clean jacoco:prepare-agent install jacoco:report
```
Par la suite, `mvn jacoco:report` suffit.
Dans sa version initiale, le programme fournit est un simple code qui se lance en terminal ou en application graphique. Dans sa version initiale, le programme fournit est un simple code qui se lance en terminal ou en application graphique.
Une fois le programme compilé, vous trouverez un jar executable dans le dossier target. Au nom de jar près (version changeante), vous pourrez l'exécuter avec: Une fois le programme compilé, vous trouverez un jar executable dans le dossier target. Au nom de jar près (version changeante), vous pourrez l'exécuter avec:
``` ```
java -jar project-2024.1.0.0-SNAPSHOT.jar --info java -jar target/project-2024.1.0.0-SNAPSHOT.jar --info
``` ```
L'option de lancement `--info` causera l'affichage dans la console d'informations de l'application. L'option de lancement `--info` causera l'affichage dans la console d'informations de l'application.
L'option de lancement `--gui` causera l'ouverture d'une fenêtre affichant le logo de l'Université de Paris. L'option de lancement `--gui` causera l'ouverture d'une fenêtre affichant le logo de l'Université de Paris.

71869
arret_idfm.csv Normal file

File diff suppressed because it is too large Load diff

18
pom.xml
View file

@ -5,11 +5,11 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>fr.u-paris.gla</groupId> <groupId>fr.u-paris.gla.pathfinder</groupId>
<artifactId>project</artifactId> <artifactId>project</artifactId>
<version>2024.1.0.0-SNAPSHOT</version> <version>2024.1.0.0-SNAPSHOT</version>
<name>Project Base</name> <name>Pathfinder</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -31,7 +31,7 @@
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20230618</version> <version>20231013</version>
</dependency> </dependency>
</dependencies> </dependencies>
@ -67,6 +67,18 @@
</archive> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins> </plugins>
<resources> <resources>
<resource> <resource>

View file

@ -63,7 +63,7 @@ public class App {
} }
} }
/** @param out */ /** @param out the output stream */
public static void printAppInfos(PrintStream out) { public static void printAppInfos(PrintStream out) {
Properties props = readApplicationProperties(); Properties props = readApplicationProperties();

View file

@ -0,0 +1,18 @@
package fr.u_paris.gla.project.idfm;
/**
* A representation of a stop with the bifurcation that is needed. All stops
* have a list of bifstop called connected. BifStop is just composed of a
* connected stop and the bifurcation used to go from the first stop to the
* connected one.
*/
public class BifStop {
// The bifurcation
public int bifurc;
public Stop stop;
public BifStop(int bif, Stop stop){
bifurc = bif;
this.stop = stop;
}
}

View file

@ -0,0 +1,55 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import fr.u_paris.gla.project.io.ScheduleFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import fr.u_paris.gla.project.io.ImageFormat;
import fr.u_paris.gla.project.utils.GPS;
public final class CSVImageProvider {
private static final NumberFormat MINUTES_SECOND_FORMATTER = NumberFormat
.getInstance(Locale.ENGLISH);
static {
MINUTES_SECOND_FORMATTER.setMinimumIntegerDigits(2);
}
private final String[] line = new String[ImageFormat.NUMBER_COLUMNS];
private final Iterator<Transport> current;
/** Create the stream provider */
public CSVImageProvider(Iterator<Transport> traces) {
this.current = traces;
}
/** Check if next exists */
public boolean hasNext() {
return this.current.hasNext();
}
/** Get Next element */
public String[] next() {
if (!this.hasNext()) {
return null;
}
Transport element = this.current.next();
this.line[ImageFormat.LINE_INDEX] = element.name;
this.line[ImageFormat.IMAGE_URL_INDEX] = element.image_url;
return Arrays.copyOf(this.line, this.line.length);
}
}

View file

@ -0,0 +1,158 @@
package fr.u_paris.gla.project.idfm;
import fr.u_paris.gla.project.io.ScheduleFormat;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.Duration;
import java.time.LocalDateTime;
// import java.time.format.ResolverStyle;
import java.text.NumberFormat;
import java.util.*;
public class CSVSchedulesProvider {
private static final DateTimeFormatter HOUR_MINUTE_FORMATTER = ScheduleFormat.getTimeFormatter();
private static final HashMap<String, int[]> timings = new HashMap<String, int[]>(){{
put("Bus", new int[]{11, 8, 7});
put("Funicular", new int[]{15, 25, 20});
put("Tram", new int[]{6, 7, 8, 9});
put("Rail", new int[]{10, 11,15,12,20});
put("Subway", new int[]{4, 2, 6,3,3,4});
}};
// Time between 2 passages for the transports with a new type we don't know yet
private static int DEFAULT_TIMING = 6;
private static final NumberFormat MINUTES_SECOND_FORMATTER = NumberFormat
.getInstance(Locale.ENGLISH);
static {
MINUTES_SECOND_FORMATTER.setMinimumIntegerDigits(2);
}
private final String[] line = new String[ScheduleFormat.NUMBER_COLUMNS];
private final Iterator<Transport> currentTransport;
private Iterator<TraceDescription> currentDescription = Collections.emptyIterator();
private String current_tansport_type = "";
private LocalDateTime currentHour = null;
private LocalDateTime lastHour = null;
/**
* Create the stream provider
*/
public CSVSchedulesProvider(Iterator<Transport> transports) {
this.currentTransport = transports;
}
public boolean hasNext() {
return currentTransport.hasNext() || currentDescription.hasNext();
}
private void skipToNext() {
if(currentHour == null || lastHour == null){
skipToNextTransport();
}else if(currentHour.compareTo(lastHour) < 0){
// System.out.println("**Skip: Le current hour est plus petit "+currentHour+"|||"+lastHour);
addRandomMinutes();
}else if (currentHour.compareTo(lastHour) >= 0) {
// System.out.println("**Skip: Le current hour est plus grand "+currentHour+"|||"+lastHour);
skipToNextDescription();
}
else if (!this.currentDescription.hasNext()) {
skipToNextTransport();
}
}
/**
* Move to the the nextDescription of a Transport line
*/
private void skipToNextDescription() {
if (this.currentDescription.hasNext()) {
TraceDescription description = this.currentDescription.next();
currentHour = convertIntoLocalDateTime(description.first);
lastHour = convertIntoLocalDateTime(description.last);
if(lastHour.compareTo(currentHour) <= 0){
lastHour = lastHour.plusDays(1);
}
this.line[ScheduleFormat.TERMINUS_INDEX] = description.from;
this.line[ScheduleFormat.TRIP_SEQUENCE_INDEX] = description.bifurcation.toString();
this.line[ScheduleFormat.TIME_INDEX] = currentHour.format(HOUR_MINUTE_FORMATTER);
}else{
skipToNextTransport();
}
}
/**
* Move to the next Transport line
*/
private void skipToNextTransport() {
if (this.currentTransport.hasNext()) {
Transport transport = this.currentTransport.next();
this.line[ScheduleFormat.LINE_INDEX] = transport.name;
current_tansport_type = transport.type;
this.currentDescription = transport.descriptions.iterator();
skipToNextDescription();
}
}
public String[] next() {
if (!hasNext()) {
return null;
}
skipToNext();
return Arrays.copyOf(this.line, this.line.length);
// return new String[][]{Arrays.copyOf(this.line, this.line.length)};
}
/**
* Add random minutes for the next passage of a transport.
* The random minutes depends on the type of the transport
*/
private void addRandomMinutes() {
// System.out.println("** addM: AVANT: "+currentHour);
currentHour = currentHour.plusMinutes(pickMinute(current_tansport_type));
this.line[ScheduleFormat.TIME_INDEX] = currentHour.format(HOUR_MINUTE_FORMATTER);
// System.out.println("** addM: APRES: "+currentHour);
// debut ++;
// if(debut == 7) throw new IllegalArgumentException();
}
/**
*
* @param transportType the type of a transport
* @return a random minute depending on the type of the transport
*/
public static int pickMinute(String transportType) {
if (!timings.containsKey(transportType)) {
return DEFAULT_TIMING;
}
int[] temps = timings.get(transportType);
Random random = new Random();
int indexAleatoire = random.nextInt(temps.length);
return temps[indexAleatoire];
}
/**
*
* @param hourMinute hour and minute representation. Ex: "14:03"
* @return a datetime of today but using hourMinute
*/
public static LocalDateTime convertIntoLocalDateTime(String hourMinute) {
LocalDateTime aujourdHui = LocalDateTime.now();
LocalTime time = LocalTime.parse(hourMinute, HOUR_MINUTE_FORMATTER);
return aujourdHui.withHour(time.getHour()).withMinute(time.getMinute()).withSecond(0).withNano(0);
}
}

View file

@ -0,0 +1,269 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import fr.u_paris.gla.project.io.NetworkFormat;
import fr.u_paris.gla.project.utils.GPS;
/**
* CSV Stream Provider class
*/
public final class CSVStreamProvider {
/**
* Formatter from numbers into GPS Coordinates
*/
private static final NumberFormat GPS_FORMATTER = NetworkFormat
.getGPSFormatter();
/**
* Formatter from numbers into MM:SS
*/
private static final NumberFormat MINUTES_SECOND_FORMATTER = NumberFormat
.getInstance(Locale.ENGLISH);
static {
MINUTES_SECOND_FORMATTER.setMinimumIntegerDigits(2);
}
/** Number of seconds in a minute. */
private static final int SECONDS_IN_MINUTES = 60;
/**
* Number of seconds in an hour
*/
private static final long SECONDS_IN_HOURS = 3_600;
// Magically chosen values
/** Maximal speed in km/h */
private static final double MAX_SPEED = 5;
/** Distance to reach maximal speed in km */
private static final double TWO_ACCELERATION_DISTANCE = 0.2;
/**
* Current CSV Line
*/
private final String[] line = new String[NetworkFormat.NUMBER_COLUMNS];
/**
* Current CSV transport line iterator
*/
private final Iterator<TraceEntry> currentTrace;
/**
* Current Stop path iterator
*/
private Iterator<List<StopEntry>> currentPath = Collections.emptyIterator();
/**
* current iterator for the begin of the line
*/
private Iterator<StopEntry> currentSegmentStart = Collections.emptyIterator();
/**
* current iterator for the end of the line
*/
private Iterator<StopEntry> currentSegmentEnd = Collections.emptyIterator();
/**
* HashMap of the current line's segments
*/
Map<StopEntry, Set<StopEntry>> lineSegments = new HashMap<>();
// The transport id with its value
private final Map<String, Transport> transports;
List <TraceDescription> descriptions = new ArrayList<>();
/**
* current begin of line
*/
private StopEntry start = null;
/**
* current end of line
*/
private StopEntry end = null;
/**
* csv stream iterator checker
*/
private boolean hasNext = false;
/**
* tells if we're already on the next
*/
private boolean onNext = false;
private String traceId = "";
private String traceType = "";
private String url_image = "";
/** Create the stream provider
* @param traces an iterator of the possible traces
* @param t map of transports */
public CSVStreamProvider(Iterator<TraceEntry> traces, Map<String, Transport> t) {
this.currentTrace = traces;
transports = t;
}
/** Method that tells if we have segments or paths to go through
* @return if there are next elements or not
*/
public boolean hasNext() {
if (!this.onNext) {
skipToNext();
}
return this.hasNext;
}
/**
* Skip to either the next segment or the next path
*/
private void skipToNext() {
if (this.onNext) {
return;
}
while (!this.onNext) {
if (!this.currentSegmentEnd.hasNext()) {
skipToNextCandidatePath();
}
if (this.onNext) {
return;
}
skipToNextNewSegment();
}
}
/**
* Skips to the next segment
*/
private void skipToNextNewSegment() {
do {
this.start = this.currentSegmentStart.next();
this.lineSegments.putIfAbsent(this.start, new HashSet<>());
this.end = this.currentSegmentEnd.next();
} while (this.lineSegments.get(this.start).contains(this.end)
&& this.currentSegmentEnd.hasNext());
if (!this.lineSegments.get(this.start).contains(this.end)) {
this.lineSegments.get(this.start).add(this.end);
this.onNext = true;
this.hasNext = true;
}
}
/** Move the reading head of path to the next one that has at least two
* elements */
private void skipToNextCandidatePath() {
currentSegmentStart = null;
do {
while (!this.currentPath.hasNext()) {
if (!this.currentTrace.hasNext()) {
this.hasNext = false;
this.onNext = true;
return;
}
TraceEntry trace = this.currentTrace.next();
this.traceId = trace.id;
this.traceType = trace.type;
this.url_image = trace.url;
this.descriptions.clear();
this.descriptions.addAll(trace.descriptions);
this.currentPath = trace.getPaths().iterator();
this.line[NetworkFormat.LINE_INDEX] = trace.lname;
this.lineSegments.clear();
}
List<StopEntry> path = this.currentPath.next();
this.currentSegmentEnd = path.iterator();
if (this.currentSegmentEnd.hasNext()) {
this.currentSegmentEnd.next();
this.currentSegmentStart = path.iterator();
}
} while (currentSegmentStart == null);
}
/** Store current trace' data as a String array
* @return The newly generated line of text
*/
public String[] next() {
if (!this.onNext) {
skipToNext();
}
this.onNext = false;
fillStation(this.start, this.line, NetworkFormat.START_INDEX);
fillStation(this.end, this.line, NetworkFormat.STOP_INDEX);
double distance = GPS.distance(this.start.latitude, this.start.longitude,
this.end.latitude, this.end.longitude);
this.line[NetworkFormat.DISTANCE_INDEX] = NumberFormat.getInstance(Locale.ENGLISH)
.format(distance);
this.line[NetworkFormat.DURATION_INDEX] = formatTime(
(long) Math.ceil(distanceToTime(distance) * SECONDS_IN_HOURS));
int bifurcation = this.lineSegments.get(this.start).size() - 1;
this.line[NetworkFormat.VARIANT_INDEX] = Integer
.toString(bifurcation);
fillTransports(bifurcation);
return Arrays.copyOf(this.line, this.line.length);
// return new String[][]{Arrays.copyOf(this.line, this.line.length)};
}
/** creates adds a station into the next line String
* @param stop the stop
* @param nextLine the next line
* @param index the stop index in the next line */
private static void fillStation(StopEntry stop, String[] nextLine, int index) {
nextLine[index] = stop.lname;
nextLine[index + 1] = MessageFormat.format("{0}, {1}", //$NON-NLS-1$
GPS_FORMATTER.format(stop.latitude),
GPS_FORMATTER.format(stop.longitude));
}
/** turns a number into a formatted time string
* @param time the time value
* @return the time as a String */
private static String formatTime(long time) {
return MessageFormat.format("{0}:{1}", //$NON-NLS-1$
MINUTES_SECOND_FORMATTER.format(time / SECONDS_IN_MINUTES), MINUTES_SECOND_FORMATTER.format(time % SECONDS_IN_MINUTES));
}
/** A tool method to give a delay to go through a certain distance.
* <p>
* This is a model with an linear acceleration and deceleration periods and a
* constant speed in between.
*
* @param distance the distance (in km)
* @return the duration of the trip (in hours) */
private static double distanceToTime(double distance) {
return Math.max(0, distance - TWO_ACCELERATION_DISTANCE) / MAX_SPEED
+ Math.pow(Math.min(distance, TWO_ACCELERATION_DISTANCE) / MAX_SPEED, 2);
}
private void fillTransports(int bif) {
if(transports != null){
String nameTransport = this.line[NetworkFormat.LINE_INDEX];
String start_p = this.line[NetworkFormat.START_INDEX];
String end_p = this.line[NetworkFormat.STOP_INDEX];
// String bifurcation = this.line[NetworkFormat.VARIANT_INDEX];
Transport transp = null;
if(!transports.containsKey(traceId)){
transp = new Transport(nameTransport,traceType, url_image);
transports.put(traceId, transp);
}else{
transp = transports.get(traceId);
}
transp.addStop(start_p, end_p, bif);
if(transp.descriptions.isEmpty()){
transp.addDescriptions(descriptions);
}
}
}
}

View file

@ -3,323 +3,432 @@
*/ */
package fr.u_paris.gla.project.idfm; package fr.u_paris.gla.project.idfm;
import java.io.BufferedReader; import fr.u_paris.gla.project.utils.CSVTools;
import java.io.FileWriter; import fr.u_paris.gla.project.utils.GPS;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import com.opencsv.CSVParserBuilder; import java.io.IOException;
import com.opencsv.CSVReader; import java.io.File;
import com.opencsv.CSVReaderBuilder; import java.text.MessageFormat;
import com.opencsv.CSVWriterBuilder; import java.util.*;
import com.opencsv.ICSVParser; import java.util.Map.Entry;
import com.opencsv.ICSVWriter; import java.util.logging.Level;
import com.opencsv.exceptions.CsvValidationException; import java.util.logging.Logger;
import java.util.stream.Stream;
import fr.u_paris.gla.project.io.NetworkFormat; /**
import fr.u_paris.gla.project.utils.GPS; * Code of an extractor for the data from IDF mobilite.
/** Code of an extractor for the data from IDF mobilite.
* *
* @author Emmanuel Bigeon */ * @author Emmanuel Bigeon
*/
public class IDFMNetworkExtractor { public class IDFMNetworkExtractor {
/** The logger for information on the process */
/**
* The logger for information on the process
*/
private static final Logger LOGGER = Logger private static final Logger LOGGER = Logger
.getLogger(IDFMNetworkExtractor.class.getName()); .getLogger(IDFMNetworkExtractor.class.getName());
/**
* the URL of the Trace CSV
*/
// IDF mobilite API URLs // IDF mobilite API URLs
private static final String TRACE_FILE_URL = "https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/traces-des-lignes-de-transport-en-commun-idfm/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&delimiter=%3B"; private static final String TRACE_FILE_URL = "https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/traces-des-lignes-de-transport-en-commun-idfm/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&delimiter=%3B";
/**
* The URL of the Stops CSV
*/
private static final String STOPS_FILE_URL = "https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/arrets-lignes/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&delimiter=%3B"; private static final String STOPS_FILE_URL = "https://data.iledefrance-mobilites.fr/api/explore/v2.1/catalog/datasets/arrets-lignes/exports/csv?lang=fr&timezone=Europe%2FBerlin&use_labels=true&delimiter=%3B";
private static final String TRACE_FILE_DOWNLOADED_NAME = "./trace_idfm.csv";
private static final String STOPS_FILE_DOWNLOADED_NAME = "./arret_idfm.csv";
/**
* the index in the CSV of a Trace's ID
*/
// IDF mobilite csv formats // IDF mobilite csv formats
private static final int IDFM_TRACE_ID_INDEX = 0; private static final int IDFM_TRACE_ID_INDEX = 0;
/**
* the index in the CSV of a Trace's Name
*/
private static final int IDFM_TRACE_SNAME_INDEX = 1; private static final int IDFM_TRACE_SNAME_INDEX = 1;
/**
* the index in the CSV of a Trace's shape
*/
private static final int IDFM_TRACE_SHAPE_INDEX = 6; private static final int IDFM_TRACE_SHAPE_INDEX = 6;
private static final int IDFM_TRACE_TYPE_INDEX = 3;
/**
* The index in the CSV of the Stops' id
*/
private static final int IDFM_STOPS_RID_INDEX = 0; private static final int IDFM_STOPS_RID_INDEX = 0;
/**
* The index in the CSV of the Stops' schedules
*/
private static final int IDFM_STOPS_SCHEDULES_INDEX = 3;
/**
* The index in the CSV of the Stops' names
*/
private static final int IDFM_STOPS_NAME_INDEX = 5; private static final int IDFM_STOPS_NAME_INDEX = 5;
/**
* The index in the CSV of the Stops' longitude
*/
private static final int IDFM_STOPS_LON_INDEX = 6; private static final int IDFM_STOPS_LON_INDEX = 6;
/**
* The index in the CSV of the Stops' latitude
*/
private static final int IDFM_STOPS_LAT_INDEX = 7; private static final int IDFM_STOPS_LAT_INDEX = 7;
private static final int IDFM_URL_INDEX = 10;
private static final String TRACE_FILE_NAME = "trace.csv";
private static final String HOURS_FILE_NAME = "hours.csv";
private static final String IMAGES_FILE_NAME = "./images.csv";
// Magically chosen values // Magically chosen values
/** A number of stops on each line */ /**
* A number of stops on each line
*/
private static final int GUESS_STOPS_BY_LINE = 5; private static final int GUESS_STOPS_BY_LINE = 5;
/** Maximal speed in km/h */
private static final double MAX_SPEED = 5;
/** Distance to reach maximal speed in km */
private static final double ACCELERATION_DISTANCE = 0.1;
/**
* The quarter of a kilometer as a static value
*/
// Well named constants // Well named constants
private static final double _250_METERS = .25; private static final double QUARTER_KILOMETER = .25;
private static final long SECONDS_IN_HOURS = 3_600;
private static final Format GPS_FORMATTER = NetworkFormat.getGPSFormatter(); /**
* Main entry point for the extractor of IDF mobilité data into a network as
public static class StopEntry {
private String lname;
public final double longitude;
public final double latitude;
/** Create the stop
*
* @param lname
* @param longitude
* @param latitude */
public StopEntry(String lname, double longitude, double latitude) {
super();
this.lname = lname;
this.longitude = longitude;
this.latitude = latitude;
}
@Override
public String toString() {
return MessageFormat.format("{0} [{1}, {2}]", this.lname, this.longitude,
this.latitude);
}
}
public static final class UnidentifiedStopEntry extends StopEntry {
/** Create the stop
*
* @param longitude
* @param latitude */
public UnidentifiedStopEntry(double longitude, double latitude) {
super("Unidentified", longitude, latitude);
}
List<StopEntry> candidates = new ArrayList<>();
@Override
public String toString() {
return "UnidentifiedStop [candidates=" + this.candidates + "]";
}
}
private static final class TraceEntry {
String lname;
List<List<StopEntry>> stops = new ArrayList<>();
}
public static void readCSVFromURL(String url, Consumer<String[]> contentLineConsumer)
throws IOException {
ICSVParser parser = new CSVParserBuilder().withSeparator(';').build();
try (InputStream is = new URL(url).openStream();
Reader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
CSVReaderBuilder csvBuilder = new CSVReaderBuilder(reader)
.withCSVParser(parser);
try (CSVReader csv = csvBuilder.build()) {
String[] line = csv.readNextSilently(); // Eliminate header
while (csv.peek() != null) {
line = csv.readNext();
contentLineConsumer.accept(line);
}
}
} catch (CsvValidationException e) {
throw new IOException("Invalid csv file for lines", e);
}
}
/** Main entry point for the extractor of IDF mobilite data into a network as
* defined by this application. * defined by this application.
* *
* @param args the arguments (expected one for the destination file) */ * @param args the arguments (expected one for the destination file)
*/
public static void main(String[] args) { public static void main(String[] args) {
if (args.length != 1) { if (args.length != 2) {
LOGGER.severe("Invalid command line. Missing target file."); LOGGER.severe("Invalid command line. Missing target files.");
return; return;
} }
Map<String, TraceEntry> traces = new HashMap<>(); Map<String, TraceEntry> traces = new HashMap<>();
try { try {
readCSVFromURL(TRACE_FILE_URL, (String[] line) -> { CSVTools.readCSVFromFile(TRACE_FILE_DOWNLOADED_NAME,
TraceEntry entry = new TraceEntry(); (String[] line) -> addLine(line, traces));
entry.lname = line[IDFM_TRACE_SNAME_INDEX];
List<List<StopEntry>> buildPaths = buildPaths(
line[IDFM_TRACE_SHAPE_INDEX]);
entry.stops.addAll(buildPaths);
if (buildPaths.isEmpty()) {
LOGGER.severe(() -> MessageFormat.format(
"Line {0} has no provided itinerary and was ignored",
entry.lname));
} else {
traces.put(line[IDFM_TRACE_ID_INDEX], entry);
}
});
} catch (IOException e) { } catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the line paths", e); LOGGER.log(Level.SEVERE, "Error while reading the line paths", e);
} }
List<StopEntry> stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE); List<StopEntry> stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE);
try { try {
readCSVFromURL(STOPS_FILE_URL, (String[] line) -> { CSVTools.readCSVFromFile(STOPS_FILE_DOWNLOADED_NAME,
StopEntry entry = new StopEntry(line[IDFM_STOPS_NAME_INDEX], (String[] line) -> addStop(line, traces, stops));
Double.parseDouble(line[IDFM_STOPS_LON_INDEX]),
Double.parseDouble(line[IDFM_STOPS_LAT_INDEX]));
String rid = line[IDFM_STOPS_RID_INDEX];
BiFunction<? super String, ? super TraceEntry, ? extends TraceEntry> func = (
k, trace) -> {
for (List<StopEntry> path : trace.stops) {
for (StopEntry stopEntry : path) {
if (stopEntry instanceof UnidentifiedStopEntry
&& GPS.distance(entry.latitude, entry.longitude,
stopEntry.latitude,
stopEntry.longitude) < _250_METERS) {
((UnidentifiedStopEntry) stopEntry).candidates.add(entry);
}
}
}
return trace;
};
traces.computeIfPresent(rid, func);
stops.add(entry);
});
} catch (IOException e) { } catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the stops", e); LOGGER.log(Level.SEVERE, "Error while reading the stops", e);
} }
cleanTraces(traces);
Map<String, Transport> transports = new HashMap<>();
CSVStreamProvider provider = new CSVStreamProvider(traces.values().iterator(), transports);
// Write into args[0]
try {
CSVTools.writeCSVToFile(args[0], Stream.iterate(provider.next(),
t -> provider.hasNext(), t -> provider.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", args[0]));
}
/*CSVStreamSchedulesProvider providerschedules = new CSVStreamSchedulesProvider(traces.values().iterator());
// TraceEntry tmp = traces.values().iterator().next();
// tmp.getTerminus()
// .forEach(m -> LOGGER.log(Level.INFO, m));
// Write into args[1]
try {
CSVTools.writeCSVToFile(args[1], Stream.iterate(providerschedules.next(),
t -> providerschedules.hasNext(), t -> providerschedules.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", args[1]));
}*/
// System.out.println("****** END ******");
// System.out.println(transports.size());
// System.out.println(transports.get("IDFM:C01371").name);
// System.out.println(transports.get("IDFM:C02060").name);
// Transport ligne_1 = transports.get("IDFM:C01371");
// Transport ligne_7 = transports.get("IDFM:C01377");
// Transport rerd = transports.get("IDFM:C01728");
// Transport b54b = transports.get("IDFM:C00940");
// System.out.println("****** AFFICHAGE LIGNE ******");
// Stop maisonBlanche = ligne_7.stopsMap.get("Maison Blanche");
// System.out.println(maisonBlanche.name);
// for (BifStop nextEntry : maisonBlanche.connected.values()) {
// System.out.println(nextEntry.bifurc+ nextEntry.stop.name);
// }
// System.out.println("****** AFFICHAGE LIGNE ******");
// Stop corientin = ligne_7.stopsMap.get("Corentin Cariou");
// System.out.println(corientin.name);
// for (BifStop nextEntry : corientin.connected.values()) {
// System.out.println(nextEntry.bifurc+ nextEntry.stop.name);
// }
// System.out.println("***************************");
// System.out.println("****** AFFICHAGE Description ******");
// System.out.println(traces.get("IDFM:C01377").descriptions);
// System.out.println("****** AFFICHAGE Description False ******");
// System.out.println(ligne_7.descriptions);
// System.out.println("****************** Build la path ***********************");
// System.out.println(ligne_7.type);
// System.out.println(rerd.type);
// ligne_7.buildBifurcation();
// rerd.buildBifurcation();
// System.out.println("******************Derniere description ***********************");
// System.out.println(ligne_7.descriptions);
// System.out.println("******************Description 54B ************************");
// b54b.buildBifurcation();
// System.out.println(b54b.descriptions);
System.out.println("******************Building bifurcations ************************");
long startTime = System.currentTimeMillis();
for (Transport entry : transports.values()) {
entry.buildBifurcationOptimzed();
}
long endTime = System.currentTimeMillis();
long tempsPasse = endTime - startTime;
long minutes = (tempsPasse / 1000) / 60;
long seconds = (tempsPasse / 1000) % 60;
long milliseconds = tempsPasse % 1000;
System.out.println("Temps écoulé : " + minutes + " minutess, " + seconds + " secndes et " + milliseconds + " millis");
System.out.println("******************Fin Building bifurcations ************************");
CSVSchedulesProvider providerschedules = new CSVSchedulesProvider(transports.values().iterator());
try {
CSVTools.writeCSVToFile(args[1], Stream.iterate(providerschedules.next(),
t -> providerschedules.hasNext(), t -> providerschedules.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", args[1]));
}
CSVImageProvider providerimage = new CSVImageProvider(transports.values().iterator());
String imageCSV = "image.csv";
try {
CSVTools.writeCSVToFile(imageCSV, Stream.iterate(providerimage.next(),
t -> providerimage.hasNext(), t -> providerimage.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", imageCSV));
}
}
public static boolean checkFileExistence(String filePath) {
File file = new File(filePath);
if (file.exists()) {
LOGGER.severe(filePath+ " already exists.");
return true;
} else {
LOGGER.severe(filePath + " does not exist.");
return false;
}
}
public static void builFiles() {
if (checkFileExistence("./"+HOURS_FILE_NAME) && checkFileExistence("./"+TRACE_FILE_NAME)) {
LOGGER.severe("Files already exists.");
return;
}
Map<String, TraceEntry> traces = new HashMap<>();
try {
CSVTools.readCSVFromFile(TRACE_FILE_DOWNLOADED_NAME,
(String[] line) -> addLine(line, traces));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the line paths", e);
}
List<StopEntry> stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE);
try {
CSVTools.readCSVFromFile(STOPS_FILE_DOWNLOADED_NAME,
(String[] line) -> addStop(line, traces, stops));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the stops", e);
}
cleanTraces(traces);
Map<String, Transport> transports = new HashMap<>();
CSVStreamProvider provider = new CSVStreamProvider(traces.values().iterator(), transports);
// Write into args[0]
try {
CSVTools.writeCSVToFile(TRACE_FILE_NAME, Stream.iterate(provider.next(),
t -> provider.hasNext(), t -> provider.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", TRACE_FILE_NAME));
}
System.out.println("******************Building bifurcations ************************");
long startTime = System.currentTimeMillis();
for (Transport entry : transports.values()) {
entry.buildBifurcationOptimzed();
}
long endTime = System.currentTimeMillis();
long tempsPasse = endTime - startTime;
long minutes = (tempsPasse / 1000) / 60;
long seconds = (tempsPasse / 1000) % 60;
long milliseconds = tempsPasse % 1000;
System.out.println("Temps écoulé : " + minutes + " minutess, " + seconds + " secndes et " + milliseconds + " millis");
System.out.println("******************Fin Building bifurcations ************************");
CSVSchedulesProvider providerschedules = new CSVSchedulesProvider(transports.values().iterator());
try {
CSVTools.writeCSVToFile(HOURS_FILE_NAME, Stream.iterate(providerschedules.next(),
t -> providerschedules.hasNext(), t -> providerschedules.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", HOURS_FILE_NAME));
}
CSVImageProvider providerimage = new CSVImageProvider(transports.values().iterator());
try {
CSVTools.writeCSVToFile(IMAGES_FILE_NAME, Stream.iterate(providerimage.next(),
t -> providerimage.hasNext(), t -> providerimage.next()));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> MessageFormat.format("Could not write in file {0}", IMAGES_FILE_NAME));
}
}
/** Clean the traces/remove the unresolved lines
* @param traces the traces to clean
*/
private static void cleanTraces(Map<String, TraceEntry> traces) {
Set<String> toRemove = new HashSet<>(); Set<String> toRemove = new HashSet<>();
for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) { for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) {
TraceEntry trace = traceEntry.getValue(); TraceEntry trace = traceEntry.getValue();
for (List<StopEntry> path : trace.stops) { if (!cleanLine(trace.getPaths())) {
for (int i = 0; i < path.size(); i++) { LOGGER.severe(() -> MessageFormat.format(
StopEntry stop = path.get(i); "Missing stop for line {0}. Line will be removed", trace.lname));
if (stop instanceof UnidentifiedStopEntry)
stop = resolve((UnidentifiedStopEntry) stop);
if (stop instanceof UnidentifiedStopEntry
&& ((UnidentifiedStopEntry) stop).candidates.isEmpty()) {
LOGGER.severe("Missing stop for line " + trace.lname
+ ". Line will be removed");
toRemove.add(traceEntry.getKey()); toRemove.add(traceEntry.getKey());
continue;
}
path.set(i, stop);
} }
} }
}
for (String string : toRemove) { for (String string : toRemove) {
traces.remove(string); traces.remove(string);
} }
// Export content in required format
try (FileWriter writer = new FileWriter(args[0], StandardCharsets.UTF_8)) {
CSVWriterBuilder wBuilder = new CSVWriterBuilder(writer).withSeparator(';');
try (ICSVWriter csv = wBuilder.build()) {
for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) {
Map<StopEntry, Set<StopEntry>> lineSegments = new HashMap<>();
String[] nextLine = new String[NetworkFormat.NUMBER_COLUMNS];
nextLine[NetworkFormat.LINE_INDEX] = traceEntry.getValue().lname;
for (List<StopEntry> path : traceEntry.getValue().stops) {
for (int i = 0; i < path.size() - 1; i++) {
StopEntry stop1 = path.get(i);
lineSegments.putIfAbsent(stop1, new HashSet<>());
StopEntry stop2 = path.get(i + 1);
if (!lineSegments.get(stop1).contains(stop2)) {
fillStation(stop1, nextLine, NetworkFormat.START_INDEX);
fillStation(stop2, nextLine, NetworkFormat.STOP_INDEX);
double distance = GPS.distance(stop1.latitude,
stop1.longitude, stop2.latitude, stop2.longitude);
nextLine[NetworkFormat.DISTANCE_INDEX] = NumberFormat
.getInstance(Locale.ENGLISH).format(distance);
nextLine[NetworkFormat.DURATION_INDEX] = formatTime(
(long) Math.ceil(distanceToTime(distance)
* SECONDS_IN_HOURS));
nextLine[NetworkFormat.VARIANT_INDEX] = Integer
.toString(lineSegments.get(stop1).size());
csv.writeNext(nextLine);
lineSegments.get(stop1).add(stop2);
} }
/** Tells if the current trasport line has all its stops entries resolved
* @param stops the stops list
* @return if the line is "clean"*/
private static boolean cleanLine(List<List<StopEntry>> stops) {
for (List<StopEntry> path : stops) {
for (int i = 0; i < path.size(); i++) {
StopEntry stop = path.get(i);
if (!(stop instanceof UnidentifiedStopEntry unidentified)) {
continue;
}
StopEntry stopResolution = unidentified.resolve();
if (stopResolution == null) {
return false;
}
path.set(i, stopResolution);
}
}
return true;
}
/** adds a stop to all related variables
* @param line the transport line involved with the new stop
* @param traces the traces related to it
* @param stops the general stops list
*/
private static void addStop(String[] line, Map<String, TraceEntry> traces,
List<StopEntry> stops) {
StopEntry entry = new StopEntry(line[IDFM_STOPS_NAME_INDEX],
Double.parseDouble(line[IDFM_STOPS_LON_INDEX]),
Double.parseDouble(line[IDFM_STOPS_LAT_INDEX]));
String rid = line[IDFM_STOPS_RID_INDEX];
//Add traces description if it's empty
if (traces.containsKey(rid)) {
TraceEntry tmp = traces.get(rid);
if (tmp.isDescriptionEmpty()) {
List<TraceDescription> descriptions = extractDescription(line[IDFM_STOPS_SCHEDULES_INDEX]);
tmp.addDescriptions(descriptions);
} }
} }
// Add terminus to the traces
if (traces.containsKey(rid)) {
extractTerminus(line[IDFM_STOPS_SCHEDULES_INDEX]).forEach(t -> traces.get(rid).addTerminus(t));
} }
traces.computeIfPresent(rid,
(String k, TraceEntry trace) -> addCandidate(trace, entry));
stops.add(entry);
} }
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e, () -> "Could not write in file " + args[1]); /** add a line to the related list of traces
* @param line the line as a string
* @param traces the traces
*/
private static void addLine(String[] line, Map<String, TraceEntry> traces) {
TraceEntry entry = new TraceEntry(line[IDFM_TRACE_SNAME_INDEX], line[IDFM_TRACE_ID_INDEX],line[IDFM_TRACE_TYPE_INDEX], line[IDFM_URL_INDEX]);
List<List<StopEntry>> buildPaths = buildPaths(line[IDFM_TRACE_SHAPE_INDEX]);
entry.getPaths().addAll(buildPaths);
if (buildPaths.isEmpty()) {
LOGGER.severe(() -> MessageFormat.format(
"Line {0} has no provided itinerary and was ignored", entry.lname));
} else {
traces.put(line[IDFM_TRACE_ID_INDEX], entry);
} }
} }
/** @param distanceToTime /** add a new entry as a candidate to a trace
* @return */ * @param trace the trace
private static String formatTime(long time) { * @param entry the entry
NumberFormat format = NumberFormat.getInstance(Locale.ENGLISH); * @return the trace in question
format.setMinimumIntegerDigits(2); */
return MessageFormat.format("{0}:{1}", format.format(time / 60), private static TraceEntry addCandidate(TraceEntry trace, StopEntry entry) {
format.format(time % 60)); for (List<StopEntry> path : trace.getPaths()) {
} for (StopEntry stopEntry : path) {
if (stopEntry instanceof UnidentifiedStopEntry unidentified
/** A tool method to give a delay to go through a certain distance. && GPS.distance(entry.latitude, entry.longitude,
* <p> stopEntry.latitude,
* This is a model with an linear acceleration and deceleration periods and a stopEntry.longitude) < QUARTER_KILOMETER) {
* constant speed in between. unidentified.addCandidate(entry);
* }
* @param distance the distance (in km) }
* @return the duration of the trip (in hours) */ }
private static double distanceToTime(double distance) { return trace;
return Math.max(0, distance - 2 * ACCELERATION_DISTANCE) / MAX_SPEED
+ Math.pow(Math.min(distance, 2 * ACCELERATION_DISTANCE) / MAX_SPEED, 2);
}
/** @param stop1
* @param nextLine
* @param i */
private static void fillStation(StopEntry stop, String[] nextLine, int index) {
nextLine[index] = stop.lname;
nextLine[index + 1] = MessageFormat.format("{0}, {1}",
GPS_FORMATTER.format(stop.latitude),
GPS_FORMATTER.format(stop.longitude));
}
/** @param stop
* @return */
private static StopEntry resolve(UnidentifiedStopEntry stop) {
if (stop.candidates.isEmpty()) {
LOGGER.severe("Unable to find stop name, will use a placeholder");
return stop;
}
if (stop.candidates.size() == 1) {
return stop.candidates.get(0);
}
Collections.sort(stop.candidates,
(Comparator<? super StopEntry>) (StopEntry s1,
StopEntry s2) -> (int) Math.signum((GPS.distance(stop.latitude,
stop.longitude, s1.latitude, s1.longitude)
- GPS.distance(stop.latitude, stop.longitude, s2.latitude,
s2.longitude))));
return stop.candidates.get(0);
} }
/** turn a JSON list of stops into a list of paths
* @param pathsJSON the JSON String of all paths
* @return the paths as a List of StopEntries
*/
private static List<List<StopEntry>> buildPaths(String pathsJSON) { private static List<List<StopEntry>> buildPaths(String pathsJSON) {
List<List<StopEntry>> all = new ArrayList<>(); List<List<StopEntry>> all = new ArrayList<>();
try { try {
@ -341,8 +450,56 @@ public class IDFMNetworkExtractor {
} }
} catch (JSONException e) { } catch (JSONException e) {
// Ignoring invalid element! // Ignoring invalid element!
LOGGER.log(Level.FINE, "Invalid json element " + pathsJSON, e); //$NON-NLS-1$ LOGGER.log(Level.FINE, e,
() -> MessageFormat.format("Invalid json element {0}", pathsJSON)); //$NON-NLS-1$
} }
return all; return all;
} }
/** extract the terminus out of a JSON file
* @param JSON the JSON
* @return a list of strings related to the terminus
*/
private static List<String> extractTerminus(String JSON) {
List<String> all = new ArrayList<>();
try {
JSONArray schedules = new JSONArray(JSON);
for (int i = 0; i < schedules.length(); i++) {
JSONObject stop = schedules.getJSONObject(i);
String terminus = stop.getString("from");
all.add(terminus);
}
} catch (
JSONException e) {
// Ignoring invalid element!
LOGGER.log(Level.FINE, e,
() -> MessageFormat.format("Invalid json element {0}", JSON)); //$NON-NLS-1$
}
return all;
}
private static List<TraceDescription> extractDescription(String JSON) {
List<TraceDescription> all = new ArrayList<>();
try {
JSONArray schedules = new JSONArray(JSON);
for (int i = 0; i < schedules.length(); i++) {
JSONObject stop = schedules.getJSONObject(i);
String from = stop.getString("from");
String to = stop.getString("to");
String first = stop.getString("first");
String last = stop.getString("last");
//We skip the lines where from equals to
// if(from.compareTo(to) != 0){
all.add(new TraceDescription(from, to, first, last));
// }
}
} catch (JSONException e) {
// Ignoring invalid element!
// e.printStackTrace();
}
return all;
}
} }

View file

@ -0,0 +1,48 @@
package fr.u_paris.gla.project.idfm;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A representation of a stop with its connected stop and which bifurcation
* is needed to go to each connected stop
*/
public class Stop {
Map<String, BifStop> connected = new HashMap<>();
public String name;
public Stop(String name){
this.name = name;
}
/**
* Checks is stopName is connected to this one
* @param stopName
* @return True if stopName is connected to the current stop
*/
public boolean isStopConnected(String stopName) {
return connected.containsKey(stopName);
}
/**
* Add Connected stop
* @param stop connected stop with the bifurcation needed
*/
public void addConnectedStop(BifStop stop) {
connected.put(stop.stop.name, stop);
}
/**
* Return the connected stop with the name : stopName
* @param stopName
* @return the connected stop with the name : stopName
*/
public BifStop getConnectedStop(String stopName) {
return connected.get(stopName);
}
}

View file

@ -0,0 +1,71 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import java.text.MessageFormat;
import java.util.Objects;
/** A transport stop data.
*
* @author Emmanuel Bigeon */
public class StopEntry implements Comparable<StopEntry> {
public final String lname;
public final double longitude;
public final double latitude;
/** Create the stop
*
* @param lname
* @param longitude
* @param latitude */
public StopEntry(String lname, double longitude, double latitude) {
super();
this.lname = lname;
this.longitude = longitude;
this.latitude = latitude;
}
@Override
public String toString() {
return MessageFormat.format("{0} [{1}, {2}]", this.lname, this.longitude, //$NON-NLS-1$
this.latitude);
}
@Override
public int compareTo(StopEntry o) {
if (latitude < o.latitude) {
return -1;
}
if (latitude > o.latitude) {
return 1;
}
if (longitude < o.longitude) {
return -1;
}
if (longitude > o.longitude) {
return 1;
}
return lname.compareTo(o.lname);
}
@Override
public int hashCode() {
return Objects.hash(latitude, lname, longitude);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StopEntry other = (StopEntry) obj;
return Double.doubleToLongBits(latitude) == Double
.doubleToLongBits(other.latitude) && Objects.equals(lname, other.lname)
&& Double.doubleToLongBits(longitude) == Double
.doubleToLongBits(other.longitude);
}
}

View file

@ -0,0 +1,29 @@
package fr.u_paris.gla.project.idfm;
import java.util.List;
import java.util.ArrayList;
/**
* A representation of a transport description encompansing its first and last
* stop in all of its direction, the first and last schedule and all the
* bifurcation that direction takes. The description comes from the fourth column * of the stop csv file.
*/
public class TraceDescription {
public String from;
public String to;
public String first;
public String last;
List<Integer> bifurcation = new ArrayList<>();
public TraceDescription(String from,String to, String first, String last){
this.from = from;
this.to = to;
this.first = first;
this.last = last;
}
@Override
public String toString() {
return "From: " + from + ", To: " + to + ", First: " + first + ", Last: " + last + ", Bifurcation: " + bifurcation;
}
}

View file

@ -0,0 +1,71 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import java.util.ArrayList;
import java.util.List;
/**
* Representation of a transport line
*
* @author Emmanuel Bigeon
*/
public final class TraceEntry {
public final String lname;
public final String id;
public final String type;
public final String url;
private List<String> terminus = new ArrayList<>();
private List<List<StopEntry>> paths = new ArrayList<>();
List <TraceDescription> descriptions = new ArrayList<>();
/**
* Create a transport line.
*
* @param lname the name of the line
*/
public TraceEntry(String lname,String ident,String t_type, String img_url) {
super();
this.lname = lname;
this.id = ident;
this.type = t_type;
this.url = img_url;
}
// FIXME list of lists are bad practice in direct access...
/** @return the list of paths */
public List<List<StopEntry>> getPaths() {
// TODO Ne pas retourner directement la liste
return paths;
}
/** @return the list of terminus */
public List<String> getTerminus() {
return terminus;
}
public void addPath(List<StopEntry> path) {
paths.add(new ArrayList<>(path));
}
public void addTerminus(String term) {
terminus.add(term);
}
public boolean isDescriptionEmpty(){
return descriptions.isEmpty();
}
/**
* Add all the description to the current one
* @param desctipt
*/
public void addDescriptions(List<TraceDescription> desctipt){
descriptions.addAll(desctipt);
}
}

View file

@ -0,0 +1,226 @@
package fr.u_paris.gla.project.idfm;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.AbstractMap.SimpleEntry;
/**
* Representation of a line with its description and stops
*/
public class Transport {
//All the stops of the line
Map<String, Stop> stopsMap = new HashMap<>();
public String name;
public String type;
public String image_url;
//All the line descriptions (directions and schedules)
List <TraceDescription> descriptions = new ArrayList<>();
public Transport(String name,String type, String url){
this.name = name;
this.type = type;
this.image_url = url;
}
/**
* Build the bifurcation for all the descriptions
*/
public void buildBifurcation(){
// int found = 0;
for(TraceDescription d : descriptions){
// System.out.println("Debut est "+d.first);
Stop debut = stopsMap.get(d.from);
Stop fin = stopsMap.get(d.to);
if (debut != null && fin != null) {
SimpleEntry<Boolean, List<Integer>> sol = roadToLast(debut.name, fin.name, new ArrayList<String>(), new ArrayList<Integer>());
if (sol.getKey()) {
// found++;
d.bifurcation = sol.getValue();
}
}
}
// System.out.println("J'en ai trouvé "+found);
}
/**
* Build the bifurcation for all the descriptions but optimized
*/
public void buildBifurcationOptimzed() {
// int found = 0;
for (TraceDescription d : descriptions) {
Stop debut = stopsMap.get(d.from);
Stop fin = stopsMap.get(d.to);
if (debut != null && fin != null) {
Set<String> alreadyVisited = new HashSet<>();
SimpleEntry<Boolean, List<Integer>> sol = roadToLastOptimized(debut.name, fin.name, alreadyVisited, new ArrayList<Integer>());
if (sol.getKey()) {
// found++;
d.bifurcation = sol.getValue();
}
}
}
// System.out.println("J'en ai trouvé " + found);
}
/**
* Check if the stop is a terminus
* @param stop the name of a Stop
* @return True if the stop is a terminus
*/
public boolean isTerminus(String stop){
for(TraceDescription t: descriptions){
if(stop.equals(t.first) || stop.equals(t.last))
return true;
}
return false;
}
/**
* Find the road from the currentStop to the last stop
* @param currentStop the current stop we are visiting
* @param last The last stop we are trying to go to
* @param alreadyVisited All the stop we already have visisted
* @param bif All the bifurcation encountered from the first stop to the current
* one
* @return True and the bifurcation if we found our road to the last stop and
* false if we didn't
*/
public SimpleEntry<Boolean,List<Integer> > roadToLast(String currentStop, String last, List<String> alreadyVisited, List<Integer> bifurcation){
if(currentStop.equals(last)){
return new SimpleEntry<>(true,bifurcation);
}
//Checker if the current stop is the bad terminus
if(isTerminus(currentStop)){
return new SimpleEntry<>(false,null);
}
List<String> visitedCopy = new ArrayList<>(alreadyVisited);
visitedCopy.add(currentStop);
Stop current = stopsMap.get(currentStop);
List <SimpleEntry<Boolean,List<Integer>> > solutions = new ArrayList<>();
for(BifStop b: current.connected.values()){
if(!visitedCopy.contains(b.stop.name)){
List<Integer> bifCopy = new ArrayList<>(bifurcation);
if(b.bifurc!= 0)
bifCopy.add(b.bifurc);
solutions.add(roadToLast(b.stop.name, last, visitedCopy, bifCopy));
}
}
//TODo: Send a list on list of integer in case there is a lot of path for the same direction
List<Integer> bifSol = new ArrayList<>();
boolean trouve = false;
for(SimpleEntry<Boolean,List<Integer>> se: solutions){
if(se.getKey()){
trouve = true;
bifSol = se.getValue();
}
}
return new SimpleEntry<>(trouve,bifSol) ;
}
/**
* Find the road from the currentStop to the last stop
* @param currentStop the current stop we are visiting
* @param last The last stop we are trying to go to
* @param alreadyVisited All the stop we already have visisted
* @param bif All the bifurcation encountered from the first stop to the current
* one
* @return True and the bifurcation if we found our road to the last stop and
* false if we didn't
*/
public SimpleEntry<Boolean, List<Integer>> roadToLastOptimized(String currentStop, String last, Set<String> alreadyVisited, List<Integer> bifurcation) {
if (currentStop.equals(last)) {
return new SimpleEntry<>(true, bifurcation);
}
// Checker if the current stop is the bad terminus
if (isTerminus(currentStop)) {
return new SimpleEntry<>(false, null);
}
alreadyVisited.add(currentStop);
Stop current = stopsMap.get(currentStop);
List<SimpleEntry<Boolean, List<Integer>>> solutions = new ArrayList<>();
for (BifStop b : current.connected.values()) {
if (!alreadyVisited.contains(b.stop.name)) {
List<Integer> bifCopy = new ArrayList<>(bifurcation);
if (b.bifurc != 0) {
bifCopy.add(b.bifurc);
}
solutions.add(roadToLastOptimized(b.stop.name, last, alreadyVisited, bifCopy));
}
}
// Todo: Send a list on list of integer in case there is a lot of path for the same direction
List<Integer> bifSol = new ArrayList<>();
boolean trouve = false;
for (SimpleEntry<Boolean, List<Integer>> se : solutions) {
if (se.getKey()) {
trouve = true;
bifSol = se.getValue();
break; // Exit loop if a solution is found
}
}
alreadyVisited.remove(currentStop); // Remove current stop from visited after processing
return new SimpleEntry<>(trouve, bifSol);
}
/**
* Connect 2 stops (start, end) and add them in stopMap if they are not already in
* @param start The name of a stop
* @param end The name of the stop connected to the start
* @param bifurcation The bifurcation taken to go from start stop to end stop
*/
public void addStop(String start, String end, int bifurcation){
Stop startStop = stopsMap.computeIfAbsent(start, Stop::new);
Stop endStop = stopsMap.computeIfAbsent(end, Stop::new);
BifStop connectedStop = new BifStop(bifurcation, endStop);
startStop.addConnectedStop(connectedStop);
}
/**
* Print every stops of the line and its connections
*/
public void printAllConnectionStops() {
System.out.println("Affichage des couples (stop, next du stop):");
for (Map.Entry<String, Stop> entry : stopsMap.entrySet()) {
Stop stop = entry.getValue();
System.out.println("Stop: " + stop.name);
System.out.println("Next:");
for (BifStop nextEntry : stop.connected.values()) {
System.out.println(nextEntry.bifurc + ": " + nextEntry.stop.name);
}
}
}
/**
* Add all the description to the current one
* @param desctipt
*/
public void addDescriptions(List<TraceDescription> desctipt){
descriptions.addAll(desctipt);
}
}

View file

@ -0,0 +1,72 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import fr.u_paris.gla.project.utils.GPS;
/** A transport stop with unidentified name and potential candidates as to their
* real value.
*
* @author Emmanuel Bigeon */
public final class UnidentifiedStopEntry extends StopEntry {
private List<StopEntry> candidates = new ArrayList<>();
/** Create the stop
*
* @param longitude
* @param latitude */
public UnidentifiedStopEntry(double longitude, double latitude) {
super("Unidentified", longitude, latitude); //$NON-NLS-1$
}
@Override
public String toString() {
return MessageFormat.format("UnidentifiedStop [candidates={0}]", this.candidates); //$NON-NLS-1$
}
/** Get the most likely choice for the stop
*
* @return the most likely candidate */
public StopEntry resolve() {
if (candidates.isEmpty()) {
return null;
}
if (candidates.size() == 1) {
return candidates.get(0);
}
Collections.sort(candidates, (Comparator<? super StopEntry>) (StopEntry s1,
StopEntry s2) -> (int) Math.signum((GPS.distance(latitude, longitude,
s1.latitude, s1.longitude)
- GPS.distance(latitude, longitude, s2.latitude, s2.longitude))));
return candidates.get(0);
}
/** Add a candidate.
*
* @param entry the candidate */
public void addCandidate(StopEntry entry) {
candidates.add(entry);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
UnidentifiedStopEntry other = (UnidentifiedStopEntry) obj;
return Objects.equals(candidates, other.candidates);
}
}

View file

@ -0,0 +1,24 @@
/**
*
*/
package fr.u_paris.gla.project.io;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.List;
/**
* A tool class for the Image format.
*/
public final class ImageFormat {
public static final int NUMBER_COLUMNS = 2;
public static final int LINE_INDEX = 0;
public static final int IMAGE_URL_INDEX = 1;
/** Hidden constructor for tool class */
private ImageFormat() {
// Tool class
}
}

View file

@ -14,8 +14,10 @@ import java.util.Locale;
* *
* @author Emmanuel Bigeon */ * @author Emmanuel Bigeon */
public final class NetworkFormat { public final class NetworkFormat {
/** The amount of columns in the CSV file */
public static final int NUMBER_COLUMNS = 8; public static final int NUMBER_COLUMNS = 8;
/** The amount of decimal places in the GPS' coordinates */
public static final int GPS_PRECISION = 18; public static final int GPS_PRECISION = 18;
/** The index of the line name in the network format */ /** The index of the line name in the network format */
@ -33,10 +35,10 @@ public final class NetworkFormat {
private static final DateTimeFormatter DURATION_FORMATTER = DateTimeFormatter private static final DateTimeFormatter DURATION_FORMATTER = DateTimeFormatter
.ofPattern("HH:mm:ss"); .ofPattern("HH:mm:ss");
private static final NumberFormat DURATION_SECONDS_FORMATTER = NumberFormat private static final NumberFormat DURATION_INDIVIDUAL_FORMATTER = NumberFormat
.getIntegerInstance(Locale.ENGLISH); .getIntegerInstance(Locale.ENGLISH);
static { static {
DURATION_SECONDS_FORMATTER.setMinimumIntegerDigits(2); DURATION_INDIVIDUAL_FORMATTER.setMinimumIntegerDigits(2);
} }
private static final Temporal ZERO = LocalTime.parse("00:00:00"); private static final Temporal ZERO = LocalTime.parse("00:00:00");
@ -45,19 +47,27 @@ public final class NetworkFormat {
// Tool class // Tool class
} }
/** Convert a {@link java.lang.String} into a {@link java.time.Duration}
* @param duration the {@link java.lang.String}
* @return the {@link java.time.Duration} object
*/
public static Duration parseDuration(String duration) { public static Duration parseDuration(String duration) {
LocalTime time = LocalTime.parse("00:" + duration, DURATION_FORMATTER); LocalTime time = LocalTime.parse("00:" + duration, DURATION_FORMATTER);
return Duration.between(time, ZERO); return Duration.between(time, ZERO);
} }
/** Format a {@link java.time.Duration} into a {@link java.lang.String}
* @param duration an object of type {@link java.time.Duration}
* @return a String that depicts the duration in format MM:SS
*/
public static String formatDuration(Duration duration) { public static String formatDuration(Duration duration) {
return Long.toString(duration.toMinutes()) + ":" return DURATION_INDIVIDUAL_FORMATTER.format(duration.toMinutes()) + ":"
+ DURATION_SECONDS_FORMATTER.format(duration.toSecondsPart()); + DURATION_INDIVIDUAL_FORMATTER.format(duration.toSecondsPart());
} }
/** Get a formatter for the numbers in a GPS coordinate pair /** Get a formatter for the numbers in a GPS coordinate pair
* *
* @return the formatter for numbers in a GPS coordinate pair */ * @return the {@link java.text.NumberFormat} formatter for numbers in a GPS coordinate pair */
public static NumberFormat getGPSFormatter() { public static NumberFormat getGPSFormatter() {
NumberFormat instance = NumberFormat.getNumberInstance(Locale.ENGLISH); NumberFormat instance = NumberFormat.getNumberInstance(Locale.ENGLISH);
instance.setMaximumFractionDigits(GPS_PRECISION); instance.setMaximumFractionDigits(GPS_PRECISION);

View file

@ -5,15 +5,25 @@ package fr.u_paris.gla.project.io;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle; import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** A tool class for the schedule format. /**
* A tool class for the schedule format.
* *
* @author Emmanuel Bigeon */ * @author Emmanuel Bigeon
*/
public final class ScheduleFormat { public final class ScheduleFormat {
/** The amount of columns in the CSV file */
public static final int NUMBER_COLUMNS = 4;
/** The index of the line name in the schedule format */
public static final int LINE_INDEX = 0; public static final int LINE_INDEX = 0;
/** The index of the trip sequence in the schedule format */
public static final int TRIP_SEQUENCE_INDEX = 1; public static final int TRIP_SEQUENCE_INDEX = 1;
/** The index of the terminus name in the schedule format */
public static final int TERMINUS_INDEX = 2; public static final int TERMINUS_INDEX = 2;
/** The index of the time in the schedule format */
public static final int TIME_INDEX = 3; public static final int TIME_INDEX = 3;
/** Hidden constructor for tool class */ /** Hidden constructor for tool class */
@ -21,16 +31,24 @@ public final class ScheduleFormat {
// Tool class // Tool class
} }
/** Read a trip sequence from its string representation /**
* Read a trip sequence from its string representation
* *
* @param representation the representation * @param representation the representation
* @return the sequence of branching */ * @return the sequence of branching
*/
public static List<Integer> getTripSequence(String representation) { public static List<Integer> getTripSequence(String representation) {
// TODO Read a trip sequence from a string
throw new RuntimeException("Not implemented yet"); List<Integer> l = new ArrayList<>();
for(String s : representation.split(","))
l.add(Integer.parseInt(s));
return l;
} }
/** Create a {@link java.time.format.DateTimeFormatter} object used to format Dates
* @return the formatter
*/
public static DateTimeFormatter getTimeFormatter() { public static DateTimeFormatter getTimeFormatter() {
return DateTimeFormatter.ofPattern("HH:mm").withResolverStyle(ResolverStyle.LENIENT); return DateTimeFormatter.ofPattern("HH:mm").withResolverStyle(ResolverStyle.LENIENT);
} }

View file

@ -1,5 +1,8 @@
package fr.u_paris.gla.project.itinerary; package fr.u_paris.gla.project.itinerary;
import java.util.ArrayList;
import java.util.Collections;
public class Connection{ public class Connection{
// Destination of the connection between the two stops // Destination of the connection between the two stops
private final Stop stop; private final Stop stop;
@ -7,15 +10,27 @@ public class Connection{
// The line used for this connection // The line used for this connection
private final String lineName; private final String lineName;
//Distance between the two stops
private final double distance; private final double distance;
//Travel time between the two stops
private final int time; private final int time;
public Connection(Stop stop, String lineName, double distance, int time){ private final ArrayList<Integer> schedules;
private final int bifurcation;
public Connection(Stop stop, String lineName, double distance, int time, int bifurcation){
this.stop = stop; this.stop = stop;
this.lineName=lineName; this.lineName=lineName;
this.distance = distance; this.distance = distance;
this.time = time; this.time = time;
this.schedules = new ArrayList<>();
this.bifurcation = bifurcation;
}
public Connection(Stop stop, String lineName, double distance, int time){
this(stop, lineName, distance, time, 0);
} }
@ -34,4 +49,51 @@ public class Connection{
public Stop getStop() { public Stop getStop() {
return stop; return stop;
} }
public void addSchedule(int hours) {
this.schedules.add(hours);
}
public void sortSchedule() {
Collections.sort(this.schedules);
}
public ArrayList<Integer> getSchedules() {
return this.schedules;
}
public int getBifurcation() {
return this.bifurcation;
}
public double getCost() {
return this.time;
}
public double getNextTime(double currentTime) {
if(this.schedules.size() == 0) {
return currentTime;
}
int i = 0;
while(i < this.schedules.size() && this.schedules.get(i) < currentTime) {
i++;
}
if(i < this.schedules.size()) {
return this.schedules.get(i);
}
return this.schedules.get(0);
}
public double getCost(double currentTime) {
if(this.schedules.size() == 0) {
if(this.lineName.equals("WALK")) {
return this.time;
}
return this.time + 900;
}
double nextTime = this.getNextTime(currentTime);
if(nextTime < currentTime) { nextTime += 86400;}
return nextTime - currentTime + this.time;
}
} }

View file

@ -2,16 +2,24 @@ package fr.u_paris.gla.project.itinerary;
import java.util.*; import java.util.*;
public class Finder { public class Finder {
private Graph graph; private Graph graph;
public Finder(Graph graph) { public Finder(Graph graph) {
this.graph = graph; this.graph = graph;
} }
public List<Stop> findPath(Stop startNode, Stop goalNode) { /**
* return a path from startNode to goalNode using A* algorithm
* @param startNode
* @param goalNode
*/
public List<Path> findPath(Stop startNode, Stop goalNode, double startTime) {
PriorityQueue<Stop> openSet = new PriorityQueue<>(Comparator.comparingDouble(GraphNode::getF)); PriorityQueue<Stop> openSet = new PriorityQueue<>(Comparator.comparingDouble(GraphNode::getF));
HashSet<Stop> closedSet = new HashSet<>(); HashSet<Stop> closedSet = new HashSet<>();
HashMap<Stop, Stop> cameFrom = new HashMap<>(); HashMap<Stop, Path> cameFrom = new HashMap<>();
HashMap<Stop, Double> gScore = new HashMap<>(); HashMap<Stop, Double> gScore = new HashMap<>();
HashMap<Stop, Double> fScore = new HashMap<>(); HashMap<Stop, Double> fScore = new HashMap<>();
@ -21,16 +29,15 @@ public class Finder {
fScore.put(node, Double.POSITIVE_INFINITY); fScore.put(node, Double.POSITIVE_INFINITY);
} }
// The cost of going from start to start is zero // The cost of going from start to start is the start time
gScore.put(startNode, 0.0); gScore.put(startNode, startTime);
// For the first node, fScore = gScore + heuristic // For the first node, fScore = gScore + heuristic
fScore.put(startNode, startNode.getHeuristicCost(goalNode)); fScore.put(startNode, startNode.getHeuristicCost(goalNode));
openSet.add(startNode); openSet.add(startNode);
while (!openSet.isEmpty()) { while (!openSet.isEmpty()) {
Stop current = openSet.poll(); Stop current = openSet.poll();
//System.out.println(current); double currentTime = gScore.get(current);
//System.out.println(graph.getConnections(current));
if (current.equals(goalNode)) { if (current.equals(goalNode)) {
return reconstructPath(cameFrom, current); return reconstructPath(cameFrom, current);
@ -42,25 +49,30 @@ public class Finder {
continue; continue;
} }
for (Connection connection : graph.getConnections(current) ) { for (Connection connection : graph.getConnections(current)) {
Stop neighbor = connection.getStop(); Stop neighbor = connection.getStop();
if (closedSet.contains(neighbor)) { if (closedSet.contains(neighbor)) {
continue; // Ignore the neighbor which is already evaluated. continue; // Ignore the neighbor which is already evaluated.
} }
double tentativeGScore = gScore.get(current) + connection.getDistance(); double tentativeGScore = currentTime + connection.getCost(currentTime);
if (!openSet.contains(neighbor)) { if (tentativeGScore >= gScore.get(neighbor)) {
openSet.add(neighbor);
} else if (tentativeGScore >= gScore.get(neighbor)) {
continue; // This is not a better path. continue; // This is not a better path.
} }
// This path is the best until now. Record it! // This path is the best until now. Record it!
cameFrom.put(neighbor, current); cameFrom.put(neighbor, new Path(current, connection, currentTime));
gScore.put(neighbor, tentativeGScore); gScore.put(neighbor, tentativeGScore);
fScore.put(neighbor, tentativeGScore + neighbor.getHeuristicCost(goalNode)); fScore.put(neighbor, tentativeGScore + neighbor.getHeuristicCost(goalNode));
if (!openSet.contains(neighbor)) {
neighbor.setF(fScore.get(neighbor)); neighbor.setF(fScore.get(neighbor));
openSet.add(neighbor);
}
else {
updatePriority(openSet, neighbor, fScore.get(neighbor));
}
} }
} }
@ -68,24 +80,37 @@ public class Finder {
return null; return null;
} }
private List<Stop> reconstructPath(HashMap<Stop, Stop> cameFrom, Stop current) { /**
List<Stop> totalPath = new ArrayList<>(); * Once we found the destination we reconstruct the path
totalPath.add(current); * @param cameFrom
* @param current
* @return path
*/
private List<Path> reconstructPath(HashMap<Stop, Path> cameFrom, Stop current) {
List<Path> totalPath = new ArrayList<>();
totalPath.add(cameFrom.get(current));
while (cameFrom.containsKey(current)) { while (cameFrom.containsKey(current)) {
current = cameFrom.get(current); current = cameFrom.get(current).getCurrentStop();
totalPath.add(0, current); // Add to the beginning of the list to maintain order if(cameFrom.get(current) != null) {
totalPath.add(0, cameFrom.get(current)); // Add to the beginning of the list to maintain order
}
} }
return totalPath; return totalPath;
} }
//TODO: /**
public List<Stop> findPath(double longitude, double latitude){ * Update the priority queue
return null; * @param openSet
* @param node
* @param newF
*/
public void updatePriority(PriorityQueue<Stop> openSet, Stop node, double newF) {
openSet.remove(node);
node.setF(newF);
openSet.add(node);
} }
} }

View file

@ -38,6 +38,11 @@ public class ItineraryCalculator {
private static final double ERROR_MARGIN = 1.; private static final double ERROR_MARGIN = 1.;
//The time public vehicles spend at each stop in seconds.
private static final int STOP_TIME = 30;
//Walking speed in m/s
private static final double WALK_SPEED = 1.;
/** /**
* Returns the coordinates from a String to a double array: * Returns the coordinates from a String to a double array:
@ -59,26 +64,40 @@ public class ItineraryCalculator {
* @param lineId the line the stop is on * @param lineId the line the stop is on
* @return the Stop object * @return the Stop object
*/ */
private static Stop getOrCreateStop(HashSet<Stop> nodes, HashMap<String, ArrayList<Stop>> tmp, String name, String gps, String lineId) { private static Stop getOrCreateStop(HashSet<Stop> nodes, HashMap<String, ArrayList<Stop>> tmp, String name, String gps, String lineId, HashMap<Stop, Set<Connection>> connections) {
ArrayList<Stop> stopList = tmp.get(name); ArrayList<Stop> stopList = tmp.get(name);
double[] coords = getCoords(gps);
// First we search by name, and then compare the coordinates since multiple stations can have the same name. A margin of error is necessary since stops can have multiple GPS coordinates // First we search by name, and then compare the coordinates since multiple stations can have the same name. A margin of error is necessary since stops can have multiple GPS coordinates
ArrayList<Stop> lineChanges = new ArrayList<>();
if (stopList != null) { if (stopList != null) {
for(Stop stop : stopList) { for(Stop stop : stopList) {
double[] coords = getCoords(gps);
double dist = GPS.distance(coords[0], coords[1], stop.getLatitude(), stop.getLongitude()); double dist = GPS.distance(coords[0], coords[1], stop.getLatitude(), stop.getLongitude());
if(dist < ERROR_MARGIN) { if(dist == 0) {
stop.addLine(lineId); stop.addLine(lineId);
return stop; return stop;
} }
if(dist < ERROR_MARGIN) {
lineChanges.add(stop);
}
} }
} }
double[] coords = getCoords(gps);
Stop newStop = new Stop(lineId, name, coords[0], coords[1]); Stop newStop = new Stop(lineId, name, coords[0], coords[1]);
nodes.add(newStop); nodes.add(newStop);
stopList = stopList == null ? new ArrayList<>() : stopList; stopList = stopList == null ? new ArrayList<>() : stopList;
stopList.add(newStop); stopList.add(newStop);
tmp.put(name, stopList); tmp.put(name, stopList);
for(Stop s : lineChanges) {
double dist = GPS.distance(coords[0], coords[1], s.getLatitude(), s.getLongitude());
int time = (int) (dist*1000/WALK_SPEED);
Connection c1 = new Connection(s, "WALK", dist, time);
connections.computeIfAbsent(newStop, k -> new HashSet<>()).add(c1);
Connection c2 = new Connection(newStop, "WALK", dist, time);
connections.computeIfAbsent(s, k -> new HashSet<>()).add(c2);
}
return newStop; return newStop;
} }
@ -90,22 +109,107 @@ public class ItineraryCalculator {
* @param connections * @param connections
*/ */
private static void addLine(String[] line, HashSet<Stop> nodes, HashMap<String, ArrayList<Stop>> tmp, HashMap<Stop, Set<Connection>> connections) { private static void addLine(String[] line, HashSet<Stop> nodes, HashMap<String, ArrayList<Stop>> tmp, HashMap<Stop, Set<Connection>> connections) {
Stop fromStop = getOrCreateStop(nodes, tmp, line[IDFM_TRACE_FROM_INDEX], line[IDFM_TRACE_FROM_GPS_INDEX], line[IDFM_TRACE_ID_INDEX]); Stop fromStop = getOrCreateStop(nodes, tmp, line[IDFM_TRACE_FROM_INDEX], line[IDFM_TRACE_FROM_GPS_INDEX], line[IDFM_TRACE_ID_INDEX], connections);
Stop toStop = getOrCreateStop(nodes, tmp, line[IDFM_TRACE_TO_INDEX], line[IDFM_TRACE_TO_GPS_INDEX], line[IDFM_TRACE_ID_INDEX]); Stop toStop = getOrCreateStop(nodes, tmp, line[IDFM_TRACE_TO_INDEX], line[IDFM_TRACE_TO_GPS_INDEX], line[IDFM_TRACE_ID_INDEX], connections);
String[] timeString = line[IDFM_TRACE_TIME_INDEX].split(":"); String[] timeString = line[IDFM_TRACE_TIME_INDEX].split(":");
int time = Integer.parseInt(timeString[0]) * 60 + Integer.parseInt(timeString[1]); String time0WithoutComma = timeString[0].replace(",", "");
int time = Integer.parseInt(time0WithoutComma) * 60 + Integer.parseInt(timeString[1]);
Connection connection = new Connection(toStop, line[IDFM_TRACE_ID_INDEX], Double.parseDouble(line[IDFM_TRACE_DISTANCE_INDEX]), time); Connection connection = new Connection(toStop, line[IDFM_TRACE_ID_INDEX], Double.parseDouble(line[IDFM_TRACE_DISTANCE_INDEX]), time, Integer.parseInt(line[IDFM_TRACE_DERIV_INDEX]));
connections.computeIfAbsent(fromStop, k -> new HashSet<>()).add(connection); connections.computeIfAbsent(fromStop, k -> new HashSet<>()).add(connection);
} }
private static void addScheduleRec(Stop current, Stop previous, String line, ArrayList<Integer> bifurcations, int time, HashMap<String, ArrayList<Stop>> stopsHashSet, HashMap<Stop, Set<Connection>> connections, HashSet<Stop> processed){
time = time%86400;
//If the stop has already been processed, it is not reprocessed.
if(processed.contains(current)) {return;}
processed.add(current);
Set<Connection> neighborhood = connections.get(current);
if(neighborhood == null) {return;}
ArrayList<Connection> directions = new ArrayList<>();
for(Connection n : neighborhood) {
if(n.getLineName().equals(line)
&& (previous == null || !n.getStop().getName().equals(previous.getName()))
) {
directions.add(n);
}
}
if(directions.size() == 0) {return;}
Stop next_stop = null;
if(directions.size() > 1) {
int bifurcation = bifurcations.size() == 0 ? 0 : bifurcations.get(0);
if(bifurcations.size() > 0) {bifurcations.remove(0);}
for(Connection d : directions) {
if(d.getBifurcation() == bifurcation) {
next_stop = d.getStop();
break;
}
}
if(next_stop == null) {
return;
}
}
else {
next_stop = directions.get(0).getStop();
if(directions.get(0).getBifurcation() != 0) {
if(bifurcations.size() > 0 && directions.get(0).getBifurcation() == bifurcations.get(0)){
bifurcations.remove(0);
}
}
}
for(Connection n : directions) {
if(n.getStop() == next_stop) {
n.addSchedule(time);
time += n.getTime() + STOP_TIME;
addScheduleRec(next_stop, current, line, bifurcations, time, stopsHashSet, connections, processed);
return;
}
}
}
private static void addSchedule(String[] input, HashMap<String, ArrayList<Stop>> stopsHashSet, HashMap<Stop, Set<Connection>> connections) {
String line = input[0];
ArrayList<Integer> bifurcations = new ArrayList<>();
if(!input[1].equals("[]")) {
String[] b = input[1].substring(1, input[1].length()-1).split(",");
bifurcations = new ArrayList<>();
for(String n : b){
bifurcations.add(Integer.parseInt(n.trim()));
}
}
String name = input[2];
String[] timeString = input[3].split(":");
int time = Integer.parseInt(timeString[0]) * 3600 + Integer.parseInt(timeString[1])*60;
ArrayList<Stop> stops = stopsHashSet.get(name);
if(stops == null) {return;}
for(Stop stop : stops) {
if(stop.getLines().contains(line)) {
addScheduleRec(stop, null, line, bifurcations, time, stopsHashSet, connections, new HashSet<>());
}
}
}
public static void main(String[] args){ public static void main(String[] args){
if (args.length != 0) { if (args.length != 0) {
LOGGER.severe("Invalid command line. Target file names are in the main file for now."); LOGGER.severe("Invalid command line. Target file names are in the main file for now.");
return; return;
} }
//IDFMNetworkExtractor.builFiles();
try { try {
HashSet<Stop> nodes = new HashSet<>(); HashSet<Stop> nodes = new HashSet<>();
@ -114,6 +218,15 @@ public class ItineraryCalculator {
CSVTools.readCSVFromFile(TRACE_FILE_NAME, CSVTools.readCSVFromFile(TRACE_FILE_NAME,
(String[] line) -> addLine(line, nodes, tmp, connections)); (String[] line) -> addLine(line, nodes, tmp, connections));
CSVTools.readCSVFromFile(HOURS_FILE_NAME,
(String[] line) -> addSchedule(line, tmp, connections));
for(Set<Connection> set : connections.values()) {
for(Connection c : set) {
c.sortSchedule();
}
}
Stop porteivry = tmp.get("Porte d'Ivry").get(0); Stop porteivry = tmp.get("Porte d'Ivry").get(0);
Stop repu = tmp.get("République").get(0); Stop repu = tmp.get("République").get(0);
@ -131,10 +244,9 @@ public class ItineraryCalculator {
//System.out.println(graph.getConnections(porteivry)); //System.out.println(graph.getConnections(porteivry));
Finder finder = new Finder(graph); Finder finder = new Finder(graph);
List<Stop> res = finder.findPath(porteivry, chatelet); List<Path> res = finder.findPath(porteivry, chatelet, 43200);
for (Path element : res) {
for (Stop element : res) { System.out.println(element.getCurrentStop());
System.out.println(element);
} }
} catch (IOException e) { } catch (IOException e) {

View file

@ -0,0 +1,50 @@
package fr.u_paris.gla.project.itinerary;
public class Path {
private Stop current;
private Stop next;
private double startTime;
private double travelTime;
private double distance;
private String line;
private Connection connection;
public Path(Stop current, Connection connection, double startTime) {
this.current = current;
this.connection = connection;
this.next = connection.getStop();
this.startTime = startTime;
this.travelTime = connection.getTime();
this.line = connection.getLineName();
}
public Connection getConnection(){
return this.connection;
}
public Stop getCurrentStop() {
return this.current;
}
public Stop getNextStop() {
return next;
}
public double getStartTime() {
return this.startTime;
}
public double travelTime() {
return this.travelTime;
}
public String getLine() {
return this.line;
}
}

View file

@ -9,13 +9,9 @@ public class Stop implements GraphNode {
private final int id; private final int id;
// The set of all the lines the stop is on // The set of all the lines the stop is on
private final Set<String> lines; private final Set<String> lines;
private final String name; private final String name;
private final double latitude; private final double latitude;
@ -90,9 +86,5 @@ public class Stop implements GraphNode {
lines.add(s); lines.add(s);
} }
public Set<String> getLines() { public Set<String> getLines() { return this.lines; }
return lines;
}
} }

View file

@ -0,0 +1,54 @@
package fr.u_paris.gla.project.utils;
import fr.u_paris.gla.project.idfm.IDFMNetworkExtractor;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ApiUtils {
/** The logger for information on the process */
private static final Logger LOGGER = Logger
.getLogger(IDFMNetworkExtractor.class.getName());
// OpenStreetMap API URL
private static final String OSM_URL = "https://nominatim.openstreetmap.org/search";
public static double[] getGPSLocation(String term) {
try {
String urlString = String.format("%s?q=%s&format=json", OSM_URL, URLEncoder.encode(term, StandardCharsets.UTF_8));
HttpURLConnection connection = (HttpURLConnection) new URL(urlString).openConnection();
connection.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
JSONArray jsonArray = new JSONArray(response.toString());
if (!jsonArray.isEmpty()) {
JSONObject firstResult = jsonArray.getJSONObject(0);
double lat = firstResult.getDouble("lat");
double lon = firstResult.getDouble("lon");
return new double[]{lat, lon};
}
}
catch (IOException e) {
LOGGER.log(Level.SEVERE, e,
() -> "Error accessing the API");
}
return new double[]{0, 0};
}
}

View file

@ -28,6 +28,11 @@ public final class CSVTools {
// Tool class // Tool class
} }
/** get a CSV file from a URL, download and parse it, and keep values in memory
* @param is the address of the CSV file
* @param contentLineConsumer the variable used to store the data
* @throws IOException if it's impossible to download the file
*/
private static void readCSVFromInputStream(InputStream is, Consumer<String[]> contentLineConsumer) private static void readCSVFromInputStream(InputStream is, Consumer<String[]> contentLineConsumer)
throws IOException { throws IOException {
ICSVParser parser = new CSVParserBuilder().withSeparator(';').build(); ICSVParser parser = new CSVParserBuilder().withSeparator(';').build();
@ -47,6 +52,12 @@ public final class CSVTools {
} }
} }
/** get a CSV file from a file and parse it, keeping values in memory
* @param filename the saved file's name and path
* @param contentLineConsumer the variable used to store the data
* @throws IOException if it's impossible to read the file
*/
public static void readCSVFromFile(String filename, Consumer<String[]> contentLineConsumer) public static void readCSVFromFile(String filename, Consumer<String[]> contentLineConsumer)
throws IOException { throws IOException {
File file = new File(filename); File file = new File(filename);

View file

@ -32,14 +32,16 @@ public final class GPS {
* @param longitude1 the longitude of the first position * @param longitude1 the longitude of the first position
* @param latitude2 the latitude of the second position * @param latitude2 the latitude of the second position
* @param longitude2 the longitude of the second position * @param longitude2 the longitude of the second position
* @return the flying distance */ * @return the flying distance in km*/
public static double distance(double latitude1, double longitude1, double latitude2, public static double distance(double latitude1, double longitude1, double latitude2,
double longitude2) { double longitude2) {
double deltaLatitude = degreeToRadian(latitude2 - latitude1); latitude1 = degreeToRadian(latitude1);
latitude2 = degreeToRadian(latitude2);
double deltaLatitude = latitude2 - latitude1;
double deltaLongitude = degreeToRadian(longitude2 - longitude1); double deltaLongitude = degreeToRadian(longitude2 - longitude1);
double a = Math.pow(Math.sin(deltaLatitude / 2), 2) double a = Math.pow(Math.sin(deltaLatitude / 2), 2)
+ Math.pow(Math.sin(deltaLongitude), 2) * Math.cos(latitude1) + Math.pow(Math.sin(deltaLongitude / 2), 2) * Math.cos(latitude1)
* Math.cos(latitude2); * Math.cos(latitude2);
return EARTH_RADIUS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return 2 * EARTH_RADIUS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
} }
} }

View file

@ -1,3 +1,3 @@
app.name=${name} app.name=${name}
app.version=${version} app.version=${version}
app.team=No team app.team=Team 3

View file

@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test;
class AppTest { class AppTest {
/** Rigorous Test :-) */ /** Rigorous Test :-) */
@Test @Test
void testPlaceholder() { public void testPlaceholder() {
assertTrue(true, "It should be true that true is true..."); assertTrue(true, "It should be true that true is true...");
} }
} }

View file

@ -0,0 +1,122 @@
package fr.u_paris.gla.project.idfm;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import fr.u_paris.gla.project.io.NetworkFormat;
import fr.u_paris.gla.project.utils.GPS;
public class CSVStreamProviderTest {
//Test de hasNext, pour le cas ou il y'a un trace et cas ou il n'y en a pas
@Test
public void testHasNext() {
// Scénario sans Trace
CSVStreamProvider providerWithoutTrace = new CSVStreamProvider(Collections.emptyIterator(),null);
assertFalse(providerWithoutTrace.hasNext(), "hasNext should return false when no traces are provided");
// Scénario avec Trace
StopEntry stop1 = new StopEntry("Stop1", 2.3522, 48.8566);
StopEntry stop2 = new StopEntry("Stop2", 2.295, 48.8738);
List<StopEntry> path = Arrays.asList(stop1, stop2);
TraceEntry trace = new TraceEntry("Ligne1","IDFM:03434","Bus", "dummy_url");
trace.addPath(path);
CSVStreamProvider providerWithTrace = new CSVStreamProvider(Arrays.asList(trace).iterator(),null);
assertTrue(providerWithTrace.hasNext(), "hasNext should return true when traces are provided");
}
//Test de la methode next()
@Test
public void testNext() {
// Initialisation des données d'exemple directement dans le test
StopEntry start = new StopEntry("Début", 2.3522, 48.8566); // Paris
StopEntry end = new StopEntry("Fin", 2.295, 48.8738); // Proche de Paris
TraceEntry traceEntry = new TraceEntry("Ligne1","IDFM:03434","Bus", "dummy_url");
traceEntry.addPath(Arrays.asList(start, end)); // Ajout d'un chemin à la trace
CSVStreamProvider provider = new CSVStreamProvider(Collections.singletonList(traceEntry).iterator(),null);
assertTrue(provider.hasNext(), "Doit avoir un prochain élément");
String[] result = provider.next();
assertNotNull(result, "Le résultat ne doit pas être null");
// Vérifications spécifiques sur le format des données de sortie
assertEquals(start.lname, result[NetworkFormat.START_INDEX], "Le nom de l'arrêt de départ doit correspondre");
assertEquals(end.lname, result[NetworkFormat.STOP_INDEX], "Le nom de l'arrêt d'arrivée doit correspondre");
// Calcul et vérification de la distance attendue
double expectedDistance = GPS.distance(start.latitude, start.longitude, end.latitude, end.longitude);
String expectedDistanceFormatted = NumberFormat.getInstance(Locale.ENGLISH).format(expectedDistance);
assertEquals(expectedDistanceFormatted, result[NetworkFormat.DISTANCE_INDEX], "La distance doit correspondre");
}
//Test de la methode private fillStation avec la réflexion
@Test
public void testFillStation() throws Exception {
// Initialisation des données de test
StopEntry stop = new StopEntry("StopName", 2.3522, 48.8566); // Exemple de coordonnées pour Paris
String[] nextLine = new String[NetworkFormat.NUMBER_COLUMNS];
// Accès à la méthode fillStation via la réflexion
Method fillStationMethod = CSVStreamProvider.class.getDeclaredMethod("fillStation", StopEntry.class, String[].class, int.class);
fillStationMethod.setAccessible(true);
// Invocation de la méthode fillStation
fillStationMethod.invoke(null, stop, nextLine, NetworkFormat.START_INDEX);
// Format attendu pour la latitude et la longitude
NumberFormat gpsFormatter = NetworkFormat.getGPSFormatter();
String expectedLatitudeLongitude = MessageFormat.format("{0}, {1}",
gpsFormatter.format(stop.latitude),
gpsFormatter.format(stop.longitude));
// Vérifications
assertEquals(stop.lname, nextLine[NetworkFormat.START_INDEX], "Le nom de l'arrêt doit correspondre.");
assertEquals(expectedLatitudeLongitude, nextLine[NetworkFormat.START_INDEX + 1], "Les coordonnées GPS doivent correspondre.");
}
//Test de la méthode static private distanceToTime()
@Test
public void testDistanceToTime() throws Exception {
// Valeurs fictives pour TWO_ACCELERATION_DISTANCE et MAX_SPEED
final double TWO_ACCELERATION_DISTANCE = 0.2; // Par exemple
final double MAX_SPEED = 5.0; // Par exemple
// Exemple de distance à tester
double distanceExample = 1.0; // 1 km
// Calcul attendu basé sur la formule fournie
double expected = Math.max(0, distanceExample - TWO_ACCELERATION_DISTANCE) / MAX_SPEED
+ Math.pow(Math.min(distanceExample, TWO_ACCELERATION_DISTANCE) / MAX_SPEED, 2);
// Accès à la méthode distanceToTime via la réflexion
Method method = CSVStreamProvider.class.getDeclaredMethod("distanceToTime", double.class);
method.setAccessible(true);
// Invocation de la méthode distanceToTime et stockage du résultat
double result = (Double) method.invoke(null, distanceExample);
// Assertion pour vérifier si le résultat est conforme à l'attendu
assertEquals(expected, result, "Le calcul du temps à partir de la distance devrait être conforme à l'attendu.");
}
}

View file

@ -0,0 +1,59 @@
package fr.u_paris.gla.project.idfm;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class IDFMNetworkExtractorTest {
//Test de clenLine de ma classe IDFMNetworkExtractor
@Test
public void testCleanLine() throws Exception {
// Création d'un arrêt non identifié avec des coordonnées spécifiques
UnidentifiedStopEntry unidentifiedStop = new UnidentifiedStopEntry(2.3522, 48.8566); // Coordonnées pour Paris
// Création d'un arrêt candidat proche de l'arrêt non identifié
StopEntry closeCandidate = new StopEntry("Proche Candidat", 2.3523, 48.8567); // Coordonnées proches de Paris
// Ajout du candidat à l'arrêt non identifié
unidentifiedStop.addCandidate(closeCandidate);
// Liste des chemins contenant l'arrêt non identifié
List<List<StopEntry>> paths = new ArrayList<>(Arrays.asList(Arrays.asList(unidentifiedStop)));
// Accès à la méthode cleanLine via la réflexion
Method cleanLineMethod = IDFMNetworkExtractor.class.getDeclaredMethod("cleanLine", List.class);
cleanLineMethod.setAccessible(true);
// Invocation de la méthode cleanLine
boolean result = (Boolean) cleanLineMethod.invoke(null, paths);
// Vérifications
assertTrue(result, "La méthode cleanLine devrait retourner true si le nettoyage a réussi.");
assertNotEquals("Unidentified", paths.get(0).get(0).lname, "L'arrêt non identifié devrait avoir été résolu.");
assertEquals(closeCandidate.lname, paths.get(0).get(0).lname, "L'arrêt devrait être résolu au candidat le plus proche.");
}
@Test
public void testAddCandidate() throws Exception {
UnidentifiedStopEntry unidentifiedStop = new UnidentifiedStopEntry(2.3522, 48.8566); // Coordonnées pour Paris
List<StopEntry> path = new ArrayList<>(Arrays.asList(unidentifiedStop));
TraceEntry trace = new TraceEntry("Ligne1","IDFM:03434","Bus", "dummy_url");
trace.addPath(path);
StopEntry candidate = new StopEntry("Proche Candidat", 2.3523, 48.8567); // Coordonnées proches
Method method = IDFMNetworkExtractor.class.getDeclaredMethod("addCandidate", TraceEntry.class, StopEntry.class);
method.setAccessible(true);
method.invoke(null, trace, candidate);
//L'appel c'est derouler correctement
assertTrue(true, "L'appel de addCandidate s'est déroulé sans erreur.");
}
}

View file

@ -0,0 +1,61 @@
package fr.u_paris.gla.project.idfm;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StopEntryTest {
//Test de toString
@Test
public void testToString() {
StopEntry stop = new StopEntry("Chatelet", 2.346, 48.853);
// Mise à jour de la valeur attendue pour correspondre au formatage réel
String expected = "Chatelet [2,346, 48,853]";
assertEquals(expected, stop.toString());
}
//Si le le test testToString du haut ne marche pas essayer celui du bas
/*@Test
public void testToString() {
StopEntry stop = new StopEntry("Chatelet", 2.346, 48.853);
// Mise à jour de la valeur attendue pour correspondre au formatage réel
String expected = "Chatelet [2,346, 48,853]";
assertEquals(expected, stop.toString());
} */
//Test de compareTo
@Test
public void testCompareTo() {
StopEntry stop1 = new StopEntry("Chatelet", 2.3467, 48.8534);
StopEntry stop2 = new StopEntry("Louvre", 2.3360, 48.8606);
assertTrue(stop1.compareTo(stop2) < 0); //
assertTrue(stop2.compareTo(stop1) > 0); //
// Test avec la même latitude et longitude mais des noms différents
StopEntry stop3 = new StopEntry("Chatelet", 2.3467, 48.8534);
assertEquals(0, stop1.compareTo(stop3));
// Test avec le même nom mais des emplacements différents
StopEntry stop4 = new StopEntry("Chatelet", 2.3500, 48.8500);
assertTrue(stop1.compareTo(stop4) > 0);
}
//Test de hashCode
@Test
public void testHashCode() {
StopEntry stop1 = new StopEntry("Chatelet", 2.3467, 48.8534);
StopEntry stop2 = new StopEntry("Chatelet", 2.3467, 48.8534);
assertEquals(stop1.hashCode(), stop2.hashCode());
}
//Test de equals
@Test
public void testEquals() {
StopEntry stop1 = new StopEntry("Chatelet", 2.3467, 48.8534);
StopEntry stop2 = new StopEntry("Chatelet", 2.3467, 48.8534);
StopEntry stop3 = new StopEntry("Louvre", 2.3360, 48.8606);
assertEquals(stop1, stop2);
assertNotEquals(stop1, stop3);
}
}

View file

@ -0,0 +1,68 @@
package fr.u_paris.gla.project.idfm;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StopTest {
@Test
public void testIsStopConnected() {
Stop stop = new Stop("Stop1");
BifStop bifStop1 = new BifStop(1, new Stop("Stop2"));
// Initially, no stops are connected
assertFalse(stop.isStopConnected("Stop2"));
// Add a connected stop
stop.addConnectedStop(bifStop1);
// Now, Stop2 should be connected
assertTrue(stop.isStopConnected("Stop2"));
}
@Test
public void testGetConnectedStop() {
Stop stop = new Stop("Stop1");
BifStop bifStop1 = new BifStop(1, new Stop("Stop2"));
BifStop bifStop2 = new BifStop(2, new Stop("Stop3"));
// Add two connected stops
stop.addConnectedStop(bifStop1);
stop.addConnectedStop(bifStop2);
// Retrieve the connected stops
BifStop retrievedStop1 = stop.getConnectedStop("Stop2");
BifStop retrievedStop2 = stop.getConnectedStop("Stop3");
// Check if the correct stops were retrieved
assertEquals(bifStop1, retrievedStop1);
assertEquals(bifStop2, retrievedStop2);
}
@Test
public void testAddConnectedStop() {
Stop stop = new Stop("Stop1");
BifStop bifStop1 = new BifStop(1, new Stop("Stop2"));
// Add a connected stop
stop.addConnectedStop(bifStop1);
// Check if the stop was added
assertTrue(stop.isStopConnected("Stop2"));
}
@Test
public void testSHJH(){
Stop stop = new Stop("Stop2323");
BifStop bifStop1 = new BifStop(1, new Stop("Stop2323"));
// Add a connected stop
stop.addConnectedStop(bifStop1);
// Check if the stop was added
assertTrue(stop.isStopConnected("Stop2323"));
}
}

View file

@ -0,0 +1,48 @@
package fr.u_paris.gla.project.idfm;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
import java.util.List;
public class TraceEntryTest {
//addTerminus
@Test
public void testAddTerminus() {
TraceEntry traceEntry = new TraceEntry("Ligne 1","IDFM:03434","Bus", "dummy_url");
String terminus1 = "Terminus A";
String terminus2 = "Terminus B";
//Ajouter des arrêt sur la ligne
traceEntry.addTerminus(terminus1);
traceEntry.addTerminus(terminus2);
List<String> terminusList = traceEntry.getTerminus();
assertEquals(2, terminusList.size(), "La liste des terminus doit contenir deux éléments.");
assertTrue(terminusList.contains(terminus1), "La liste des terminus doit contenir le terminus A.");
assertTrue(terminusList.contains(terminus2), "La liste des terminus doit contenir le terminus B.");
}
//addPath
@Test
public void testAddPath() {
TraceEntry traceEntry = new TraceEntry("Ligne 1","IDFM:03434","Bus", "dummy_url");
StopEntry stop1 = new StopEntry("Station 1", 2.300, 48.850);
StopEntry stop2 = new StopEntry("Station 2", 2.310, 48.855);
List<StopEntry> path = Arrays.asList(stop1, stop2);
traceEntry.addPath(path);
List<List<StopEntry>> paths = traceEntry.getPaths();
assertEquals(1, paths.size(), "Il doit y avoir un chemin dans la liste des chemins.");
assertEquals(2, paths.get(0).size(), "Le chemin ajouté doit contenir deux arrêts.");
assertTrue(paths.get(0).containsAll(path), "Le chemin ajouté doit contenir les arrêts spécifiés.");
}
//Verfier si le nom de la ligne lname est correctement initialiser
@Test
public void testTraceEntryName() {
TraceEntry traceEntry = new TraceEntry("Ligne 1","IDFM:03434","Bus", "dummy_url");
assertEquals("Ligne 1", traceEntry.lname, "Le nom de la ligne doit être 'Ligne 1'.");
}
}

View file

@ -0,0 +1,64 @@
package fr.u_paris.gla.project.idfm;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.*;
public class TransportTest {
/*@Test
public void testRoadToLast() {
Transport transport = new Transport("Test Line", "Bus");
transport.addStop("A", "B", 1);
transport.addStop("B", "C", 2);
transport.addStop("C", "D", 3);
TraceDescription td = new TraceDescription("A", "D", "A", "D");
transport.descriptions.add(td);
List<String> visited = new ArrayList<>();
List<Integer> bifurcations = new ArrayList<>();
SimpleEntry<Boolean, List<Integer>> result = transport.roadToLast("A", "D", visited, bifurcations);
assertFalse(result.getKey());
assertEquals(List.of(1, 2, 3), result.getValue());
}*/
/*@Test
public void testRoadToLastOptimized() {
SimpleEntry<Boolean, List<Integer>> result = transport.roadToLastOptimized("A", "D", new HashSet<>(), new ArrayList<>());
assertTrue(result.getKey());
assertEquals(List.of(1, 2, 3), result.getValue());
}*/
@Test
public void testIsTerminus() {
Transport transport = new Transport("Test Line", "Bus", "dummy_url");
transport.addStop("A", "B", 1);
transport.addStop("B", "C", 2);
transport.addStop("C", "D", 3);
TraceDescription td = new TraceDescription("A", "D", "A", "D");
transport.descriptions.add(td);
assertTrue(transport.isTerminus("A"));
assertTrue(transport.isTerminus("D"));
assertFalse(transport.isTerminus("B"));
}
@Test
public void testAddStop() {
Transport transport = new Transport("Test Line", "Bus", "dummy_url");
transport.addStop("A", "B", 1);
transport.addStop("B", "C", 2);
transport.addStop("C", "D", 3);
TraceDescription td = new TraceDescription("A", "D", "A", "D");
transport.descriptions.add(td);
transport.addStop("D", "E", 4);
assertTrue(transport.stopsMap.containsKey("E"));
assertEquals("E", transport.stopsMap.get("E").name);
assertTrue(transport.stopsMap.get("D").isStopConnected("E"));
}
}

View file

@ -0,0 +1,84 @@
package fr.u_paris.gla.project.idfm;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class UnidentifiedStopEntryTest {
// Test de la méthode resolve de la classe UnidentifiedStopEntry
@Test
public void testResolve() {
// Création d'un UnidentifiedStopEntry avec des coordonnées arbitraires (0,0)
UnidentifiedStopEntry unidentifiedStopEntry = new UnidentifiedStopEntry(0, 0);
// Test lorsque la liste des candidats est vide
assertNull(unidentifiedStopEntry.resolve());
// Test lorsque la liste des candidats contient un seul StopEntry
StopEntry stopEntry1 = new StopEntry("Stop1", 10.0, 20.0);
unidentifiedStopEntry.addCandidate(stopEntry1);
assertEquals(stopEntry1, unidentifiedStopEntry.resolve());
// Test lorsque la liste des candidats contient plusieurs StopEntries
StopEntry stopEntry2 = new StopEntry("Stop2", 30.0, 40.0);
unidentifiedStopEntry.addCandidate(stopEntry2);
// En supposant que la méthode GPS.distance fonctionne correctement, stopEntry1 devrait être plus proche
assertEquals(stopEntry1, unidentifiedStopEntry.resolve());
// Test lorsque la liste des candidats contient plusieurs StopEntries et que le plus proche change
UnidentifiedStopEntry unidentifiedStopEntry2 = new UnidentifiedStopEntry(35.0, 45.0);
unidentifiedStopEntry2.addCandidate(stopEntry1);
unidentifiedStopEntry2.addCandidate(stopEntry2);
// Maintenant, stopEntry1 devrait être plus proche
assertEquals(stopEntry2, unidentifiedStopEntry2.resolve());
}
// Test de la méthode addCandidate de la classe UnidentifiedStopEntry
@Test
public void testAddCandidate() {
// Création d'un UnidentifiedStopEntry avec des coordonnées arbitraires (0,0)
UnidentifiedStopEntry unidentifiedStopEntry = new UnidentifiedStopEntry(0, 0);
// Test lorsque nous ajoutons un StopEntry à la liste des candidats
StopEntry stopEntry1 = new StopEntry("Stop1", 10.0, 20.0);
unidentifiedStopEntry.addCandidate(stopEntry1);
assertEquals(stopEntry1, unidentifiedStopEntry.resolve());
// Test lorsque nous ajoutons un autre StopEntry à la liste des candidats
StopEntry stopEntry2 = new StopEntry("Stop2", 30.0, 40.0);
unidentifiedStopEntry.addCandidate(stopEntry2);
// En supposant que la méthode GPS.distance fonctionne correctement, stopEntry1 devrait être plus proche
assertEquals(stopEntry1, unidentifiedStopEntry.resolve());
}
// Test de la méthode equals de la classe UnidentifiedStopEntry
@Test
public void testEquals() {
// Création de deux UnidentifiedStopEntry avec les mêmes coordonnées
UnidentifiedStopEntry unidentifiedStopEntry1 = new UnidentifiedStopEntry(0, 0);
UnidentifiedStopEntry unidentifiedStopEntry2 = new UnidentifiedStopEntry(0, 0);
// Test lorsque nous comparons un UnidentifiedStopEntry avec lui-même
assertTrue(unidentifiedStopEntry1.equals(unidentifiedStopEntry1));
// Test lorsque nous comparons deux UnidentifiedStopEntry qui n'ont pas de candidats
assertTrue(unidentifiedStopEntry1.equals(unidentifiedStopEntry2));
// Test lorsque nous ajoutons le même StopEntry aux deux UnidentifiedStopEntry
StopEntry stopEntry = new StopEntry("Stop1", 10.0, 20.0);
unidentifiedStopEntry1.addCandidate(stopEntry);
unidentifiedStopEntry2.addCandidate(stopEntry);
assertTrue(unidentifiedStopEntry1.equals(unidentifiedStopEntry2));
// Test lorsque nous ajoutons un autre StopEntry à l'un des UnidentifiedStopEntry
StopEntry stopEntry2 = new StopEntry("Stop2", 30.0, 40.0);
unidentifiedStopEntry1.addCandidate(stopEntry2);
assertFalse(unidentifiedStopEntry1.equals(unidentifiedStopEntry2));
}
}

View file

@ -0,0 +1,80 @@
package fr.u_paris.gla.project.io;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeParseException;
import static org.junit.jupiter.api.Assertions.*;
class NetworkFormatTest {
String t = "00:00";
NumberFormat GPS_test = NetworkFormat.getGPSFormatter();
@Test
public void testParseDurationEqual() {
assertEquals(Duration.ZERO, NetworkFormat.parseDuration(t));
}
@Test
public void testParseDurationTooBig() {
String y = "119:00";
assertThrows(DateTimeParseException.class, () -> NetworkFormat.parseDuration(y));
}
@Test
public void formatDuration() {
assertEquals(t, NetworkFormat.formatDuration(Duration.ZERO));
}
@Test
public void parseThenFormatDuration(){
String t = "00:00";
assertEquals(t, NetworkFormat.formatDuration(NetworkFormat.parseDuration(t)));
}
@Test
public void getGPSFormatterPos() {
double GPS_pos = 1.456489615649813;
assertEquals(String.valueOf(GPS_pos), GPS_test.format(GPS_pos));
}
@Test
public void getGPSFormatterNeg() {
double GPS_neg = -1.456489615649813;
assertEquals(String.valueOf(GPS_neg), GPS_test.format(GPS_neg));
}
@Test
public void getGPSFormatterNul() {
int GPS_nul = 0;
assertEquals(String.valueOf(GPS_nul), GPS_test.format(GPS_nul));
}
@Test
public void getGPSFormatterBig() {
String string_int = "4565156498156489";
String string_float = "5675747274674276474267479751262167";
BigDecimal GPS_big = new BigDecimal(string_int + "." + string_float);
assertEquals(string_int + "." + string_float.substring(0, NetworkFormat.GPS_PRECISION),
GPS_test.format(GPS_big).replace(",", "").replace(" ",""));
}
}

View file

@ -0,0 +1,33 @@
package fr.u_paris.gla.project.io;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class ScheduleFormatTest {
@Test
public void getTripSequence() {
String rpz = "4,5,19,21";
List<Integer> test = Arrays.asList(4, 5, 19, 21);
assertEquals(test, ScheduleFormat.getTripSequence(rpz));
}
@Test
public void getTimeFormatter() {
DateTimeFormatter formatter = ScheduleFormat.getTimeFormatter();
LocalDateTime date = LocalDateTime.now();
String test = date.format(formatter);
//format date: YYYY-MM-DDTHH:MM:SS.DECIMAL
assertEquals(date.toString().substring(11, 16), test);
}
}

View file

@ -0,0 +1,79 @@
package fr.u_paris.gla.project.utils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class CSVToolsTest {
@Test
public void readCSVFromURL_invalid() {
// TODO Fix the exception thrown
/**
assertThrows(IOException.class,() -> {
Consumer<String[]> test = s -> System.out.println(Arrays.toString(s));
CSVTools.readCSVFromURL("https://google.fr",
test);
}
);
*/
}
@Test
public void testreadCSVFromURL_valid() {
assertDoesNotThrow(() -> {
Consumer<String[]> test = s -> System.out.println(Arrays.toString(s));
CSVTools.readCSVFromURL("https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
test);
}
);
}
@Test
void writeCSVToFile() {
assertDoesNotThrow(() -> {
String[] stuff = {"jsqdsqdsqsqffdfgzava", "pfezegrrbeebn", "dfbsduifzegbczi", "sdfsdfcy"};
String[][] t = {stuff, stuff};
Stream<String[]> test = Arrays.stream(t);
CSVTools.writeCSVToFile("test.csv", test);
});
}
@Test
void writeCSVToFile_specialName() {
assertDoesNotThrow(() -> {
String[] stuff = {"jsqdsqdsqsqffdfgzava", "pfezegrrbeebn", "dfbsduifzegbczi", "sdfsdfcy"};
String[][] t = {stuff, stuff};
Stream<String[]> test = Arrays.stream(t);
CSVTools.writeCSVToFile("éè'-'_-éè_à.csv", test);
});
}
@Test
void writeCSVToFile_invalidName() {
assertThrows( IOException.class ,() -> {
String[] stuff = {"jsqdsqdsqsqffdfgzava", "pfezegrrbeebn", "dfbsduifzegbczi", "sdfsdfcy"};
String[][] t = {stuff, stuff};
Stream<String[]> test = Arrays.stream(t);
CSVTools.writeCSVToFile(".", test);
});
}
}

View file

@ -0,0 +1,39 @@
package fr.u_paris.gla.project.utils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GPSTest {
@Test
public void testDistance_SameLat(){
assertDoesNotThrow(
() -> {
GPS.distance(5, 3, 5, 11);
}
);
}
@Test
public void distance_SameLon(){
assertDoesNotThrow(
() -> {
GPS.distance(5, 3, 7, 3);
}
);
}
@Test
public void distance_SamePoint() {
assertEquals(0.0, GPS.distance(5, 3, 5, 3) );
}
@Test
public void distance_NegativePoint(){
assertNotEquals(0.0, GPS.distance(-5, 7, -13, 4));
}
}

1950
trace_idfm.csv Normal file

File diff suppressed because one or more lines are too long