Skip to content

Commit

Permalink
restructured travel time api
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Aug 14, 2023
1 parent 4ba2874 commit de5c4e6
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 147 deletions.
70 changes: 15 additions & 55 deletions src/main/java/org/matsim/prepare/network/FreeSpeedOptimizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime;
import org.matsim.core.utils.geometry.CoordUtils;
import org.matsim.prepare.traveltime.SampleValidationRoutes;
import picocli.CommandLine;

import java.io.File;
Expand Down Expand Up @@ -72,7 +73,7 @@ public class FreeSpeedOptimizer implements MATSimAppCommand {
private List<String> validationFiles;

private Network network;
private Object2DoubleMap<Entry> validationSet;
private Object2DoubleMap<SampleValidationRoutes.FromToNodes> validationSet;
private Map<Id<Link>, PrepareNetworkParams.Feature> features;

private ObjectMapper mapper;
Expand Down Expand Up @@ -198,9 +199,9 @@ private Result evaluateNetwork(Request request, String save) throws IOException
List<Data> rbl = new ArrayList<>();
List<Data> traffic_light = new ArrayList<>();

for (Object2DoubleMap.Entry<Entry> e : validationSet.object2DoubleEntrySet()) {
for (Object2DoubleMap.Entry<SampleValidationRoutes.FromToNodes> e : validationSet.object2DoubleEntrySet()) {

Entry r = e.getKey();
SampleValidationRoutes.FromToNodes r = e.getKey();

Node fromNode = network.getNodes().get(r.fromNode());
Node toNode = network.getNodes().get(r.toNode());
Expand Down Expand Up @@ -249,72 +250,31 @@ private Result evaluateNetwork(Request request, String save) throws IOException
}

/**
* Collect highest observed speed.
* Calculate the target speed.
*/
static Object2DoubleMap<Entry> readValidation(List<String> validationFiles) throws IOException {
static Object2DoubleMap<SampleValidationRoutes.FromToNodes> readValidation(List<String> validationFiles) throws IOException {

// entry to hour and list of speeds
Map<Entry, Int2ObjectMap<DoubleList>> entries = new LinkedHashMap<>();
Map<SampleValidationRoutes.FromToNodes, Int2ObjectMap<DoubleList>> entries = SampleValidationRoutes.readValidation(validationFiles);

if (validationFiles != null)
for (String file : validationFiles) {
Object2DoubleMap<SampleValidationRoutes.FromToNodes> result = new Object2DoubleOpenHashMap<>();

log.info("Loading {}", file);
// Target values
for (Map.Entry<SampleValidationRoutes.FromToNodes, Int2ObjectMap<DoubleList>> e : entries.entrySet()) {

try (CSVParser parser = new CSVParser(Files.newBufferedReader(Path.of(file)),
CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build())) {
Int2ObjectMap<DoubleList> perHour = e.getValue();

for (CSVRecord r : parser) {
Entry e = new Entry(Id.createNodeId(r.get("from_node")), Id.createNodeId(r.get("to_node")));
double speed = Double.parseDouble(r.get("dist")) / Double.parseDouble(r.get("travel_time"));
// Use avg from all values for 3:00 and 21:00
double avg = DoubleStream.concat(perHour.get(3).doubleStream(), perHour.get(21).doubleStream())
.average().orElseThrow();

if (!Double.isFinite(speed)) {
log.warn("Invalid entry {}", r);
continue;
}

Int2ObjectMap<DoubleList> perHour = entries.computeIfAbsent(e, (k) -> new Int2ObjectLinkedOpenHashMap<>());
perHour.computeIfAbsent(Integer.parseInt(r.get("hour")), k -> new DoubleArrayList()).add(speed);
}
}
}

Object2DoubleMap<Entry> result = new Object2DoubleOpenHashMap<>();

try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(Path.of("routes-ref.csv")), CSVFormat.DEFAULT)) {

printer.printRecord("from_node", "to_node", "hour", "min", "max", "mean", "std");

// Target values
for (Map.Entry<Entry, Int2ObjectMap<DoubleList>> e : entries.entrySet()) {

Int2ObjectMap<DoubleList> perHour = e.getValue();

// Use avg from all values for 3:00 and 21:00
double avg = DoubleStream.concat(perHour.get(3).doubleStream(), perHour.get(21).doubleStream())
.average().orElseThrow();


for (Int2ObjectMap.Entry<DoubleList> e2 : perHour.int2ObjectEntrySet()) {

SummaryStatistics stats = new SummaryStatistics();
// This is as kmh
e2.getValue().forEach(v -> stats.addValue(v * 3.6));

printer.printRecord(e.getKey().fromNode, e.getKey().toNode, e2.getIntKey(),
stats.getMin(), stats.getMax(), stats.getMean(), stats.getStandardDeviation());
}

result.put(e.getKey(), avg);
}
result.put(e.getKey(), avg);
}

return result;
}

private record Entry(Id<Node> fromNode, Id<Node> toNode) {
}

private record Data(double[] x, double yPred, double yTrue) {

}
Expand Down
138 changes: 138 additions & 0 deletions src/main/java/org/matsim/prepare/traveltime/FetchRoutesTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.matsim.prepare.traveltime;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Node;
import org.matsim.prepare.traveltime.api.*;

import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Task to fetch routes from api service.
*/
final class FetchRoutesTask implements Runnable {

private static final Logger log = LogManager.getLogger(FetchRoutesTask.class);

private final RouteApi api;
private final String apiKey;

private final List<SampleValidationRoutes.Route> routes;
private final List<Integer> hours;
private final Path out;

FetchRoutesTask(RouteApi api, String apiKey, List<SampleValidationRoutes.Route> routes, List<Integer> hours, Path out) {
this.api = api;
this.apiKey = apiKey;
this.routes = routes;
this.hours = hours;
this.out = out;
}


private void fetch() throws Exception {

// Collect existing entries to support resuming
Set<Entry> entries = new HashSet<>();
OpenOption open;
if (Files.exists(out)) {
open = StandardOpenOption.APPEND;
try (CSVParser csv = new CSVParser(Files.newBufferedReader(out), CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build())) {
for (CSVRecord r : csv) {
entries.add(new Entry(
Id.createNodeId(r.get("from_node")),
Id.createNodeId(r.get("to_node")),
r.get("api"),
Integer.parseInt(r.get("hour")))
);
}
}

} else
open = StandardOpenOption.CREATE_NEW;

try (RouteValidator val = switch (api) {
case google -> new GoogleRouteValidator(apiKey);
case woosmap -> new WoosMapRouteValidator(apiKey);
case mapbox -> new MapboxRouteValidator(apiKey);
case here -> new HereRouteValidator(apiKey);
case tomtom -> new TomTomRouteValidator(apiKey);
}) {

try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(out, open), CSVFormat.DEFAULT)) {

if (open == StandardOpenOption.CREATE_NEW) {
csv.printRecord("from_node", "to_node", "api", "hour", "dist", "travel_time");
csv.flush();
}

int i = 0;
for (SampleValidationRoutes.Route route : routes) {
for (int h : hours) {

// Skip entries already present
Entry e = new Entry(route.fromNode(), route.toNode(), val.name(), h);
if (entries.contains(e))
continue;

try {
RouteValidator.Result res = fetchWithBackoff(val, route, h, 0);
csv.printRecord(route.fromNode(), route.toNode(), val.name(), h, res.dist(), res.travelTime());
} catch (Exception ex) {
log.warn("Could not retrieve result for route {} {}", api, route, ex);
}
}

csv.flush();

if (i++ % 50 == 0) {
log.info("{}: processed {} routes", api, i - 1);
}
}
}
}
}

/**
* Fetch route with increasing delay.
*/
private RouteValidator.Result fetchWithBackoff(RouteValidator val, SampleValidationRoutes.Route route, int h, int i) throws InterruptedException {
try {
return val.retrieve(route.from(), route.to(), h);
} catch (Exception e) {
if (i < 3) {
long backoff = (long) (10000d * Math.pow(2, i));
log.warn("Failed to fetch result for {} {}: {}, (retrying after {}s)", api, route, e.getMessage(), backoff / 1000);

Thread.sleep(backoff);
return fetchWithBackoff(val, route, h, ++i);
}

throw e;
}
}

@Override
public void run() {
try {
fetch();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

record Entry(Id<Node> fromNode, Id<Node> toNode, String api, int hour) {
}

}
12 changes: 12 additions & 0 deletions src/main/java/org/matsim/prepare/traveltime/RouteApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.matsim.prepare.traveltime;

/**
* Defines different available API services.
*/
public enum RouteApi {
google,
woosmap,
mapbox,
here,
tomtom
}
Loading

0 comments on commit de5c4e6

Please sign in to comment.