|
19 | 19 | import org.matsim.api.core.v01.network.Node;
|
20 | 20 | import org.matsim.application.CommandSpec;
|
21 | 21 | import org.matsim.application.MATSimAppCommand;
|
22 |
| -import org.matsim.application.options.CrsOptions; |
23 |
| -import org.matsim.application.options.InputOptions; |
24 |
| -import org.matsim.application.options.OutputOptions; |
25 |
| -import org.matsim.application.options.ShpOptions; |
| 22 | +import org.matsim.application.options.*; |
26 | 23 | import org.matsim.application.prepare.network.SampleNetwork;
|
27 | 24 | import org.matsim.core.network.NetworkUtils;
|
28 | 25 | import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility;
|
|
32 | 29 | import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime;
|
33 | 30 | import org.matsim.core.utils.geometry.CoordUtils;
|
34 | 31 | import org.matsim.core.utils.geometry.transformations.GeotoolsTransformation;
|
| 32 | +import org.matsim.core.utils.io.IOUtils; |
35 | 33 | import picocli.CommandLine;
|
36 | 34 |
|
37 | 35 | import java.io.IOException;
|
| 36 | +import java.io.UncheckedIOException; |
38 | 37 | import java.nio.file.Files;
|
39 | 38 | import java.nio.file.Path;
|
40 | 39 | import java.util.*;
|
@@ -86,6 +85,8 @@ public class SampleValidationRoutes implements MATSimAppCommand {
|
86 | 85 | @CommandLine.Option(names = "--mode", description = "Mode to validate", defaultValue = TransportMode.car)
|
87 | 86 | private String mode;
|
88 | 87 |
|
| 88 | + @CommandLine.Option(names = "--input-od", description = "Use input fromNode,toNode instead of sampling", required = false) |
| 89 | + private String inputOD; |
89 | 90 |
|
90 | 91 | public static void main(String[] args) {
|
91 | 92 | new SampleValidationRoutes().execute(args);
|
@@ -142,9 +143,15 @@ public Integer call() throws Exception {
|
142 | 143 | OnlyTimeDependentTravelDisutility util = new OnlyTimeDependentTravelDisutility(tt);
|
143 | 144 | LeastCostPathCalculator router = new SpeedyALTFactory().createPathCalculator(network, util, tt);
|
144 | 145 |
|
145 |
| - List<Route> routes = sampleRoutes(network, router, rnd); |
146 | 146 |
|
147 |
| - log.info("Sampled {} routes in range {}", routes.size(), distRange); |
| 147 | + List<Route> routes; |
| 148 | + if (inputOD != null) { |
| 149 | + log.info("Using input OD file {}", inputOD); |
| 150 | + routes = queryRoutes(network, router); |
| 151 | + } else { |
| 152 | + routes = sampleRoutes(network, router, rnd); |
| 153 | + log.info("Sampled {} routes in range {}", routes.size(), distRange); |
| 154 | + } |
148 | 155 |
|
149 | 156 | try (CSVPrinter csv = new CSVPrinter(Files.newBufferedWriter(output.getPath()), CSVFormat.DEFAULT)) {
|
150 | 157 | csv.printRecord("from_node", "to_node", "beeline_dist", "dist", "travel_time", "geometry");
|
@@ -228,6 +235,7 @@ private List<Route> sampleRoutes(Network network, LeastCostPathCalculator router
|
228 | 235 | ShpOptions.Index index = shp.isDefined() ? shp.createIndex(crs, "_") : null;
|
229 | 236 | Predicate<Link> exclude = excludeRoads != null && !excludeRoads.isBlank() ? new Predicate<>() {
|
230 | 237 | final Pattern p = Pattern.compile(excludeRoads, Pattern.CASE_INSENSITIVE);
|
| 238 | + |
231 | 239 | @Override
|
232 | 240 | public boolean test(Link link) {
|
233 | 241 | return p.matcher(NetworkUtils.getHighwayType(link)).find();
|
@@ -282,6 +290,59 @@ public boolean test(Link link) {
|
282 | 290 | return result;
|
283 | 291 | }
|
284 | 292 |
|
| 293 | + /** |
| 294 | + * Use given od pairs as input for validation. |
| 295 | + */ |
| 296 | + private List<Route> queryRoutes(Network network, LeastCostPathCalculator router) { |
| 297 | + |
| 298 | + List<Route> result = new ArrayList<>(); |
| 299 | + String crs = ProjectionUtils.getCRS(network); |
| 300 | + |
| 301 | + if (this.crs.getInputCRS() != null) |
| 302 | + crs = this.crs.getInputCRS(); |
| 303 | + |
| 304 | + if (crs == null) { |
| 305 | + throw new IllegalArgumentException("Input CRS could not be detected. Please specify with --input-crs [EPSG:xxx]"); |
| 306 | + } |
| 307 | + |
| 308 | + GeotoolsTransformation ct = new GeotoolsTransformation(crs, "EPSG:4326"); |
| 309 | + |
| 310 | + try (CSVParser parser = CSVParser.parse(IOUtils.getBufferedReader(inputOD), CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true). |
| 311 | + setDelimiter(CsvOptions.detectDelimiter(inputOD)).build())) { |
| 312 | + |
| 313 | + List<String> header = parser.getHeaderNames(); |
| 314 | + if (!header.contains("from_node")) |
| 315 | + throw new IllegalArgumentException("Missing 'from_node' column in input file"); |
| 316 | + if (!header.contains("to_node")) |
| 317 | + throw new IllegalArgumentException("Missing 'to_node' column in input file"); |
| 318 | + |
| 319 | + for (CSVRecord r : parser) { |
| 320 | + Node fromNode = network.getNodes().get(Id.createNodeId(r.get("from_node"))); |
| 321 | + Node toNode = network.getNodes().get(Id.createNodeId(r.get("to_node"))); |
| 322 | + |
| 323 | + if (fromNode == null) |
| 324 | + throw new IllegalArgumentException("Node " + r.get("from_node") + " not found"); |
| 325 | + if (toNode == null) |
| 326 | + throw new IllegalArgumentException("Node " + r.get("to_node") + " not found"); |
| 327 | + |
| 328 | + LeastCostPathCalculator.Path path = router.calcLeastCostPath(fromNode, toNode, 0, null, null); |
| 329 | + result.add(new Route( |
| 330 | + fromNode.getId(), |
| 331 | + toNode.getId(), |
| 332 | + ct.transform(fromNode.getCoord()), |
| 333 | + ct.transform(toNode.getCoord()), |
| 334 | + path.travelTime, |
| 335 | + path.links.stream().mapToDouble(Link::getLength).sum() |
| 336 | + )); |
| 337 | + } |
| 338 | + |
| 339 | + } catch (IOException e) { |
| 340 | + throw new UncheckedIOException(e); |
| 341 | + } |
| 342 | + |
| 343 | + return result; |
| 344 | + } |
| 345 | + |
285 | 346 | /**
|
286 | 347 | * Key as pair of from and to node.
|
287 | 348 | */
|
|
0 commit comments