diff --git a/README.md b/README.md index 69e985a..8278cc2 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,9 @@ 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. -Une fois le programme compilé, vous trouverez un jar exécutable dans le dossier `target`. Au nom de jar près (version changeante), vous pourrez l'exécuter avec : - -```bash -java -jar project-2024.1.0.0-SNAPSHOT.jar --info +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 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. diff --git a/pom.xml b/pom.xml index 02ce6a7..40af7f3 100644 --- a/pom.xml +++ b/pom.xml @@ -31,12 +31,28 @@ org.json json - 20230618 + 20231013 + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/${project.build.finalName}.lib + + + + org.apache.maven.plugins maven-jar-plugin @@ -45,15 +61,23 @@ true - fr.u_paris.gla.project.App + ${project.build.finalName}.lib/ + fr.u_paris.gla.project.itinerary.ItineraryCalculator + - org.jacoco - jacoco-maven-plugin - 0.8.11 + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + **/*Test.java + + true + diff --git a/src/main/java/fr/u_paris/gla/project/App.java b/src/main/java/fr/u_paris/gla/project/App.java index 811ed94..08e16a4 100644 --- a/src/main/java/fr/u_paris/gla/project/App.java +++ b/src/main/java/fr/u_paris/gla/project/App.java @@ -63,7 +63,7 @@ public class App { } } - /** @param out */ + /** @param out the output stream */ public static void printAppInfos(PrintStream out) { Properties props = readApplicationProperties(); diff --git a/src/main/java/fr/u_paris/gla/project/idfm/BifStop.java b/src/main/java/fr/u_paris/gla/project/idfm/BifStop.java new file mode 100644 index 0000000..098337f --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/BifStop.java @@ -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; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/CSVImageProvider.java b/src/main/java/fr/u_paris/gla/project/idfm/CSVImageProvider.java new file mode 100644 index 0000000..2d4d115 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/CSVImageProvider.java @@ -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 current; + + /** Create the stream provider */ + public CSVImageProvider(Iterator 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); + } +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/CSVSchedulesProvider.java b/src/main/java/fr/u_paris/gla/project/idfm/CSVSchedulesProvider.java new file mode 100644 index 0000000..52835d3 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/CSVSchedulesProvider.java @@ -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 timings = new HashMap(){{ + 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 currentTransport; + private Iterator currentDescription = Collections.emptyIterator(); + + private String current_tansport_type = ""; + private LocalDateTime currentHour = null; + private LocalDateTime lastHour = null; + + + + + /** + * Create the stream provider + */ + public CSVSchedulesProvider(Iterator 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); + } +} \ No newline at end of file diff --git a/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamProvider.java b/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamProvider.java new file mode 100644 index 0000000..d09754f --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamProvider.java @@ -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 currentTrace; + /** + * Current Stop path iterator + */ + private Iterator> currentPath = Collections.emptyIterator(); + /** + * current iterator for the begin of the line + */ + private Iterator currentSegmentStart = Collections.emptyIterator(); + /** + * current iterator for the end of the line + */ + private Iterator currentSegmentEnd = Collections.emptyIterator(); + /** + * HashMap of the current line's segments + */ + Map> lineSegments = new HashMap<>(); + // The transport id with its value + private final Map transports; + List 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 traces, Map 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 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. + *

