[refactor] Reduce complexity by refactoring
- Extracted csv related operations - Divided in smaller functions - Externalized classes
This commit is contained in:
parent
d4e04eaef6
commit
5406582d34
6 changed files with 490 additions and 234 deletions
168
src/main/java/fr/u_paris/gla/project/idfm/CSVStreamProvider.java
Normal file
168
src/main/java/fr/u_paris/gla/project/idfm/CSVStreamProvider.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,51 +3,31 @@
|
|||
*/
|
||||
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 java.util.stream.Stream;
|
||||
|
||||
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 fr.u_paris.gla.project.io.NetworkFormat;
|
||||
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 */
|
||||
public class IDFMNetworkExtractor {
|
||||
|
||||
/** The logger for information on the process */
|
||||
private static final Logger LOGGER = Logger
|
||||
.getLogger(IDFMNetworkExtractor.class.getName());
|
||||
|
@ -68,83 +48,10 @@ public class IDFMNetworkExtractor {
|
|||
|
||||
// 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;
|
||||
private static final int GUESS_STOPS_BY_LINE = 5;
|
||||
|
||||
// Well named constants
|
||||
private static final double _250_METERS = .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);
|
||||
}
|
||||
}
|
||||
private static final double QUARTER_KILOMETER = .25;
|
||||
|
||||
/** Main entry point for the extractor of IDF mobilite data into a network as
|
||||
* defined by this application.
|
||||
|
@ -159,165 +66,103 @@ public class IDFMNetworkExtractor {
|
|||
|
||||
Map<String, TraceEntry> traces = new HashMap<>();
|
||||
try {
|
||||
readCSVFromURL(TRACE_FILE_URL, (String[] line) -> {
|
||||
TraceEntry entry = new TraceEntry();
|
||||
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);
|
||||
}
|
||||
});
|
||||
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<StopEntry> 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<? 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);
|
||||
});
|
||||
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);
|
||||
|
||||
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<>();
|
||||
for (Entry<String, TraceEntry> traceEntry : traces.entrySet()) {
|
||||
TraceEntry trace = traceEntry.getValue();
|
||||
for (List<StopEntry> 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<String, TraceEntry> traceEntry : traces.entrySet()) {
|
||||
Map<StopEntry, Set<StopEntry>> lineSegments = new HashMap<>();
|
||||
String[] nextLine = new String[NetworkFormat.NUMBER_COLUMNS];
|
||||
nextLine[NetworkFormat.LINE_INDEX] = traceEntry.getValue().lname;
|
||||
for (List<StopEntry> path : traceEntry.getValue().stops) {
|
||||
for (int i = 0; i < path.size() - 1; i++) {
|
||||
StopEntry stop1 = path.get(i);
|
||||
lineSegments.putIfAbsent(stop1, new HashSet<>());
|
||||
StopEntry stop2 = path.get(i + 1);
|
||||
if (!lineSegments.get(stop1).contains(stop2)) {
|
||||
fillStation(stop1, nextLine, NetworkFormat.START_INDEX);
|
||||
fillStation(stop2, nextLine, NetworkFormat.STOP_INDEX);
|
||||
double distance = GPS.distance(stop1.latitude,
|
||||
stop1.longitude, stop2.latitude, stop2.longitude);
|
||||
nextLine[NetworkFormat.DISTANCE_INDEX] = NumberFormat
|
||||
.getInstance(Locale.ENGLISH).format(distance);
|
||||
nextLine[NetworkFormat.DURATION_INDEX] = formatTime(
|
||||
(long) Math.ceil(distanceToTime(distance)
|
||||
* SECONDS_IN_HOURS));
|
||||
nextLine[NetworkFormat.VARIANT_INDEX] = Integer
|
||||
.toString(lineSegments.get(stop1).size());
|
||||
csv.writeNext(nextLine);
|
||||
lineSegments.get(stop1).add(stop2);
|
||||
}
|
||||
}
|
||||
}
|
||||
/** @param path */
|
||||
private static boolean cleanLine(List<List<StopEntry>> stops) {
|
||||
for (List<StopEntry> path : stops) {
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
StopEntry stop = path.get(i);
|
||||
if (!(stop instanceof UnidentifiedStopEntry)) {
|
||||
continue;
|
||||
}
|
||||
UnidentifiedStopEntry unidentified = (UnidentifiedStopEntry) stop;
|
||||
StopEntry stopResolution = unidentified.resolve();
|
||||
if (stopResolution == null) {
|
||||
return false;
|
||||
}
|
||||
path.set(i, stopResolution);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @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);
|
||||
return trace;
|
||||
}
|
||||
|
||||
private static List<List<StopEntry>> buildPaths(String pathsJSON) {
|
||||
|
@ -341,7 +186,8 @@ 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;
|
||||
}
|
||||
|
|
71
src/main/java/fr/u_paris/gla/project/idfm/StopEntry.java
Normal file
71
src/main/java/fr/u_paris/gla/project/idfm/StopEntry.java
Normal 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);
|
||||
}
|
||||
}
|
34
src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java
Normal file
34
src/main/java/fr/u_paris/gla/project/idfm/TraceEntry.java
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
65
src/main/java/fr/u_paris/gla/project/utils/CSVTools.java
Normal file
65
src/main/java/fr/u_paris/gla/project/utils/CSVTools.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue