From 2f0114dac84d53acd4f652283c9f63a955717d31 Mon Sep 17 00:00:00 2001 From: Mylloon Date: Fri, 8 Mar 2024 23:35:51 +0100 Subject: [PATCH] Add departures Co-authored-by: Ange Herman KOUE-HEMAZRO --- .../idfm/CSVStreamSchedulesProvider.java | 140 ++++++++++++++++++ .../project/idfm/IDFMNetworkExtractor.java | 70 +++++++-- .../u_paris/gla/project/idfm/TraceEntry.java | 32 ++-- .../gla/project/io/ScheduleFormat.java | 28 ++-- 4 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 src/main/java/fr/u_paris/gla/project/idfm/CSVStreamSchedulesProvider.java diff --git a/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamSchedulesProvider.java b/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamSchedulesProvider.java new file mode 100644 index 0000000..a5c91eb --- /dev/null +++ b/src/main/java/fr/u_paris/gla/project/idfm/CSVStreamSchedulesProvider.java @@ -0,0 +1,140 @@ +package fr.u_paris.gla.project.idfm; + +import java.util.Arrays; +import java.util.Iterator; + +import fr.u_paris.gla.project.io.ScheduleFormat; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import fr.u_paris.gla.project.utils.GPS; + +public class CSVStreamSchedulesProvider { + + /* + * private static final NumberFormat GPS_FORMATTER = ScheduleFormat + * .getGPSFormatter(); + */ + 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; + 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; + + private String[] line = new String[ScheduleFormat.NUMBER_COLUMNS]; + + private Iterator currentTrace; + private Iterator> currentPath = Collections.emptyIterator(); + private Iterator currentSegmentStart = Collections.emptyIterator(); + private Iterator currentSegmentEnd = Collections.emptyIterator(); + Map> lineSegments = new HashMap<>(); + + private StopEntry start = null; + private StopEntry end = null; + + private boolean hasNext = false; + private boolean onNext = false; + + /** Create the stream provider */ + public CSVStreamSchedulesProvider(Iterator traces) { + this.currentTrace = traces; + } + + public boolean hasNext() { + if (!this.onNext) { + skipToNext(); + } + return this.hasNext; + } + + private void skipToNext() { + if (this.onNext) { + return; + } + while (!this.onNext) { + if (!this.currentSegmentEnd.hasNext()) { + skipToNextCandidatePath(); + } + if (this.onNext) { + return; + } + skipToNextNewSegment(); + } + } + + 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.currentPath = trace.getPaths().iterator(); + this.line[ScheduleFormat.LINE_INDEX] = trace.lname; + this.line[ScheduleFormat.TERMINUS_INDEX] = trace.getTerminus().get(0); + 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); + } + + public String[] next() { + if (!this.onNext) { + skipToNext(); + } + this.onNext = false; + + this.line[ScheduleFormat.TIME_INDEX] = null; + + /* + * this.line[ScheduleFormat.DISTANCE_INDEX] = + * NumberFormat.getInstance(Locale.ENGLISH) + * .format(distance); + * this.line[ScheduleFormat.VARIANT_INDEX] = Integer + * .toString(this.lineSegments.get(this.start).size() - 1); + */ + + return Arrays.copyOf(this.line, this.line.length); + } +} 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 4d63584..71d08f5 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,5 +1,5 @@ /** - * + * */ package fr.u_paris.gla.project.idfm; @@ -23,9 +23,11 @@ import org.json.JSONObject; import fr.u_paris.gla.project.utils.CSVTools; 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 */ @@ -37,14 +39,15 @@ public class IDFMNetworkExtractor { 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"; // IDF mobilite csv formats - private static final int IDFM_TRACE_ID_INDEX = 0; + private static final int IDFM_TRACE_ID_INDEX = 0; private static final int IDFM_TRACE_SNAME_INDEX = 1; private static final int IDFM_TRACE_SHAPE_INDEX = 6; - private static final int IDFM_STOPS_RID_INDEX = 0; + private static final int IDFM_STOPS_RID_INDEX = 0; + private static final int IDFM_STOPS_SCHEDULES_INDEX = 3; 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; + private static final int IDFM_STOPS_LON_INDEX = 6; + private static final int IDFM_STOPS_LAT_INDEX = 7; // Magically chosen values /** A number of stops on each line */ @@ -53,14 +56,16 @@ public class IDFMNetworkExtractor { // Well named constants private static final double QUARTER_KILOMETER = .25; - /** Main entry point for the extractor of IDF mobilite data into a network as + /** + * Main entry point for the extractor of IDF mobilite 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; } @@ -91,6 +96,20 @@ public class IDFMNetworkExtractor { 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)); + + 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])); + } } private static void cleanTraces(Map traces) { @@ -134,6 +153,10 @@ public class IDFMNetworkExtractor { Double.parseDouble(line[IDFM_STOPS_LON_INDEX]), Double.parseDouble(line[IDFM_STOPS_LAT_INDEX])); String rid = line[IDFM_STOPS_RID_INDEX]; + if (traces.keySet().contains(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); @@ -191,4 +214,25 @@ public class IDFMNetworkExtractor { } return all; } + + private static List extractTerminus(String pathsJSON) { + List all = new ArrayList<>(); + try { + JSONArray schedules = new JSONArray(pathsJSON); + 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}", pathsJSON)); //$NON-NLS-1$ + } + + return all; + } } 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 index ee0f2e7..6e271c4 100644 --- a/src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java +++ b/src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java @@ -1,22 +1,27 @@ /** - * + * */ package fr.u_paris.gla.project.idfm; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -/** Representation of a transport line - * - * @author Emmanuel Bigeon */ +/** + * Representation of a transport line + * + * @author Emmanuel Bigeon + */ public final class TraceEntry { - public final String lname; + public final String lname; + + private List terminus = new ArrayList<>(); private List> paths = new ArrayList<>(); - /** Create a transport line. - * - * @param lname the name of the line */ + /** + * Create a transport line. + * + * @param lname the name of the line + */ public TraceEntry(String lname) { super(); this.lname = lname; @@ -29,7 +34,16 @@ public final class TraceEntry { 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); + } } 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..fc4d4ba 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,5 +1,5 @@ /** - * + * */ package fr.u_paris.gla.project.io; @@ -7,30 +7,36 @@ import java.time.format.DateTimeFormatter; import java.time.format.ResolverStyle; 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; + public static final int NUMBER_COLUMNS = 4; + + public static final int LINE_INDEX = 0; public static final int TRIP_SEQUENCE_INDEX = 1; - public static final int TERMINUS_INDEX = 2; - public static final int TIME_INDEX = 3; + public static final int TERMINUS_INDEX = 2; + 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"); } - + public static DateTimeFormatter getTimeFormatter() { return DateTimeFormatter.ofPattern("HH:mm").withResolverStyle(ResolverStyle.LENIENT); }