+ * 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); + } + } + + } + + +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractor.java b/src/main/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractor.java index 50d4834..d14fb82 100644 --- a/src/main/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractor.java +++ b/src/main/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractor.java @@ -1,325 +1,432 @@ /** - * + * */ package fr.u_paris.gla.project.idfm; -import java.io.BufferedReader; -import java.io.FileWriter; -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 fr.u_paris.gla.project.utils.CSVTools; +import fr.u_paris.gla.project.utils.GPS; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import com.opencsv.CSVParserBuilder; -import com.opencsv.CSVReader; -import com.opencsv.CSVReaderBuilder; -import com.opencsv.CSVWriterBuilder; -import com.opencsv.ICSVParser; -import com.opencsv.ICSVWriter; -import com.opencsv.exceptions.CsvValidationException; +import java.io.IOException; +import java.io.File; +import java.text.MessageFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.logging.Level; +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. - * - * @author Emmanuel Bigeon */ +/** + * Code of an extractor for the data from IDF mobilite. + * + * @author Emmanuel Bigeon + */ public class IDFMNetworkExtractor { - /** The logger for information on the process */ + + /** + * The logger for information on the process + */ private static final Logger LOGGER = Logger .getLogger(IDFMNetworkExtractor.class.getName()); + /** + * the URL of the Trace CSV + */ // 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"; + /** + * 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"; + /** + * the index in the CSV of a Trace's ID + */ // 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; + /** + * 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_TYPE_INDEX = 3; - private static final int IDFM_STOPS_RID_INDEX = 0; + /** + * The index in the CSV of the Stops' id + */ + 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_LON_INDEX = 6; - private static final int IDFM_STOPS_LAT_INDEX = 7; + /** + * The index in the CSV of the Stops' longitude + */ + 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_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 - /** A number of stops on each line */ - 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; + /** + * A number of stops on each line + */ + private static final int GUESS_STOPS_BY_LINE = 5; + /** + * The quarter of a kilometer as a static value + */ // Well named constants - private static final double _250_METERS = .25; - private static final long SECONDS_IN_HOURS = 3_600; + private static final double QUARTER_KILOMETER = .25; - private static final Format GPS_FORMATTER = NetworkFormat.getGPSFormatter(); - - 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 candidates = new ArrayList<>(); - - @Override - public String toString() { - return "UnidentifiedStop [candidates=" + this.candidates + "]"; - } - } - - private static final class TraceEntry { - String lname; - List> stops = new ArrayList<>(); - } - - public static void readCSVFromURL(String url, Consumer 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 + /** + * Main entry point for the extractor of IDF mobilité data into a network as * 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) { - if (args.length != 1) { - LOGGER.severe("Invalid command line. Missing target file."); + if (args.length != 2) { + LOGGER.severe("Invalid command line. Missing target files."); return; } Map traces = new HashMap<>(); try { - readCSVFromURL(TRACE_FILE_URL, (String[] line) -> { - TraceEntry entry = new TraceEntry(); - entry.lname = line[IDFM_TRACE_SNAME_INDEX]; - List> 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); - } - }); + CSVTools.readCSVFromURL(TRACE_FILE_URL, + (String[] line) -> addLine(line, traces)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error while reading the line paths", e); } List stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE); try { - readCSVFromURL(STOPS_FILE_URL, (String[] line) -> { - 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]; - BiFunction func = ( - k, trace) -> { - for (List 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); - }); + CSVTools.readCSVFromURL(STOPS_FILE_URL, + (String[] line) -> addStop(line, traces, stops)); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error while reading the stops", e); } + cleanTraces(traces); + + Map 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 traces = new HashMap<>(); + try { + CSVTools.readCSVFromURL(TRACE_FILE_URL, + (String[] line) -> addLine(line, traces)); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error while reading the line paths", e); + } + + List stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE); + try { + CSVTools.readCSVFromURL(STOPS_FILE_URL, + (String[] line) -> addStop(line, traces, stops)); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error while reading the stops", e); + } + + cleanTraces(traces); + + Map 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 traces) { Set toRemove = new HashSet<>(); for (Entry traceEntry : traces.entrySet()) { TraceEntry trace = traceEntry.getValue(); - for (List path : trace.stops) { - for (int i = 0; i < path.size(); i++) { - StopEntry stop = path.get(i); - 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()); - continue; - } - path.set(i, stop); - } + if (!cleanLine(trace.getPaths())) { + LOGGER.severe(() -> MessageFormat.format( + "Missing stop for line {0}. Line will be removed", trace.lname)); + toRemove.add(traceEntry.getKey()); } } - for (String string : toRemove) { 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 traceEntry : traces.entrySet()) { - Map> lineSegments = new HashMap<>(); - String[] nextLine = new String[NetworkFormat.NUMBER_COLUMNS]; - nextLine[NetworkFormat.LINE_INDEX] = traceEntry.getValue().lname; - for (List 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> stops) { + for (List 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 traces, + List 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 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); + } + + /** 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 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> 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); + } + } + + /** add a new entry as a candidate to a trace + * @param trace the trace + * @param entry the entry + * @return the trace in question + */ + private static TraceEntry addCandidate(TraceEntry trace, StopEntry entry) { + for (List path : trace.getPaths()) { + for (StopEntry stopEntry : path) { + if (stopEntry instanceof UnidentifiedStopEntry unidentified + && GPS.distance(entry.latitude, entry.longitude, + stopEntry.latitude, + stopEntry.longitude) < QUARTER_KILOMETER) { + unidentified.addCandidate(entry); } } - } catch (IOException e) { - LOGGER.log(Level.SEVERE, e, () -> "Could not write in file " + args[1]); } + return trace; } - /** @param distanceToTime - * @return */ - private static String formatTime(long time) { - NumberFormat format = NumberFormat.getInstance(Locale.ENGLISH); - format.setMinimumIntegerDigits(2); - return MessageFormat.format("{0}:{1}", format.format(time / 60), - format.format(time % 60)); - } - - /** A tool method to give a delay to go through a certain distance. - *

- * 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 - 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) (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> buildPaths(String pathsJSON) { List> all = new ArrayList<>(); try { @@ -341,8 +448,56 @@ public class IDFMNetworkExtractor { } } catch (JSONException e) { // 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; } + + /** extract the terminus out of a JSON file + * @param JSON the JSON + * @return a list of strings related to the terminus + */ + private static List extractTerminus(String JSON) { + List 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 extractDescription(String JSON) { + List 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; + } } diff --git a/src/main/java/fr/u_paris/gla/project/idfm/Stop.java b/src/main/java/fr/u_paris/gla/project/idfm/Stop.java new file mode 100644 index 0000000..0a0a2fa --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/Stop.java @@ -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 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); + } + +} + diff --git a/src/main/java/fr/u_paris/gla/project/idfm/StopEntry.java b/src/main/java/fr/u_paris/gla/project/idfm/StopEntry.java new file mode 100644 index 0000000..0b1685f --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/StopEntry.java @@ -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 { + 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); + } +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/TraceDescription.java b/src/main/java/fr/u_paris/gla/project/idfm/TraceDescription.java new file mode 100644 index 0000000..2de7424 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/TraceDescription.java @@ -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 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; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java b/src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java new file mode 100644 index 0000000..8891fb8 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java @@ -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 terminus = new ArrayList<>(); + private List> paths = new ArrayList<>(); + List 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> getPaths() { + // TODO Ne pas retourner directement la liste + return paths; + } + + /** @return the list of terminus */ + public List getTerminus() { + return terminus; + } + + public void addPath(List 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 desctipt){ + descriptions.addAll(desctipt); + } +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/Transport.java b/src/main/java/fr/u_paris/gla/project/idfm/Transport.java new file mode 100644 index 0000000..1e13dd9 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/Transport.java @@ -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 stopsMap = new HashMap<>(); + public String name; + public String type; + public String image_url; + + //All the line descriptions (directions and schedules) + List 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> sol = roadToLast(debut.name, fin.name, new ArrayList(), new ArrayList()); + 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 alreadyVisited = new HashSet<>(); + SimpleEntry> sol = roadToLastOptimized(debut.name, fin.name, alreadyVisited, new ArrayList()); + 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 > roadToLast(String currentStop, String last, List alreadyVisited, List 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 visitedCopy = new ArrayList<>(alreadyVisited); + visitedCopy.add(currentStop); + + Stop current = stopsMap.get(currentStop); + List > > solutions = new ArrayList<>(); + for(BifStop b: current.connected.values()){ + if(!visitedCopy.contains(b.stop.name)){ + List 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 bifSol = new ArrayList<>(); + boolean trouve = false; + for(SimpleEntry> 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> roadToLastOptimized(String currentStop, String last, Set alreadyVisited, List 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>> solutions = new ArrayList<>(); + for (BifStop b : current.connected.values()) { + if (!alreadyVisited.contains(b.stop.name)) { + List 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 bifSol = new ArrayList<>(); + boolean trouve = false; + for (SimpleEntry> 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 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 desctipt){ + descriptions.addAll(desctipt); + } + + +} diff --git a/src/main/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntry.java b/src/main/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntry.java new file mode 100644 index 0000000..fc4f8d0 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntry.java @@ -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 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) (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); + } + +} diff --git a/src/main/java/fr/u_paris/gla/project/io/ImageFormat.java b/src/main/java/fr/u_paris/gla/project/io/ImageFormat.java new file mode 100644 index 0000000..7f7f8c8 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/io/ImageFormat.java @@ -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 + } +} diff --git a/src/main/java/fr/u_paris/gla/project/io/NetworkFormat.java b/src/main/java/fr/u_paris/gla/project/io/NetworkFormat.java index ff95c99..a46499d 100644 --- a/src/main/java/fr/u_paris/gla/project/io/NetworkFormat.java +++ b/src/main/java/fr/u_paris/gla/project/io/NetworkFormat.java @@ -14,8 +14,10 @@ import java.util.Locale; * * @author Emmanuel Bigeon */ public final class NetworkFormat { - + /** The amount of columns in the CSV file */ public static final int NUMBER_COLUMNS = 8; + + /** The amount of decimal places in the GPS' coordinates */ public static final int GPS_PRECISION = 18; /** 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 .ofPattern("HH:mm:ss"); - private static final NumberFormat DURATION_SECONDS_FORMATTER = NumberFormat + private static final NumberFormat DURATION_INDIVIDUAL_FORMATTER = NumberFormat .getIntegerInstance(Locale.ENGLISH); static { - DURATION_SECONDS_FORMATTER.setMinimumIntegerDigits(2); + DURATION_INDIVIDUAL_FORMATTER.setMinimumIntegerDigits(2); } private static final Temporal ZERO = LocalTime.parse("00:00:00"); @@ -45,22 +47,30 @@ public final class NetworkFormat { // 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) { LocalTime time = LocalTime.parse("00:" + duration, DURATION_FORMATTER); 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) { - return Long.toString(duration.toMinutes()) + ":" - + DURATION_SECONDS_FORMATTER.format(duration.toSecondsPart()); + return DURATION_INDIVIDUAL_FORMATTER.format(duration.toMinutes()) + ":" + + DURATION_INDIVIDUAL_FORMATTER.format(duration.toSecondsPart()); } /** 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() { NumberFormat instance = NumberFormat.getNumberInstance(Locale.ENGLISH); instance.setMaximumFractionDigits(GPS_PRECISION); return instance; } -} +} \ No newline at end of file diff --git a/src/main/java/fr/u_paris/gla/project/io/ScheduleFormat.java b/src/main/java/fr/u_paris/gla/project/io/ScheduleFormat.java index ba4a645..77020b6 100644 --- a/src/main/java/fr/u_paris/gla/project/io/ScheduleFormat.java +++ b/src/main/java/fr/u_paris/gla/project/io/ScheduleFormat.java @@ -1,36 +1,54 @@ /** - * + * */ 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 schedule format. - * - * @author Emmanuel Bigeon */ +/** + * A tool class for the schedule format. + * + * @author Emmanuel Bigeon + */ public final class ScheduleFormat { - public static final int LINE_INDEX = 0; + /** 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; + /** The index of the trip sequence in the schedule format */ public static final int TRIP_SEQUENCE_INDEX = 1; - public static final int TERMINUS_INDEX = 2; - public static final int TIME_INDEX = 3; + /** The index of the terminus name in the schedule format */ + public static final int TERMINUS_INDEX = 2; + /** The index of the time in the schedule format */ + public static final int TIME_INDEX = 3; /** Hidden constructor for tool class */ private ScheduleFormat() { // Tool class } - /** Read a trip sequence from its string representation - * + /** + * Read a trip sequence from its string representation + * * @param representation the representation - * @return the sequence of branching */ + * @return the sequence of branching + */ public static List getTripSequence(String representation) { - // TODO Read a trip sequence from a string - throw new RuntimeException("Not implemented yet"); - } - + List 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() { return DateTimeFormatter.ofPattern("HH:mm").withResolverStyle(ResolverStyle.LENIENT); } diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/Connection.java b/src/main/java/fr/u_paris/gla/project/itinerary/Connection.java new file mode 100644 index 0000000..ed9dde6 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/Connection.java @@ -0,0 +1,99 @@ +package fr.u_paris.gla.project.itinerary; + +import java.util.ArrayList; +import java.util.Collections; + +public class Connection{ + // Destination of the connection between the two stops + private final Stop stop; + + // The line used for this connection + private final String lineName; + + //Distance between the two stops + private final double distance; + + //Travel time between the two stops + private final int time; + + private final ArrayList schedules; + + private final int bifurcation; + + public Connection(Stop stop, String lineName, double distance, int time, int bifurcation){ + this.stop = stop; + this.lineName=lineName; + this.distance = distance; + 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); + } + + + public String getLineName() { + return lineName; + } + + public double getDistance() { + return distance; + } + + public int getTime() { + return time; + } + + public Stop getStop() { + return stop; + } + + public void addSchedule(int hours) { + this.schedules.add(hours); + } + + public void sortSchedule() { + Collections.sort(this.schedules); + } + + public ArrayList 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; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/Finder.java b/src/main/java/fr/u_paris/gla/project/itinerary/Finder.java new file mode 100644 index 0000000..1e557b3 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/Finder.java @@ -0,0 +1,101 @@ +package fr.u_paris.gla.project.itinerary; + +import java.util.*; + +public class Finder { + private Graph graph; + public Finder(Graph graph) { + this.graph = graph; + } + + public List findPath(Stop startNode, Stop goalNode) { + double startTime = 43200; //12h + + PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingDouble(GraphNode::getF)); + HashSet closedSet = new HashSet<>(); + HashMap cameFrom = new HashMap<>(); + HashMap gScore = new HashMap<>(); + HashMap fScore = new HashMap<>(); + + // Initialize scores for all nodes to infinity + for (Stop node : graph.getNodes()) { + gScore.put(node, Double.POSITIVE_INFINITY); + fScore.put(node, Double.POSITIVE_INFINITY); + } + + // The cost of going from start to start is the start time + gScore.put(startNode, startTime); + // For the first node, fScore = gScore + heuristic + fScore.put(startNode, startNode.getHeuristicCost(goalNode)); + openSet.add(startNode); + + while (!openSet.isEmpty()) { + Stop current = openSet.poll(); + double currentTime = gScore.get(current); + + if (current.equals(goalNode)) { + return reconstructPath(cameFrom, current); + } + + closedSet.add(current); + + if(graph.getConnections(current) == null) { + continue; + } + + for (Connection connection : graph.getConnections(current)) { + Stop neighbor = connection.getStop(); + if (closedSet.contains(neighbor)) { + continue; // Ignore the neighbor which is already evaluated. + } + + double tentativeGScore = currentTime + connection.getCost(currentTime); + + if (tentativeGScore >= gScore.get(neighbor)) { + continue; // This is not a better path. + } + + // This path is the best until now. Record it! + cameFrom.put(neighbor, current); + gScore.put(neighbor, tentativeGScore); + fScore.put(neighbor, tentativeGScore + neighbor.getHeuristicCost(goalNode)); + + if (!openSet.contains(neighbor)) { + neighbor.setF(fScore.get(neighbor)); + openSet.add(neighbor); + } + else { + updatePriority(openSet, neighbor, fScore.get(neighbor)); + } + } + } + + // If we reach here, it means there's no path from start to goal + return null; + } + + private List reconstructPath(HashMap cameFrom, Stop current) { + List totalPath = new ArrayList<>(); + totalPath.add(current); + + while (cameFrom.containsKey(current)) { + current = cameFrom.get(current); + totalPath.add(0, current); // Add to the beginning of the list to maintain order + } + + return totalPath; + } + + public void updatePriority(PriorityQueue openSet, Stop node, double newF) { + openSet.remove(node); + node.setF(newF); + openSet.add(node); + } + + //TODO: + public List findPath(double longitude, double latitude){ + return null; + } + +} + diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/Graph.java b/src/main/java/fr/u_paris/gla/project/itinerary/Graph.java new file mode 100644 index 0000000..795add9 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/Graph.java @@ -0,0 +1,28 @@ +package fr.u_paris.gla.project.itinerary; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Graph{ + private final Set nodes; + + private final Map> connections; + + public Graph(Set nodes, Map> connections) { + this.nodes = nodes; + this.connections = connections; + } + + public Set getConnections(Stop node) { + return connections.get(node); + } + + public Set getNodes() { + return nodes; + } + + public Map> getConnections() { + return connections; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/GraphNode.java b/src/main/java/fr/u_paris/gla/project/itinerary/GraphNode.java new file mode 100644 index 0000000..175db6e --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/GraphNode.java @@ -0,0 +1,14 @@ +package fr.u_paris.gla.project.itinerary; + +import java.util.List; +import java.util.Set; + +public interface GraphNode { + int getId(); + double getHeuristicCost(Stop goalNode); + + Set getNeighbors(); + double getCost(Stop neighbor); + double getF(); + void setF(double value); +} diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/ItineraryCalculator.java b/src/main/java/fr/u_paris/gla/project/itinerary/ItineraryCalculator.java new file mode 100644 index 0000000..f2ebe85 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/ItineraryCalculator.java @@ -0,0 +1,258 @@ +package fr.u_paris.gla.project.itinerary; + +import fr.u_paris.gla.project.idfm.*; +import fr.u_paris.gla.project.utils.CSVTools; +import fr.u_paris.gla.project.utils.GPS; + +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ItineraryCalculator { + private static final Logger LOGGER = Logger + .getLogger(IDFMNetworkExtractor.class.getName()); + + // IDF mobilite generated file + private static final String TRACE_FILE_NAME = "./trace.csv"; + + private static final String HOURS_FILE_NAME = "./hours.csv"; + + // IDF mobilite file format + + private static final int IDFM_TRACE_ID_INDEX = 0; + + private static final int IDFM_TRACE_DERIV_INDEX = 1; + + private static final int IDFM_TRACE_FROM_INDEX = 2; + + private static final int IDFM_TRACE_FROM_GPS_INDEX = 3; + + private static final int IDFM_TRACE_TO_INDEX= 4; + + private static final int IDFM_TRACE_TO_GPS_INDEX = 5; + + private static final int IDFM_TRACE_TIME_INDEX = 6; + + private static final int IDFM_TRACE_DISTANCE_INDEX = 7; + + 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: + * "49.08, 3.07" -> {49.08, 3.07} + * @param gps the string representation + * @return the double array + */ + private static double[] getCoords(String gps) { + String []stringCoords = gps.split(", "); + return new double[] {Double.parseDouble(stringCoords[0]), Double.parseDouble(stringCoords[1])}; + } + + /** + * Searchs for a stop with the same name and GPS coordinates in the graph, and creates it if non existant + * @param nodes a graph of the stops + * @param tmp list of the created stops + * @param name the name of the stop + * @param gps the coordinate of the stop + * @param lineId the line the stop is on + * @return the Stop object + */ + private static Stop getOrCreateStop(HashSet nodes, HashMap> tmp, String name, String gps, String lineId, HashMap> connections) { + ArrayList 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 + ArrayList lineChanges = new ArrayList<>(); + if (stopList != null) { + for(Stop stop : stopList) { + + double dist = GPS.distance(coords[0], coords[1], stop.getLatitude(), stop.getLongitude()); + if(dist == 0) { + stop.addLine(lineId); + return stop; + } + if(dist < ERROR_MARGIN) { + lineChanges.add(stop); + } + } + } + + Stop newStop = new Stop(lineId, name, coords[0], coords[1]); + nodes.add(newStop); + stopList = stopList == null ? new ArrayList<>() : stopList; + stopList.add(newStop); + 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; + } + + /** + * Adds into the graph the connection between two stops, parsed from a CSV line + * @param line the current line we want to parse + * @param nodes the graph of stops + * @param tmp list of the created stops + * @param connections + */ + private static void addLine(String[] line, HashSet nodes, HashMap> tmp, HashMap> connections) { + 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], connections); + + String[] timeString = line[IDFM_TRACE_TIME_INDEX].split(":"); + 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, Integer.parseInt(line[IDFM_TRACE_DERIV_INDEX])); + + connections.computeIfAbsent(fromStop, k -> new HashSet<>()).add(connection); + } + + private static void addScheduleRec(Stop current, Stop previous, String line, ArrayList bifurcations, int time, HashMap> stopsHashSet, HashMap> connections, HashSet processed){ + time = time%86400; + //If the stop has already been processed, it is not reprocessed. + if(processed.contains(current)) {return;} + processed.add(current); + + Set neighborhood = connections.get(current); + if(neighborhood == null) {return;} + + + ArrayList 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 : neighborhood) { + if(n.getStop() == next_stop) { + n.addSchedule(time); + time += n.getTime(); + addScheduleRec(next_stop, current, line, bifurcations, time, stopsHashSet, connections, processed); + return; + } + } + } + + private static void addSchedule(String[] input, HashMap> stopsHashSet, HashMap> connections) { + + String line = input[0]; + + ArrayList 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 + STOP_TIME; + + + ArrayList 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){ + if (args.length != 0) { + LOGGER.severe("Invalid command line. Target file names are in the main file for now."); + return; + } + IDFMNetworkExtractor.builFiles(); + + try { + HashSet nodes = new HashSet<>(); + HashMap> connections = new HashMap<>(); + HashMap> tmp = new HashMap<>(); + CSVTools.readCSVFromFile(TRACE_FILE_NAME, + (String[] line) -> addLine(line, nodes, tmp, connections)); + + CSVTools.readCSVFromFile(HOURS_FILE_NAME, + (String[] line) -> addSchedule(line, tmp, connections)); + + for(Set set : connections.values()) { + for(Connection c : set) { + c.sortSchedule(); + } + } + + + Stop porteivry = tmp.get("Porte d'Ivry").get(0); + Stop repu = tmp.get("République").get(0); + + Graph graph = new Graph(nodes, connections); + int cpt = 0; + for (Map.Entry> entry : graph.getConnections().entrySet()) { + if (entry.getValue() == null) cpt +=1; + } + Stop garenord = tmp.get("Gare du Nord").get(0); + + Stop chatelet = tmp.get("Châtelet").get(0); + //System.out.println(graph.getConnections(garenord)); + //System.out.println(cpt); + //System.out.println(graph.getConnections(porteivry)); + Finder finder = new Finder(graph); + + List res = finder.findPath(porteivry, chatelet); + for (Stop element : res) { + System.out.println(element); + } + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error while reading the line paths", e); + } + + + } +} diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/Path.java b/src/main/java/fr/u_paris/gla/project/itinerary/Path.java new file mode 100644 index 0000000..a492b99 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/Path.java @@ -0,0 +1,18 @@ +package fr.u_paris.gla.project.itinerary; + +public class Path { + private T current; + + private T previous; + + private double currentScore; + + private final double targetScore; + + public Path(T current, T previous, double currentScore, double targetScore) { + this.current = current; + this.previous = previous; + this.currentScore = currentScore; + this.targetScore = targetScore; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/itinerary/Stop.java b/src/main/java/fr/u_paris/gla/project/itinerary/Stop.java new file mode 100644 index 0000000..6d9169c --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/itinerary/Stop.java @@ -0,0 +1,90 @@ +package fr.u_paris.gla.project.itinerary; + +import java.util.HashSet; +import java.util.Set; + +public class Stop implements GraphNode { + // The total number of stops + private static int counter = 0; + + private final int id; + + // The set of all the lines the stop is on + private final Set lines; + + private final String name; + + private final double latitude; + + private final double longitude; + + private double f; + + public Stop(String line, String name, double latitude, double longitude) { + lines = new HashSet<>(); + lines.add(line); + this.id = counter++; + this.name = name; + this.latitude = latitude; + this.longitude = longitude; + this.f = 0; + } + + @Override + public String toString() { + return "Stop{" + + "id=" + id + + ", lines=" + lines + + ", name='" + name + '\'' + + ", latitude=" + latitude + + ", longitude=" + longitude + + '}'; + } + + @Override + public int getId(){ + return id; + } + + @Override + public double getHeuristicCost(Stop goalNode) { + return 0; + } + + @Override + public Set getNeighbors() { + return null; + } + + @Override + public double getCost(Stop neighbor) { + return 0; + } + + @Override + public double getF() { + return f; + } + + public void setF(double value) { + this.f = value; + } + + public String getName(){ + return name; + } + + public double getLatitude(){ + return latitude; + } + + public double getLongitude(){ + return longitude; + } + + public void addLine(String s){ + lines.add(s); + } + + public Set getLines() { return this.lines; } +} diff --git a/src/main/java/fr/u_paris/gla/project/utils/ApiUtils.java b/src/main/java/fr/u_paris/gla/project/utils/ApiUtils.java new file mode 100644 index 0000000..3a40845 --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/utils/ApiUtils.java @@ -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}; + } +} diff --git a/src/main/java/fr/u_paris/gla/project/utils/CSVTools.java b/src/main/java/fr/u_paris/gla/project/utils/CSVTools.java new file mode 100644 index 0000000..8fa35db --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/utils/CSVTools.java @@ -0,0 +1,107 @@ +/** + * + */ +package fr.u_paris.gla.project.utils; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import com.opencsv.CSVWriterBuilder; +import com.opencsv.ICSVParser; +import com.opencsv.ICSVWriter; +import com.opencsv.exceptions.CsvValidationException; + +/** A CSV tool class. + * + * @author Emmanuel Bigeon */ +public final class CSVTools { + + /** Hidden constructor of tool class */ + private CSVTools() { + // 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 contentLineConsumer) + throws IOException { + ICSVParser parser = new CSVParserBuilder().withSeparator(';').build(); + try (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", e); //$NON-NLS-1$ + } + } + + + /** 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 contentLineConsumer) + throws IOException { + File file = new File(filename); + readCSVFromInputStream(new FileInputStream(file), contentLineConsumer); + } + + /** get a CSV file from a URL, download and parse it, and keep values in memory + * @param url 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 + */ + public static void readCSVFromURL(String url, Consumer contentLineConsumer) + throws IOException { + readCSVFromInputStream(new URL(url).openStream(), contentLineConsumer); + } + + /** Save our current CSV variable's data into an actual file + * @param filename the saved file's name and path + * @param contentLineConsumer our data variable + * @throws IOException if we can't write the data into the file + */ + public static void writeCSVToFile(String filename, + Stream contentLinesConsumer) throws IOException { + try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) { + CSVWriterBuilder wBuilder = new CSVWriterBuilder(writer).withSeparator(';'); + try (ICSVWriter csv = wBuilder.build()) { + contentLinesConsumer.forEachOrdered(csv::writeNext); + } + } + } + + // /** Save our current CSV variable's data into an actual file + // * @param filename the saved file's name and path + // * @param contentLineConsumer our data variable + // * @throws IOException if we can't write the data into the file + // */ + // public static void writeCSVToFile(String filename, + // Stream contentLinesConsumer) throws IOException { + // try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) { + // CSVWriterBuilder wBuilder = new CSVWriterBuilder(writer).withSeparator(';'); + // try (ICSVWriter csv = wBuilder.build()) { + // contentLinesConsumer.forEachOrdered(line -> Arrays.stream(line).forEach(csv::writeNext)); + // } + // } + // } + +} diff --git a/src/main/java/fr/u_paris/gla/project/utils/GPS.java b/src/main/java/fr/u_paris/gla/project/utils/GPS.java index 72f2202..678fc3a 100644 --- a/src/main/java/fr/u_paris/gla/project/utils/GPS.java +++ b/src/main/java/fr/u_paris/gla/project/utils/GPS.java @@ -32,14 +32,16 @@ public final class GPS { * @param longitude1 the longitude of the first position * @param latitude2 the latitude 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, double longitude2) { - double deltaLatitude = degreeToRadian(latitude2 - latitude1); + latitude1 = degreeToRadian(latitude1); + latitude2 = degreeToRadian(latitude2); + double deltaLatitude = latitude2 - latitude1; double deltaLongitude = degreeToRadian(longitude2 - longitude1); 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); - 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)); } } diff --git a/src/test/java/fr/u_paris/gla/project/AppTest.java b/src/test/java/fr/u_paris/gla/project/AppTest.java index 2351945..cdb42b4 100644 --- a/src/test/java/fr/u_paris/gla/project/AppTest.java +++ b/src/test/java/fr/u_paris/gla/project/AppTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; class AppTest { /** Rigorous Test :-) */ @Test - void testPlaceholder() { + public void testPlaceholder() { assertTrue(true, "It should be true that true is true..."); } } diff --git a/src/test/java/fr/u_paris/gla/project/idfm/CSVStreamProviderTest.java b/src/test/java/fr/u_paris/gla/project/idfm/CSVStreamProviderTest.java new file mode 100644 index 0000000..0ed235a --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/CSVStreamProviderTest.java @@ -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 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."); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractorTest.java b/src/test/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractorTest.java new file mode 100644 index 0000000..7e6bffc --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/IDFMNetworkExtractorTest.java @@ -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> 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 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."); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/StopEntryTest.java b/src/test/java/fr/u_paris/gla/project/idfm/StopEntryTest.java new file mode 100644 index 0000000..deb31ce --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/StopEntryTest.java @@ -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); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/StopTest.java b/src/test/java/fr/u_paris/gla/project/idfm/StopTest.java new file mode 100644 index 0000000..6139466 --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/StopTest.java @@ -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")); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/TraceEntryTest.java b/src/test/java/fr/u_paris/gla/project/idfm/TraceEntryTest.java new file mode 100644 index 0000000..e53c1dc --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/TraceEntryTest.java @@ -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 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 path = Arrays.asList(stop1, stop2); + traceEntry.addPath(path); + List> 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'."); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/TransportTest.java b/src/test/java/fr/u_paris/gla/project/idfm/TransportTest.java new file mode 100644 index 0000000..59540cf --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/TransportTest.java @@ -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 visited = new ArrayList<>(); + List bifurcations = new ArrayList<>(); + SimpleEntry> result = transport.roadToLast("A", "D", visited, bifurcations); + assertFalse(result.getKey()); + assertEquals(List.of(1, 2, 3), result.getValue()); + }*/ + + /*@Test + public void testRoadToLastOptimized() { + SimpleEntry> 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")); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntryTest.java b/src/test/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntryTest.java new file mode 100644 index 0000000..d51a60f --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/idfm/UnidentifiedStopEntryTest.java @@ -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)); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/io/NetworkFormatTest.java b/src/test/java/fr/u_paris/gla/project/io/NetworkFormatTest.java new file mode 100644 index 0000000..4422575 --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/io/NetworkFormatTest.java @@ -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(" ","")); + + + } + + + +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/io/ScheduleFormatTest.java b/src/test/java/fr/u_paris/gla/project/io/ScheduleFormatTest.java new file mode 100644 index 0000000..152ffd9 --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/io/ScheduleFormatTest.java @@ -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 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); + + + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/utils/CSVToolsTest.java b/src/test/java/fr/u_paris/gla/project/utils/CSVToolsTest.java new file mode 100644 index 0000000..e40bca4 --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/utils/CSVToolsTest.java @@ -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 test = s -> System.out.println(Arrays.toString(s)); + CSVTools.readCSVFromURL("https://google.fr", + test); + + } + ); + */ + } + + @Test + public void testreadCSVFromURL_valid() { + assertDoesNotThrow(() -> { + Consumer 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 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 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 test = Arrays.stream(t); + CSVTools.writeCSVToFile(".", test); + + }); + } +} \ No newline at end of file diff --git a/src/test/java/fr/u_paris/gla/project/utils/GPSTest.java b/src/test/java/fr/u_paris/gla/project/utils/GPSTest.java new file mode 100644 index 0000000..ed1dc45 --- /dev/null +++ b/src/test/java/fr/u_paris/gla/project/utils/GPSTest.java @@ -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)); + } + + +} \ No newline at end of file