[refactor] Reduce complexity by refactoring

- Extracted csv related operations
- Divided in smaller functions
- Externalized classes
This commit is contained in:
Bigeon Emmanuel 2024-02-14 17:12:58 +01:00
parent d4e04eaef6
commit 5406582d34
6 changed files with 490 additions and 234 deletions

View file

@ -0,0 +1,168 @@
/**
*
*/
package fr.u_paris.gla.project.idfm;
import java.text.MessageFormat;
import java.text.NumberFormat;
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;
public final class CSVStreamProvider {
private static final NumberFormat GPS_FORMATTER = NetworkFormat
.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[NetworkFormat.NUMBER_COLUMNS];
private Iterator<TraceEntry> currentTrace;
private Iterator<List<StopEntry>> currentPath = Collections.emptyIterator();
private Iterator<StopEntry> currentSegmentStart = Collections.emptyIterator();
private Iterator<StopEntry> currentSegmentEnd = Collections.emptyIterator();
Map<StopEntry, Set<StopEntry>> lineSegments = new HashMap<>();
private StopEntry start = null;
private StopEntry end = null;
private boolean hasNext = false;
private boolean onNext = false;
/** Create the stream provider */
public CSVStreamProvider(Iterator<TraceEntry> 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[NetworkFormat.LINE_INDEX] = trace.lname;
this.lineSegments.clear();
}
List<StopEntry> path = this.currentPath.next();
this.currentSegmentEnd = path.iterator();
if (this.currentSegmentEnd.hasNext()) {
this.currentSegmentEnd.next();
this.currentSegmentStart = path.iterator();
}
} while (currentSegmentStart == null);
}
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));
this.line[NetworkFormat.VARIANT_INDEX] = Integer
.toString(this.lineSegments.get(this.start).size() - 1);
return Arrays.copyOf(this.line, this.line.length);
}
/** @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}", //$NON-NLS-1$
GPS_FORMATTER.format(stop.latitude),
GPS_FORMATTER.format(stop.longitude));
}
/** @param distanceToTime
* @return */
private static String formatTime(long time) {
return MessageFormat.format("{0}:{1}", //$NON-NLS-1$
MINUTES_SECOND_FORMATTER.format(time / SECONDS_IN_MINUTES), MINUTES_SECOND_FORMATTER.format(time % SECONDS_IN_MINUTES));
}
/** A tool method to give a delay to go through a certain distance.
* <p>
* This is a model with an linear acceleration and deceleration periods and a
* constant speed in between.
*
* @param distance the distance (in km)
* @return the duration of the trip (in hours) */
private static double distanceToTime(double distance) {
return Math.max(0, distance - TWO_ACCELERATION_DISTANCE) / MAX_SPEED
+ Math.pow(Math.min(distance, TWO_ACCELERATION_DISTANCE) / MAX_SPEED, 2);
}
}

View file

@ -3,51 +3,31 @@
*/ */
package fr.u_paris.gla.project.idfm; package fr.u_paris.gla.project.idfm;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException; 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.MessageFormat;
import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Stream;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import com.opencsv.CSVParserBuilder; import fr.u_paris.gla.project.utils.CSVTools;
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 fr.u_paris.gla.project.io.NetworkFormat;
import fr.u_paris.gla.project.utils.GPS; import fr.u_paris.gla.project.utils.GPS;
/** Code of an extractor for the data from IDF mobilite. /** Code of an extractor for the data from IDF mobilite.
* *
* @author Emmanuel Bigeon */ * @author Emmanuel Bigeon */
public class IDFMNetworkExtractor { public class IDFMNetworkExtractor {
/** The logger for information on the process */ /** The logger for information on the process */
private static final Logger LOGGER = Logger private static final Logger LOGGER = Logger
.getLogger(IDFMNetworkExtractor.class.getName()); .getLogger(IDFMNetworkExtractor.class.getName());
@ -68,83 +48,10 @@ public class IDFMNetworkExtractor {
// Magically chosen values // Magically chosen values
/** A number of stops on each line */ /** A number of stops on each line */
private static final int GUESS_STOPS_BY_LINE = 5; private static final int GUESS_STOPS_BY_LINE = 5;
/** Maximal speed in km/h */
private static final double MAX_SPEED = 5;
/** Distance to reach maximal speed in km */
private static final double ACCELERATION_DISTANCE = 0.1;
// Well named constants // Well named constants
private static final double _250_METERS = .25; private static final double QUARTER_KILOMETER = .25;
private static final long SECONDS_IN_HOURS = 3_600;
private static final Format GPS_FORMATTER = NetworkFormat.getGPSFormatter();
public static class StopEntry {
private String lname;
public final double longitude;
public final double latitude;
/** Create the stop
*
* @param lname
* @param longitude
* @param latitude */
public StopEntry(String lname, double longitude, double latitude) {
super();
this.lname = lname;
this.longitude = longitude;
this.latitude = latitude;
}
@Override
public String toString() {
return MessageFormat.format("{0} [{1}, {2}]", this.lname, this.longitude,
this.latitude);
}
}
public static final class UnidentifiedStopEntry extends StopEntry {
/** Create the stop
*
* @param longitude
* @param latitude */
public UnidentifiedStopEntry(double longitude, double latitude) {
super("Unidentified", longitude, latitude);
}
List<StopEntry> candidates = new ArrayList<>();
@Override
public String toString() {
return "UnidentifiedStop [candidates=" + this.candidates + "]";
}
}
private static final class TraceEntry {
String lname;
List<List<StopEntry>> stops = new ArrayList<>();
}
public static void readCSVFromURL(String url, Consumer<String[]> contentLineConsumer)
throws IOException {
ICSVParser parser = new CSVParserBuilder().withSeparator(';').build();
try (InputStream is = new URL(url).openStream();
Reader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
CSVReaderBuilder csvBuilder = new CSVReaderBuilder(reader)
.withCSVParser(parser);
try (CSVReader csv = csvBuilder.build()) {
String[] line = csv.readNextSilently(); // Eliminate header
while (csv.peek() != null) {
line = csv.readNext();
contentLineConsumer.accept(line);
}
}
} catch (CsvValidationException e) {
throw new IOException("Invalid csv file for lines", e);
}
}
/** Main entry point for the extractor of IDF mobilite data into a network as /** Main entry point for the extractor of IDF mobilite data into a network as
* defined by this application. * defined by this application.
@ -159,165 +66,103 @@ public class IDFMNetworkExtractor {
Map<String, TraceEntry> traces = new HashMap<>(); Map<String, TraceEntry> traces = new HashMap<>();
try { try {
readCSVFromURL(TRACE_FILE_URL, (String[] line) -> { CSVTools.readCSVFromURL(TRACE_FILE_URL,
TraceEntry entry = new TraceEntry(); (String[] line) -> addLine(line, traces));
entry.lname = line[IDFM_TRACE_SNAME_INDEX];
List<List<StopEntry>> buildPaths = buildPaths(
line[IDFM_TRACE_SHAPE_INDEX]);
entry.stops.addAll(buildPaths);
if (buildPaths.isEmpty()) {
LOGGER.severe(() -> MessageFormat.format(
"Line {0} has no provided itinerary and was ignored",
entry.lname));
} else {
traces.put(line[IDFM_TRACE_ID_INDEX], entry);
}
});
} catch (IOException e) { } catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the line paths", e); LOGGER.log(Level.SEVERE, "Error while reading the line paths", e);
} }
List<StopEntry> stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE); List<StopEntry> stops = new ArrayList<>(traces.size() * GUESS_STOPS_BY_LINE);
try { try {
readCSVFromURL(STOPS_FILE_URL, (String[] line) -> { CSVTools.readCSVFromURL(STOPS_FILE_URL,
StopEntry entry = new StopEntry(line[IDFM_STOPS_NAME_INDEX], (String[] line) -> addStop(line, traces, stops));
Double.parseDouble(line[IDFM_STOPS_LON_INDEX]),
Double.parseDouble(line[IDFM_STOPS_LAT_INDEX]));
String rid = line[IDFM_STOPS_RID_INDEX];
BiFunction<? super String, ? super TraceEntry, ? extends TraceEntry> func = (
k, trace) -> {
for (List<StopEntry> path : trace.stops) {
for (StopEntry stopEntry : path) {
if (stopEntry instanceof UnidentifiedStopEntry
&& GPS.distance(entry.latitude, entry.longitude,
stopEntry.latitude,
stopEntry.longitude) < _250_METERS) {
((UnidentifiedStopEntry) stopEntry).candidates.add(entry);
}
}
}
return trace;
};
traces.computeIfPresent(rid, func);
stops.add(entry);
});
} catch (IOException e) { } catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error while reading the stops", e); LOGGER.log(Level.SEVERE, "Error while reading the stops", e);
} }
cleanTraces(traces);
CSVStreamProvider provider = new CSVStreamProvider(traces.values().iterator());
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]));
}
}
private static void cleanTraces(Map<String, TraceEntry> traces) {
Set<String> toRemove = new HashSet<>(); Set<String> toRemove = new HashSet<>();
for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) { for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) {
TraceEntry trace = traceEntry.getValue(); TraceEntry trace = traceEntry.getValue();
for (List<StopEntry> path : trace.stops) { if (!cleanLine(trace.getPaths())) {
for (int i = 0; i < path.size(); i++) { LOGGER.severe(() -> MessageFormat.format(
StopEntry stop = path.get(i); "Missing stop for line {0}. Line will be removed", trace.lname));
if (stop instanceof UnidentifiedStopEntry) toRemove.add(traceEntry.getKey());
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);
}
} }
} }
for (String string : toRemove) { for (String string : toRemove) {
traces.remove(string); traces.remove(string);
} }
}
// Export content in required format /** @param path */
try (FileWriter writer = new FileWriter(args[0], StandardCharsets.UTF_8)) { private static boolean cleanLine(List<List<StopEntry>> stops) {
CSVWriterBuilder wBuilder = new CSVWriterBuilder(writer).withSeparator(';'); for (List<StopEntry> path : stops) {
try (ICSVWriter csv = wBuilder.build()) { for (int i = 0; i < path.size(); i++) {
for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) { StopEntry stop = path.get(i);
Map<StopEntry, Set<StopEntry>> lineSegments = new HashMap<>(); if (!(stop instanceof UnidentifiedStopEntry)) {
String[] nextLine = new String[NetworkFormat.NUMBER_COLUMNS]; continue;
nextLine[NetworkFormat.LINE_INDEX] = traceEntry.getValue().lname; }
for (List<StopEntry> path : traceEntry.getValue().stops) { UnidentifiedStopEntry unidentified = (UnidentifiedStopEntry) stop;
for (int i = 0; i < path.size() - 1; i++) { StopEntry stopResolution = unidentified.resolve();
StopEntry stop1 = path.get(i); if (stopResolution == null) {
lineSegments.putIfAbsent(stop1, new HashSet<>()); return false;
StopEntry stop2 = path.get(i + 1); }
if (!lineSegments.get(stop1).contains(stop2)) { path.set(i, stopResolution);
fillStation(stop1, nextLine, NetworkFormat.START_INDEX); }
fillStation(stop2, nextLine, NetworkFormat.STOP_INDEX); }
double distance = GPS.distance(stop1.latitude, return true;
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);
}
}
}
private static void addStop(String[] line, Map<String, TraceEntry> traces,
List<StopEntry> stops) {
StopEntry entry = new StopEntry(line[IDFM_STOPS_NAME_INDEX],
Double.parseDouble(line[IDFM_STOPS_LON_INDEX]),
Double.parseDouble(line[IDFM_STOPS_LAT_INDEX]));
String rid = line[IDFM_STOPS_RID_INDEX];
traces.computeIfPresent(rid,
(String k, TraceEntry trace) -> addCandidate(trace, entry));
stops.add(entry);
}
private static void addLine(String[] line, Map<String, TraceEntry> traces) {
TraceEntry entry = new TraceEntry(line[IDFM_TRACE_SNAME_INDEX]);
List<List<StopEntry>> buildPaths = buildPaths(line[IDFM_TRACE_SHAPE_INDEX]);
entry.getPaths().addAll(buildPaths);
if (buildPaths.isEmpty()) {
LOGGER.severe(() -> MessageFormat.format(
"Line {0} has no provided itinerary and was ignored", entry.lname));
} else {
traces.put(line[IDFM_TRACE_ID_INDEX], entry);
}
}
private static TraceEntry addCandidate(TraceEntry trace, StopEntry entry) {
for (List<StopEntry> 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.
* <p>
* This is a model with an linear acceleration and deceleration periods and a
* constant speed in between.
*
* @param distance the distance (in km)
* @return the duration of the trip (in hours) */
private static double distanceToTime(double distance) {
return Math.max(0, distance - 2 * ACCELERATION_DISTANCE) / MAX_SPEED
+ Math.pow(Math.min(distance, 2 * ACCELERATION_DISTANCE) / MAX_SPEED, 2);
}
/** @param stop1
* @param nextLine
* @param i */
private static void fillStation(StopEntry stop, String[] nextLine, int index) {
nextLine[index] = stop.lname;
nextLine[index + 1] = MessageFormat.format("{0}, {1}",
GPS_FORMATTER.format(stop.latitude),
GPS_FORMATTER.format(stop.longitude));
}
/** @param stop
* @return */
private static StopEntry resolve(UnidentifiedStopEntry stop) {
if (stop.candidates.isEmpty()) {
LOGGER.severe("Unable to find stop name, will use a placeholder");
return stop;
}
if (stop.candidates.size() == 1) {
return stop.candidates.get(0);
}
Collections.sort(stop.candidates,
(Comparator<? super StopEntry>) (StopEntry s1,
StopEntry s2) -> (int) Math.signum((GPS.distance(stop.latitude,
stop.longitude, s1.latitude, s1.longitude)
- GPS.distance(stop.latitude, stop.longitude, s2.latitude,
s2.longitude))));
return stop.candidates.get(0);
} }
private static List<List<StopEntry>> buildPaths(String pathsJSON) { private static List<List<StopEntry>> buildPaths(String pathsJSON) {
@ -341,7 +186,8 @@ public class IDFMNetworkExtractor {
} }
} catch (JSONException e) { } catch (JSONException e) {
// Ignoring invalid element! // Ignoring invalid element!
LOGGER.log(Level.FINE, "Invalid json element " + pathsJSON, e); //$NON-NLS-1$ LOGGER.log(Level.FINE, e,
() -> MessageFormat.format("Invalid json element {0}", pathsJSON)); //$NON-NLS-1$
} }
return all; return all;
} }

View file

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

View file

@ -0,0 +1,34 @@
/**
*
*/
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 */
public final class TraceEntry {
public final String lname;
private List<List<StopEntry>> paths = new ArrayList<>();
/** Create a transport line.
*
* @param lname the name of the line */
public TraceEntry(String lname) {
super();
this.lname = lname;
}
// FIXME list of lists are bad practice in direct access...
/** @return the list of paths */
public List<List<StopEntry>> getPaths() {
return Collections.unmodifiableList(paths);
}
public void addPath(List<StopEntry> path) {
paths.add(new ArrayList<>(path));
}
}

View file

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

View file

@ -0,0 +1,65 @@
/**
*
*/
package fr.u_paris.gla.project.utils;
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.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
}
public static void readCSVFromURL(String url, Consumer<String[]> contentLineConsumer)
throws IOException {
ICSVParser parser = new CSVParserBuilder().withSeparator(';').build();
try (InputStream is = new URL(url).openStream();
Reader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
CSVReaderBuilder csvBuilder = new CSVReaderBuilder(reader)
.withCSVParser(parser);
try (CSVReader csv = csvBuilder.build()) {
String[] line = csv.readNextSilently(); // Eliminate header
while (csv.peek() != null) {
line = csv.readNext();
contentLineConsumer.accept(line);
}
}
} catch (CsvValidationException e) {
throw new IOException("Invalid csv file", e); //$NON-NLS-1$
}
}
public static void writeCSVToFile(String filename,
Stream<String[]> contentLineConsumer) throws IOException {
try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) {
CSVWriterBuilder wBuilder = new CSVWriterBuilder(writer).withSeparator(';');
try (ICSVWriter csv = wBuilder.build()) {
contentLineConsumer.forEachOrdered(csv::writeNext);
}
}
}
}