diff --git a/.gitignore b/.gitignore index 96f823b9a9c..6adadab9bb5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,8 @@ matsimExamples matsim/docs/doxygen/html matsim/src/main/java/Doxyfile +# VS Code files +.vscode + # ignore output directories: output/ diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/extensive/MultiInsertionDetourPathCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/extensive/MultiInsertionDetourPathCalculator.java index 62089251f80..f4eaece5ead 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/extensive/MultiInsertionDetourPathCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/extensive/MultiInsertionDetourPathCalculator.java @@ -43,6 +43,7 @@ import org.matsim.core.mobsim.framework.events.MobsimBeforeCleanupEvent; import org.matsim.core.mobsim.framework.listeners.MobsimBeforeCleanupListener; import org.matsim.core.router.speedy.SpeedyGraph; +import org.matsim.core.router.speedy.SpeedyGraphBuilder; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; @@ -63,7 +64,7 @@ class MultiInsertionDetourPathCalculator implements MobsimBeforeCleanupListener MultiInsertionDetourPathCalculator(Network network, TravelTime travelTime, TravelDisutility travelDisutility, DrtConfigGroup drtCfg) { - SpeedyGraph graph = new SpeedyGraph(network); + SpeedyGraph graph = SpeedyGraphBuilder.build(network); IdMap nodeMap = new IdMap<>(Node.class); nodeMap.putAll(network.getNodes()); diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java index 7caf007d369..1fd27d75d84 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/zone/skims/TravelTimeMatrices.java @@ -34,6 +34,7 @@ import org.matsim.contrib.zone.skims.SparseMatrix.SparseRow; import org.matsim.core.router.speedy.LeastCostPathTree; import org.matsim.core.router.speedy.SpeedyGraph; +import org.matsim.core.router.speedy.SpeedyGraphBuilder; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; import org.matsim.core.utils.misc.Counter; @@ -115,7 +116,7 @@ private interface Calculation { private static void calculate(RoutingParams params, Collection elements, Calculation calculation, String counterPrefix) { var trees = IntStream.range(0, params.numberOfThreads) - .mapToObj(i -> new LeastCostPathTree(new SpeedyGraph(params.routingNetwork), params.travelTime, params.travelDisutility)) + .mapToObj(i -> new LeastCostPathTree(SpeedyGraphBuilder.build(params.routingNetwork), params.travelTime, params.travelDisutility)) .toList(); var executorService = new ExecutorServiceWithResource<>(trees); var counter = new Counter(counterPrefix, " / " + elements.size()); diff --git a/contribs/dvrp/src/main/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTFactory.java b/contribs/dvrp/src/main/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTFactory.java index ddbf9814fa9..854dc19a5c2 100644 --- a/contribs/dvrp/src/main/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTFactory.java +++ b/contribs/dvrp/src/main/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTFactory.java @@ -37,7 +37,7 @@ public class SpeedyMultiSourceALTFactory { public SpeedyMultiSourceALT createPathCalculator(Network network, TravelDisutility travelCosts, TravelTime travelTimes) { - SpeedyGraph graph = this.graphs.computeIfAbsent(network, SpeedyGraph::new); + SpeedyGraph graph = this.graphs.computeIfAbsent(network, SpeedyGraphBuilder::build); var graphTravelCostsPair = Pair.of(graph, travelCosts); int landmarksCount = Math.min(16, graph.nodeCount); diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/path/OneToManyPathCalculatorTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/path/OneToManyPathCalculatorTest.java index 2db1640cdf5..46497ef7852 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/path/OneToManyPathCalculatorTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/path/OneToManyPathCalculatorTest.java @@ -31,6 +31,7 @@ import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.Node; @@ -39,6 +40,7 @@ import org.matsim.core.network.NetworkUtils; import org.matsim.core.router.speedy.LeastCostPathTree; import org.matsim.core.router.speedy.SpeedyGraph; +import org.matsim.core.router.speedy.SpeedyGraphBuilder; import org.matsim.core.router.util.LeastCostPathCalculator.Path; import org.matsim.core.router.util.TravelTime; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; @@ -65,7 +67,7 @@ public class OneToManyPathCalculatorTest { private final IdMap nodeMap = new IdMap<>(Node.class); private final TravelTime travelTime = new FreeSpeedTravelTime(); - private final LeastCostPathTree dijkstraTree = new LeastCostPathTree(new SpeedyGraph(network), travelTime, + private final LeastCostPathTree dijkstraTree = new LeastCostPathTree(SpeedyGraphBuilder.build(network), travelTime, new TimeAsTravelDisutility(travelTime)); @BeforeEach diff --git a/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTBackwardTest.java b/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTBackwardTest.java index 828831db27b..06d8986af53 100644 --- a/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTBackwardTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTBackwardTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.Node; @@ -74,7 +75,7 @@ public class SpeedyMultiSourceALTBackwardTest { private final TravelTime travelTime = new FreeSpeedTravelTime(); private final TravelDisutility travelDisutility = new TimeAsTravelDisutility(travelTime); - private final SpeedyGraph speedyGraph = new SpeedyGraph(network); + private final SpeedyGraph speedyGraph = SpeedyGraphBuilder.build(network); private final SpeedyALTData landmarks = new SpeedyALTData(speedyGraph, 3, travelDisutility); private final SpeedyMultiSourceALT multiSourceALT = new SpeedyMultiSourceALT(landmarks, travelTime, travelDisutility); diff --git a/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTForwardTest.java b/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTForwardTest.java index 45287f7b776..40c190308b6 100644 --- a/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTForwardTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/core/router/speedy/SpeedyMultiSourceALTForwardTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.Node; @@ -72,7 +73,7 @@ public class SpeedyMultiSourceALTForwardTest { private final TravelTime travelTime = new FreeSpeedTravelTime(); private final TravelDisutility travelDisutility = new TimeAsTravelDisutility(travelTime); - private final SpeedyGraph speedyGraph = new SpeedyGraph(network); + private final SpeedyGraph speedyGraph = SpeedyGraphBuilder.build(network); private final SpeedyALTData landmarks = new SpeedyALTData(speedyGraph, 3, travelDisutility); private final SpeedyMultiSourceALT multiSourceALT = new SpeedyMultiSourceALT(landmarks, travelTime, travelDisutility); diff --git a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/analysis/skims/NetworkSkimMatrices.java b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/analysis/skims/NetworkSkimMatrices.java index 421b9f9f1ef..08417e3c688 100644 --- a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/analysis/skims/NetworkSkimMatrices.java +++ b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/analysis/skims/NetworkSkimMatrices.java @@ -32,6 +32,7 @@ import org.matsim.api.core.v01.population.Person; import org.matsim.core.network.NetworkUtils; import org.matsim.core.population.PopulationUtils; +import org.matsim.core.router.speedy.SpeedyGraphBuilder; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; import org.matsim.core.utils.misc.Counter; @@ -58,7 +59,7 @@ private NetworkSkimMatrices() { public static NetworkIndicators calculateSkimMatrices(Network xy2lNetwork, Network routingNetwork, Map coordsPerZone, double departureTime, TravelTime travelTime, TravelDisutility travelDisutility, int numberOfThreads) { - SpeedyGraph routingGraph = new SpeedyGraph(routingNetwork); + SpeedyGraph routingGraph = SpeedyGraphBuilder.build(routingNetwork); Map nodesPerZone = new HashMap<>(); for (Map.Entry e : coordsPerZone.entrySet()) { T zoneId = e.getKey(); diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/assignment/VehicleAssignmentProblem.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/assignment/VehicleAssignmentProblem.java index 02984967122..f2b22ae47ba 100644 --- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/assignment/VehicleAssignmentProblem.java +++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/optimizer/assignment/VehicleAssignmentProblem.java @@ -35,6 +35,7 @@ import org.matsim.contrib.taxi.optimizer.VehicleData; import org.matsim.contrib.taxi.optimizer.assignment.AssignmentDestinationData.DestEntry; import org.matsim.core.router.speedy.SpeedyGraph; +import org.matsim.core.router.speedy.SpeedyGraphBuilder; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; @@ -71,7 +72,7 @@ public VehicleAssignmentProblem(Network network, TravelTime travelTime, TravelDi IdMap nodeMap = new IdMap<>(Node.class); nodeMap.putAll(network.getNodes()); - pathSearch = OneToManyPathSearch.createSearch(new SpeedyGraph(network), nodeMap, travelTime, travelDisutility, + pathSearch = OneToManyPathSearch.createSearch(SpeedyGraphBuilder.build(network), nodeMap, travelTime, travelDisutility, false); // TODO this kNN is slow diff --git a/matsim/src/main/java/org/matsim/core/network/DisallowedNextLinks.java b/matsim/src/main/java/org/matsim/core/network/DisallowedNextLinks.java index 11aaa27429b..25c0bbd7303 100644 --- a/matsim/src/main/java/org/matsim/core/network/DisallowedNextLinks.java +++ b/matsim/src/main/java/org/matsim/core/network/DisallowedNextLinks.java @@ -1,12 +1,15 @@ package org.matsim.core.network; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -34,10 +37,34 @@ public class DisallowedNextLinks { // list in favor of a smaller memory footprint. private final Map>>> linkIdSequencesMap = new HashMap<>(); + /** + * A builder to allow building a populated instance in one statement. + * Be aware that several calls to the build() method of a particular Builder instance will + * return the same DisallowedNextLinks instance! + */ + public static class Builder { + private final DisallowedNextLinks instance = new DisallowedNextLinks(); + + public Builder withDisallowedLinkSequence(String mode, List> linkSequence) { + instance.addDisallowedLinkSequence(mode, linkSequence); + return this; + } + + /** + * Be aware that several calls to the build() method of a particular Builder + * instance will return the same DisallowedNextLinks instance! + * + * @return an instance with the required disallowedNextLinks set + */ + public DisallowedNextLinks build() { + return instance; + } + } + public DisallowedNextLinks() { // ! remove constructor, if routing considers this if (!warnedAboutNotConsideredInRouting) { warnedAboutNotConsideredInRouting = true; - LOG.warn("Considering DisallowedNextLinks in routing is not yet implemented!"); + LOG.warn("Considering DisallowedNextLinks in routing is only implemented by SpeedyDijkstra and SpeedyALT!"); } } @@ -75,6 +102,25 @@ public List>> getDisallowedLinkSequences(String mode) { return sequences != null ? Collections.unmodifiableList(sequences) : Collections.emptyList(); } + /** + * Returns an aggregation of all link restrictions, for all modes. This is meant to be used in monomodal networks. + */ + public Collection>> getMergedDisallowedLinkSequences() { + // Implementation note: we do not want duplicates, so the logical choice is a set, + // as the argument for memory footprint does not hold here (short lived object). + // This makes the return types inconsistent, but one could argue that all methods should + // return Collections rather than Lists (ordering is not meaningful) + // We use a LinkedHashSet to preserve iteration order, to be consistent with other methods in this class + // td 04.24 + final Set>> sequences = new LinkedHashSet<>(); + + for (List>> sequencesForMode : linkIdSequencesMap.values()) { + sequences.addAll(sequencesForMode); + } + + return Collections.unmodifiableSet(sequences); + } + @Nullable public List>> removeDisallowedLinkSequences(String mode) { return this.linkIdSequencesMap.remove(mode); @@ -85,6 +131,18 @@ public Map>>> getAsMap() { .collect(Collectors.toMap(Entry::getKey, e -> Collections.unmodifiableList(e.getValue()))); } + public DisallowedNextLinks copyOnlyModes(final Collection modes) { + final DisallowedNextLinks newInstance = new DisallowedNextLinks(); + + for (String mode : modes) { + for (List> sequence : getDisallowedLinkSequences(mode)) { + newInstance.addDisallowedLinkSequence(mode, sequence); + } + } + + return newInstance; + } + public void clear() { this.linkIdSequencesMap.clear(); } @@ -120,4 +178,9 @@ public int hashCode() { return this.linkIdSequencesMap.hashCode(); } + @Override + public String toString() { + return "DisallowedNextLinks [linkIdSequencesMap=" + linkIdSequencesMap + "]"; + } + } diff --git a/matsim/src/main/java/org/matsim/core/network/algorithms/TransportModeNetworkFilter.java b/matsim/src/main/java/org/matsim/core/network/algorithms/TransportModeNetworkFilter.java index 9672bdfb7e0..03849cc570c 100644 --- a/matsim/src/main/java/org/matsim/core/network/algorithms/TransportModeNetworkFilter.java +++ b/matsim/src/main/java/org/matsim/core/network/algorithms/TransportModeNetworkFilter.java @@ -28,6 +28,7 @@ import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.NetworkFactory; import org.matsim.api.core.v01.network.Node; +import org.matsim.core.network.DisallowedNextLinks; import org.matsim.core.network.NetworkChangeEvent; import org.matsim.core.network.NetworkUtils; import org.matsim.core.network.TimeDependentNetwork; @@ -102,6 +103,11 @@ public void filter(final Network subNetwork, final Set extractModes) { NetworkUtils.setType(link2, NetworkUtils.getType(link)); AttributesUtils.copyAttributesFromTo(link, link2); subNetwork.addLink(link2); + + DisallowedNextLinks disallowedNextLinks = NetworkUtils.getDisallowedNextLinks(link); + if (disallowedNextLinks != null) { + NetworkUtils.setDisallowedNextLinks(link2, disallowedNextLinks.copyOnlyModes(extractModes)); + } } } diff --git a/matsim/src/main/java/org/matsim/core/router/NetworkRoutingProvider.java b/matsim/src/main/java/org/matsim/core/router/NetworkRoutingProvider.java index b40087b6891..667764861ca 100644 --- a/matsim/src/main/java/org/matsim/core/router/NetworkRoutingProvider.java +++ b/matsim/src/main/java/org/matsim/core/router/NetworkRoutingProvider.java @@ -99,20 +99,7 @@ public RoutingModule get() { + routingMode + ";\tmode=" + mode) ; // the network refers to the (transport)mode: - Network filteredNetwork = null; - - // Ensure this is not performed concurrently by multiple threads! - synchronized (this.singleModeNetworksCache.getSingleModeNetworksCache()) { - filteredNetwork = this.singleModeNetworksCache.getSingleModeNetworksCache().get(mode); - if (filteredNetwork == null) { - TransportModeNetworkFilter filter = new TransportModeNetworkFilter(network); - Set modes = new HashSet<>(); - modes.add(mode); - filteredNetwork = NetworkUtils.createNetwork(networkConfigGroup); - filter.filter(filteredNetwork, modes); - this.singleModeNetworksCache.getSingleModeNetworksCache().put(mode, filteredNetwork); - } - } + Network filteredNetwork = singleModeNetworksCache.getOrCreateSingleModeNetwork(mode); // the travel time & disutility refer to the routing mode: TravelDisutilityFactory travelDisutilityFactory = this.travelDisutilityFactories.get(routingMode); diff --git a/matsim/src/main/java/org/matsim/core/router/SingleModeNetworksCache.java b/matsim/src/main/java/org/matsim/core/router/SingleModeNetworksCache.java index 7f49c3d7f25..c99829948a0 100644 --- a/matsim/src/main/java/org/matsim/core/router/SingleModeNetworksCache.java +++ b/matsim/src/main/java/org/matsim/core/router/SingleModeNetworksCache.java @@ -21,16 +21,46 @@ package org.matsim.core.router; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.matsim.api.core.v01.network.Network; +import org.matsim.core.config.groups.NetworkConfigGroup; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.algorithms.TransportModeNetworkFilter; + +import com.google.inject.Inject; public class SingleModeNetworksCache { - private Map singleModeNetworksCache = new ConcurrentHashMap<>(); + private final Map singleModeNetworksCache = new ConcurrentHashMap<>(); + private final Network fullNetwork; + private final NetworkConfigGroup networkConfigGroup; + + @Inject + public SingleModeNetworksCache(Network fullNetwork, NetworkConfigGroup networkConfigGroup) { + this.fullNetwork = fullNetwork; + this.networkConfigGroup = networkConfigGroup; + } public Map getSingleModeNetworksCache() { return singleModeNetworksCache; } + + public Network getOrCreateSingleModeNetwork(final String mode) { + return getSingleModeNetworksCache().computeIfAbsent(mode, this::filterNetwork); + } + + private Network filterNetwork(final String mode) { + TransportModeNetworkFilter filter = new TransportModeNetworkFilter(fullNetwork); + Set modes = new HashSet<>(); + modes.add(mode); + + final Network filteredNetwork = NetworkUtils.createNetwork(networkConfigGroup); + filter.filter(filteredNetwork, modes); + + return filteredNetwork; + } } diff --git a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALTFactory.java b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALTFactory.java index 47f2e8f10aa..0ca720daf0a 100644 --- a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALTFactory.java +++ b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALTFactory.java @@ -21,7 +21,7 @@ public class SpeedyALTFactory implements LeastCostPathCalculatorFactory { public LeastCostPathCalculator createPathCalculator(Network network, TravelDisutility travelCosts, TravelTime travelTimes) { SpeedyGraph graph = this.graphs.get(network); if (graph == null) { - graph = new SpeedyGraph(network); + graph = SpeedyGraphBuilder.build(network); this.graphs.put(network, graph); } SpeedyALTData landmarks = this.landmarksData.get(graph); diff --git a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyDijkstraFactory.java b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyDijkstraFactory.java index d01e4a2690b..77bed4a19b5 100644 --- a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyDijkstraFactory.java +++ b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyDijkstraFactory.java @@ -20,7 +20,7 @@ public class SpeedyDijkstraFactory implements LeastCostPathCalculatorFactory { public LeastCostPathCalculator createPathCalculator(Network network, TravelDisutility travelCosts, TravelTime travelTimes) { SpeedyGraph graph = graphs.get(network); if (graph == null) { - graph = new SpeedyGraph(network); + graph = SpeedyGraphBuilder.build(network); graphs.put(network, graph); } return new SpeedyDijkstra(graph, travelTimes, travelCosts); diff --git a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraph.java b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraph.java index fbb2896a408..e7cd69bcf3d 100644 --- a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraph.java +++ b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraph.java @@ -45,8 +45,8 @@ public class SpeedyGraph { * So, a network-graph with 1 Mio nodes and 2 Mio links should consume between 64 and 72 MB RAM only. */ - private final static int NODE_SIZE = 2; - private final static int LINK_SIZE = 6; + final static int NODE_SIZE = 2; + final static int LINK_SIZE = 6; final int nodeCount; final int linkCount; @@ -55,72 +55,14 @@ public class SpeedyGraph { private final Link[] links; private final Node[] nodes; - public SpeedyGraph(Network network) { - this.nodeCount = Id.getNumberOfIds(Node.class); - this.linkCount = Id.getNumberOfIds(Link.class); - - this.nodeData = new int[nodeCount * NODE_SIZE]; - this.linkData = new int[linkCount * LINK_SIZE]; - this.links = new Link[linkCount]; - this.nodes = new Node[nodeCount]; - - Arrays.fill(this.nodeData, -1); - Arrays.fill(this.linkData, -1); - - for (Node node : network.getNodes().values()) { - this.nodes[node.getId().index()] = node; - } - for (Link link : network.getLinks().values()) { - addLink(link); - } - } - - private void addLink(Link link) { - int fromNodeIdx = link.getFromNode().getId().index(); - int toNodeIdx = link.getToNode().getId().index(); - int linkIdx = link.getId().index(); - - int base = linkIdx * LINK_SIZE; - this.linkData[base + 2] = fromNodeIdx; - this.linkData[base + 3] = toNodeIdx; - this.linkData[base + 4] = (int) Math.round(link.getLength() * 100.0); - this.linkData[base + 5] = (int) Math.round(link.getLength() / link.getFreespeed() * 100.0); - - setOutLink(fromNodeIdx, linkIdx); - setInLink(toNodeIdx, linkIdx); - - this.links[linkIdx] = link; - } - - private void setOutLink(int fromNodeIdx, int linkIdx) { - final int nodeI = fromNodeIdx * NODE_SIZE; - int outLinkIdx = this.nodeData[nodeI]; - if (outLinkIdx < 0) { - this.nodeData[nodeI] = linkIdx; - return; - } - int lastLinkIdx; - do { - lastLinkIdx = outLinkIdx; - outLinkIdx = this.linkData[lastLinkIdx * LINK_SIZE]; - } while (outLinkIdx >= 0); - this.linkData[lastLinkIdx * LINK_SIZE] = linkIdx; - } - - private void setInLink(int toNodeIdx, int linkIdx) { - final int nodeI = toNodeIdx * NODE_SIZE + 1; - int inLinkIdx = this.nodeData[nodeI]; - if (inLinkIdx < 0) { - this.nodeData[nodeI] = linkIdx; - return; - } - int lastLinkIdx; - do { - lastLinkIdx = inLinkIdx; - inLinkIdx = this.linkData[lastLinkIdx * LINK_SIZE + 1]; - } while (inLinkIdx >= 0); - this.linkData[lastLinkIdx * LINK_SIZE + 1] = linkIdx; - } + SpeedyGraph(int[] nodeData, int[] linkData, Node[] nodes, Link[] links) { + this.nodeData = nodeData; + this.linkData = linkData; + this.nodes = nodes; + this.links = links; + this.nodeCount = this.nodes.length; + this.linkCount = this.links.length; + } public LinkIterator getOutLinkIterator() { return new OutLinkIterator(this); diff --git a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraphBuilder.java b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraphBuilder.java new file mode 100644 index 00000000000..724343da24d --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyGraphBuilder.java @@ -0,0 +1,446 @@ +package org.matsim.core.router.speedy; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.network.DisallowedNextLinks; +import org.matsim.core.network.NetworkUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Creates a {@link SpeedyGraph} for a provided {@link Network}. + * + * @author mrieser / Simunto + */ +public class SpeedyGraphBuilder { + + private int nodeCount; + private int linkCount; + private int[] nodeData; + private int[] linkData; + private Link[] links; + private Node[] nodes; + + public static SpeedyGraph build(Network network) { + if (hasTurnRestrictions(network)) { + return new SpeedyGraphBuilder().buildWithTurnRestrictions(network); + } + return new SpeedyGraphBuilder().buildWithoutTurnRestrictions(network); + } + + private static boolean hasTurnRestrictions(Network network) { + for (Link link : network.getLinks().values()) { + if (NetworkUtils.getDisallowedNextLinks(link) != null) { + return true; + } + } + return false; + } + + private SpeedyGraph buildWithTurnRestrictions(Network network) { + /* + * The implementation follows the algorithm developed by + * Marcel Rieser (Simunto) and Hannes Rewald (Volkswagen Group) + * in October 2023 during the MATSim Code Sprint week in Berlin, + * and documented at https://github.com/matsim-org/matsim-code-examples/wiki/turn-restrictions. + * + * TL;DR: + * Main idea of algorithm: for each link with turn-restrictions, create a sub-graph of the network + * containing all links required to model all allowed paths, but exclude the last link of turn restrictions' + * link sequence to enforce the "disallow" along that route. + * + * + * Implementation details: + * - The easiest solution would be to make a copy of the original network, then start modifying it + * according to the algorithm above (e.g. add and delete links and nodes), and then to convert the + * resulting network into a graph. This would require substantial amount of memory for duplicating + * the complete network, and might pose problems as we will have multiple links with the same id. + * - Given the assumption that turn restrictions apply only to a small amount of the full network, + * we keep the original network intact. Instead, we keep all modifications in separate data-structures + * so they can be used to create the routing-graph. + * - If the network is already filtered for a specific mode, it might be that links referenced + * in a turn restriction are missing. The implementation must be able to deal with such cases, + * prevent NullPointerExceptions. + * - As turn restrictions are mode-specific, the algorithm needs to know for which mode the + * turn restriction need to be considered. + */ + + TurnRestrictionsContext context = new TurnRestrictionsContext(network); + + for (Link startingLink : network.getLinks().values()) { + DisallowedNextLinks disallowedNextLinks = NetworkUtils.getDisallowedNextLinks(startingLink); + if (disallowedNextLinks == null) { + continue; + } + Collection>> turnRestrictions = disallowedNextLinks.getMergedDisallowedLinkSequences(); + if (turnRestrictions == null || turnRestrictions.isEmpty()) { + continue; + } + + // steps 1 to 5: + ColoredLink coloredStartingLink = applyTurnRestriction(context, turnRestrictions, startingLink); + + // step 6: turn restrictions have to be applied separately to existing colored links as well. + // see if there are already colored link copies available for this starting link + List coloredLinks = context.coloredLinksPerLinkMap.get(startingLink.getId()); + if (coloredLinks != null) { + for (ColoredLink coloredLink : coloredLinks) { + // optimization: re-point toNode instead of re-applying full turn restrictions + if (coloredLink.toColoredNode == null) { + coloredLink.toColoredNode = coloredStartingLink.toColoredNode; + coloredLink.toNode = null; + } else { + applyTurnRestriction(context, turnRestrictions, coloredLink); + } + } + } + } + + // create routing graph from context + this.nodeCount = context.nodeCount; + this.linkCount = context.linkCount; + + this.nodeData = new int[this.nodeCount * SpeedyGraph.NODE_SIZE]; + this.linkData = new int[this.linkCount * SpeedyGraph.LINK_SIZE]; + this.links = new Link[this.linkCount]; + this.nodes = new Node[this.nodeCount]; + + Arrays.fill(this.nodeData, -1); + Arrays.fill(this.linkData, -1); + + for (Node node : network.getNodes().values()) { + this.nodes[node.getId().index()] = node; + } + for (Link link : network.getLinks().values()) { + if (context.replacedLinks.get(link.getId()) == null) { + addLink(link); + } + } + for (ColoredNode node : context.coloredNodes) { + this.nodes[node.index] = node.node; + } + for (ColoredLink link : context.coloredLinks) { + addLink(link); + } + + return new SpeedyGraph(this.nodeData, this.linkData, this.nodes, this.links); + } + + private ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection>> restrictions, Link startingLink) { + return this.applyTurnRestriction(context, restrictions, startingLink, null); + } + + private void applyTurnRestriction(TurnRestrictionsContext context, Collection>> restrictions, ColoredLink startingLink) { + this.applyTurnRestriction(context, restrictions, null, startingLink); + } + + private ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection>> restrictions, Link startingLink, ColoredLink coloredStartingLink) { + Set affectedNodes = new HashSet<>(); + Set affectedColoredNodes = new HashSet<>(); + Set affectedLinks = new HashSet<>(); + Set affectedColoredLinks = new HashSet<>(); + Set> endLinkIds = new HashSet<>(); + + // step 1 and 2: collect end-links, affected-links and affected-nodes + for (List> restriction : restrictions) { + + Link currentLink; + ColoredLink currentColoredLink; + Node currentNode = startingLink == null ? null : startingLink.getToNode(); + // due to the optimization in step 6, every colored starting link leads to a colored to-node + ColoredNode currentColoredNode = coloredStartingLink == null ? null : coloredStartingLink.toColoredNode; + + // walk along the restricted path, collect affectedLinks, affectedNodes and endLink + for (Id linkId : restriction) { + if (currentNode != null) { + // handle regular node + affectedNodes.add(currentNode); + currentLink = null; + currentColoredLink = null; + for (Link outLink : currentNode.getOutLinks().values()) { + if (outLink.getId() == linkId) { + currentColoredLink = context.replacedLinks.get(linkId); + if (currentColoredLink == null) { + currentLink = outLink; + } + break; + } + } + + if (currentLink != null) { + affectedLinks.add(currentLink); + currentNode = currentLink.getToNode(); + currentColoredNode = null; + } + if (currentColoredLink != null) { + affectedColoredLinks.add(currentColoredLink); + currentNode = currentColoredLink.toNode; + currentColoredNode = currentColoredLink.toColoredNode; + } + if (currentLink == null && currentColoredLink == null) { + // link of restriction is no longer part of the network, maybe we are in a sub-graph + break; + } + } else if (currentColoredNode != null) { + // handle colored node + affectedColoredNodes.add(currentColoredNode); + currentLink = null; + currentColoredLink = null; + for (ColoredLink outLink : currentColoredNode.outLinks) { + if (outLink.link.getId() == linkId) { + currentColoredLink = outLink; + break; + } + } + if (currentColoredLink != null) { + affectedColoredLinks.add(currentColoredLink); + currentNode = currentColoredLink.toNode; + currentColoredNode = currentColoredLink.toColoredNode; + } + if (currentColoredLink == null) { + // link of restriction is no longer part of the network, maybe we are in a sub-graph + break; + } + } + } + endLinkIds.add(restriction.get(restriction.size() - 1)); + } + + // step 3: create colored copies of nodes + Map, ColoredNode> newlyColoredNodes = new HashMap<>(); + for (Node affectedNode : affectedNodes) { + int nodeIndex = context.nodeCount; + context.nodeCount++; + ColoredNode newlyColoredNode = new ColoredNode(nodeIndex, affectedNode, new ArrayList<>()); + newlyColoredNodes.put(affectedNode.getId(), newlyColoredNode); + context.coloredNodes.add(newlyColoredNode); + } + for (ColoredNode affectedColoredNode : affectedColoredNodes) { + int nodeIndex = context.nodeCount; + context.nodeCount++; + ColoredNode newlyColoredNode = new ColoredNode(nodeIndex, affectedColoredNode.node, new ArrayList<>()); + newlyColoredNodes.put(affectedColoredNode.node.getId(), newlyColoredNode); + context.coloredNodes.add(newlyColoredNode); + } + + // step 4: create colored copies of links + for (Node affectedNode : affectedNodes) { + for (Link outLink : affectedNode.getOutLinks().values()) { + if (endLinkIds.contains(outLink.getId())) { + continue; + } + ColoredLink replacedOutLink = context.replacedLinks.get(outLink.getId()); + int linkIndex = context.linkCount; + context.linkCount++; + ColoredLink newlyColoredLink; + ColoredNode fromNode = newlyColoredNodes.get(outLink.getFromNode().getId()); + if (affectedLinks.contains(outLink) || (replacedOutLink != null && affectedColoredLinks.contains(replacedOutLink))) { + ColoredNode toNode = newlyColoredNodes.get(outLink.getToNode().getId()); + newlyColoredLink = new ColoredLink(linkIndex, outLink, fromNode, null, toNode, null); + } else { + Node toNode = outLink.getToNode(); + newlyColoredLink = new ColoredLink(linkIndex, outLink, fromNode, null, null, toNode); + } + fromNode.outLinks.add(newlyColoredLink); + context.coloredLinks.add(newlyColoredLink); + context.coloredLinksPerLinkMap.computeIfAbsent(outLink.getId(), id -> new ArrayList<>(3)).add(newlyColoredLink); + } + } + for (ColoredNode affectedNode : affectedColoredNodes) { + for (ColoredLink outLink : affectedNode.outLinks) { + if (endLinkIds.contains(outLink.link.getId())) { + continue; + } + int linkIndex = context.linkCount; + context.linkCount++; + ColoredLink newlyColoredLink; + ColoredNode fromNode = newlyColoredNodes.get(outLink.link.getFromNode().getId()); + if (affectedColoredLinks.contains(outLink)) { + ColoredNode toNode = newlyColoredNodes.get(outLink.link.getToNode().getId()); + newlyColoredLink = new ColoredLink(linkIndex, outLink.link, fromNode, null, toNode, null); + } else { + newlyColoredLink = new ColoredLink(linkIndex, outLink.link, fromNode, null, outLink.toColoredNode, outLink.toNode); + } + fromNode.outLinks.add(newlyColoredLink); + context.coloredLinks.add(newlyColoredLink); + context.coloredLinksPerLinkMap.computeIfAbsent(outLink.link.getId(), id -> new ArrayList<>(3)).add(newlyColoredLink); + } + } + + // step 5: replace starting link + if (startingLink != null) { + ColoredNode toNode = newlyColoredNodes.get(startingLink.getToNode().getId()); + int linkIndex = startingLink.getId().index(); // re-use the index + ColoredLink newlyColoredStartingLink = new ColoredLink(linkIndex, startingLink, null, startingLink.getFromNode(), toNode, null); + context.coloredLinks.add(newlyColoredStartingLink); + context.replacedLinks.put(startingLink.getId(), newlyColoredStartingLink); + + return newlyColoredStartingLink; + } + if (coloredStartingLink != null) { + // don't really replace the colored started link, but re-point it to the newly colored node + coloredStartingLink.toColoredNode = newlyColoredNodes.get(coloredStartingLink.link.getToNode().getId()); + return null; + + } + throw new IllegalArgumentException("either startingLink or coloredStartingLink must be set"); + } + + private SpeedyGraph buildWithoutTurnRestrictions(Network network) { + this.nodeCount = Id.getNumberOfIds(Node.class); + this.linkCount = Id.getNumberOfIds(Link.class); + + this.nodeData = new int[this.nodeCount * SpeedyGraph.NODE_SIZE]; + this.linkData = new int[this.linkCount * SpeedyGraph.LINK_SIZE]; + this.links = new Link[this.linkCount]; + this.nodes = new Node[this.nodeCount]; + + Arrays.fill(this.nodeData, -1); + Arrays.fill(this.linkData, -1); + + for (Node node : network.getNodes().values()) { + this.nodes[node.getId().index()] = node; + } + for (Link link : network.getLinks().values()) { + addLink(link); + } + + return new SpeedyGraph(this.nodeData, this.linkData, this.nodes, this.links); + } + + private void addLink(Link link) { + int fromNodeIdx = link.getFromNode().getId().index(); + int toNodeIdx = link.getToNode().getId().index(); + int linkIdx = link.getId().index(); + + int base = linkIdx * SpeedyGraph.LINK_SIZE; + this.linkData[base + 2] = fromNodeIdx; + this.linkData[base + 3] = toNodeIdx; + this.linkData[base + 4] = (int) Math.round(link.getLength() * 100.0); + this.linkData[base + 5] = (int) Math.round(link.getLength() / link.getFreespeed() * 100.0); + + setOutLink(fromNodeIdx, linkIdx); + setInLink(toNodeIdx, linkIdx); + + this.links[linkIdx] = link; + } + + private void addLink(ColoredLink link) { + int fromNodeIdx = -1; + int toNodeIdx = -1; + int linkIdx = link.index; + + if (link.fromColoredNode != null) { + fromNodeIdx = link.fromColoredNode.index; + } + if (link.fromNode != null) { + fromNodeIdx = link.fromNode.getId().index(); + } + if (link.toColoredNode != null) { + toNodeIdx = link.toColoredNode.index; + } + if (link.toNode != null) { + toNodeIdx = link.toNode.getId().index(); + } + + int base = linkIdx * SpeedyGraph.LINK_SIZE; + this.linkData[base + 2] = fromNodeIdx; + this.linkData[base + 3] = toNodeIdx; + this.linkData[base + 4] = (int) Math.round(link.link.getLength() * 100.0); + this.linkData[base + 5] = (int) Math.round(link.link.getLength() / link.link.getFreespeed() * 100.0); + + setOutLink(fromNodeIdx, linkIdx); + setInLink(toNodeIdx, linkIdx); + + this.links[linkIdx] = link.link; + } + + private void setOutLink(int fromNodeIdx, int linkIdx) { + final int nodeI = fromNodeIdx * SpeedyGraph.NODE_SIZE; + int outLinkIdx = this.nodeData[nodeI]; + if (outLinkIdx < 0) { + this.nodeData[nodeI] = linkIdx; + return; + } + int lastLinkIdx; + do { + lastLinkIdx = outLinkIdx; + outLinkIdx = this.linkData[lastLinkIdx * SpeedyGraph.LINK_SIZE]; + } while (outLinkIdx >= 0); + this.linkData[lastLinkIdx * SpeedyGraph.LINK_SIZE] = linkIdx; + } + + private void setInLink(int toNodeIdx, int linkIdx) { + final int nodeI = toNodeIdx * SpeedyGraph.NODE_SIZE + 1; + int inLinkIdx = this.nodeData[nodeI]; + if (inLinkIdx < 0) { + this.nodeData[nodeI] = linkIdx; + return; + } + int lastLinkIdx; + do { + lastLinkIdx = inLinkIdx; + inLinkIdx = this.linkData[lastLinkIdx * SpeedyGraph.LINK_SIZE + 1]; + } while (inLinkIdx >= 0); + this.linkData[lastLinkIdx * SpeedyGraph.LINK_SIZE + 1] = linkIdx; + } + + private static class TurnRestrictionsContext { + int nodeCount; + int linkCount; + final Network network; + Map, ColoredLink> replacedLinks = new HashMap<>(); + List coloredNodes = new ArrayList<>(); + List coloredLinks = new ArrayList<>(); + Map, List> coloredLinksPerLinkMap = new HashMap<>(); + + public TurnRestrictionsContext(Network network) { + this.network = network; + this.nodeCount = Id.getNumberOfIds(Node.class); + this.linkCount = Id.getNumberOfIds(Link.class); + + } + } + + private static final class ColoredLink { + private final int index; + private final Link link; + private final ColoredNode fromColoredNode; + private final Node fromNode; + private ColoredNode toColoredNode; + private Node toNode; + + private ColoredLink( + int index, + Link link, + ColoredNode fromColoredNode, + Node fromNode, + ColoredNode toColoredNode, + Node toNode + ) { + this.index = index; + this.link = link; + this.fromColoredNode = fromColoredNode; + this.fromNode = fromNode; + this.toColoredNode = toColoredNode; + this.toNode = toNode; + } + } + + private record ColoredNode ( + int index, + Node node, + List outLinks + ) { + } + +} diff --git a/matsim/src/test/java/org/matsim/core/network/algorithms/TransportModeNetworkFilterTest.java b/matsim/src/test/java/org/matsim/core/network/algorithms/TransportModeNetworkFilterTest.java index bd159f2cf7f..381728164b2 100644 --- a/matsim/src/test/java/org/matsim/core/network/algorithms/TransportModeNetworkFilterTest.java +++ b/matsim/src/test/java/org/matsim/core/network/algorithms/TransportModeNetworkFilterTest.java @@ -19,6 +19,7 @@ package org.matsim.core.network.algorithms; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -35,6 +36,7 @@ import org.matsim.api.core.v01.network.Node; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; +import org.matsim.core.network.DisallowedNextLinks; import org.matsim.core.network.NetworkChangeEvent; import org.matsim.core.network.NetworkChangeEvent.ChangeType; import org.matsim.core.network.NetworkChangeEvent.ChangeValue; @@ -90,6 +92,16 @@ void testFilter_SingleMode() { Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getInLinks().get(f.linkIds[3])); Assertions.assertEquals(1, subNetwork.getNodes().get(f.nodeIds[7]).getOutLinks().size()); Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[7]).getOutLinks().get(f.linkIds[7])); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.car, Arrays.asList(f.linkIds[14])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[3]))); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.car, Arrays.asList(f.linkIds[7])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[6]))); subNetwork = ScenarioUtils.createScenario(ConfigUtils.createConfig()).getNetwork(); filter.filter(subNetwork, createHashSet(TransportMode.bike)); @@ -115,6 +127,11 @@ void testFilter_SingleMode() { Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getInLinks().get(f.linkIds[13])); Assertions.assertEquals(1, subNetwork.getNodes().get(f.nodeIds[4]).getOutLinks().size()); Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getOutLinks().get(f.linkIds[4])); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.bike, Arrays.asList(f.linkIds[7])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[6]))); subNetwork = ScenarioUtils.createScenario(ConfigUtils.createConfig()).getNetwork(); filter.filter(subNetwork, createHashSet(TransportMode.walk)); @@ -137,7 +154,7 @@ void testFilter_SingleMode() { Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getInLinks().get(f.linkIds[13])); Assertions.assertEquals(1, subNetwork.getNodes().get(f.nodeIds[4]).getOutLinks().size()); Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getOutLinks().get(f.linkIds[14])); - } + } @Test void testFilter_MultipleModes() { @@ -183,6 +200,17 @@ void testFilter_MultipleModes() { Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[7]).getOutLinks().get(f.linkIds[7])); Assertions.assertEquals(1, subNetwork.getNodes().get(f.nodeIds[10]).getInLinks().size()); Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[10]).getInLinks().get(f.linkIds[9])); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.car, Arrays.asList(f.linkIds[14])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[3]))); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.car, Arrays.asList(f.linkIds[7])) + .withDisallowedLinkSequence(TransportMode.bike, Arrays.asList(f.linkIds[7])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[6]))); subNetwork = ScenarioUtils.createScenario(ConfigUtils.createConfig()).getNetwork(); filter.filter(subNetwork, createHashSet(TransportMode.bike, TransportMode.walk)); @@ -218,6 +246,11 @@ void testFilter_MultipleModes() { Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[4]).getInLinks().get(f.linkIds[13])); Assertions.assertEquals(1, subNetwork.getNodes().get(f.nodeIds[10]).getOutLinks().size()); Assertions.assertNotNull(subNetwork.getNodes().get(f.nodeIds[10]).getOutLinks().get(f.linkIds[16])); + Assertions.assertEquals( + new DisallowedNextLinks.Builder() + .withDisallowedLinkSequence(TransportMode.bike, Arrays.asList(f.linkIds[7])) + .build(), + NetworkUtils.getDisallowedNextLinks(subNetwork.getLinks().get(f.linkIds[6]))); } @Test @@ -361,6 +394,10 @@ void testFilter_timeVariant() { * c cb * * Legend: c = car, w = walk, b = bike + * + * With turn restrictions: + * - for car: 3 -> 14 and 6 -> 7 + * - for bike: 6 -> 7 * * * @@ -419,6 +456,14 @@ private static class Fixture { network.addLink(createLink(network, this.linkIds[14], this.nodeIds[ 4], this.nodeIds[ 7], this.modesCW)); network.addLink(createLink(network, this.linkIds[15], this.nodeIds[ 7], this.nodeIds[10], this.modesW)); network.addLink(createLink(network, this.linkIds[16], this.nodeIds[10], this.nodeIds[13], this.modesCBW)); + + // turn restrictions: create one link with only car turn restriction, one link with car and bike + Link link3 = network.getLinks().get(this.linkIds[3]); + NetworkUtils.addDisallowedNextLinks(link3, TransportMode.car, Arrays.asList(this.linkIds[14])); + + Link link6 = network.getLinks().get(this.linkIds[6]); + NetworkUtils.addDisallowedNextLinks(link6, TransportMode.car, Arrays.asList(this.linkIds[7])); + NetworkUtils.addDisallowedNextLinks(link6, TransportMode.bike, Arrays.asList(this.linkIds[7])); } } diff --git a/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTest.java b/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTest.java index 1aa98e5bc16..0bb049ca193 100644 --- a/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTest.java +++ b/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTest.java @@ -47,7 +47,7 @@ public abstract class AbstractLeastCostPathCalculatorTest { @RegisterExtension - private MatsimTestUtils utils = new MatsimTestUtils(); + protected MatsimTestUtils utils = new MatsimTestUtils(); protected abstract LeastCostPathCalculator getLeastCostPathCalculator(final Network network); diff --git a/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTestWithTurnRestrictions.java b/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTestWithTurnRestrictions.java new file mode 100644 index 00000000000..11e4ddfc1ce --- /dev/null +++ b/matsim/src/test/java/org/matsim/core/router/AbstractLeastCostPathCalculatorTestWithTurnRestrictions.java @@ -0,0 +1,99 @@ +package org.matsim.core.router; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.Arrays; + +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.jupiter.api.Test; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.config.Config; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.io.MatsimNetworkReader; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.LeastCostPathCalculator.Path; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; +import org.xml.sax.SAXException; + +public abstract class AbstractLeastCostPathCalculatorTestWithTurnRestrictions extends AbstractLeastCostPathCalculatorTest { + @Test + void testCalcLeastCostPath_TurnRestrictions() throws SAXException, ParserConfigurationException, IOException { + Network network = createTurnRestrictionsTestNetwork(); + + Node nodeS = network.getNodes().get(Id.create("S", Node.class)); + Node nodeT = network.getNodes().get(Id.create("T", Node.class)); + + LeastCostPathCalculator routerAlgo = getLeastCostPathCalculator(network); + Path path = routerAlgo.calcLeastCostPath(nodeS, nodeT, 8.0*3600, null, null); + + assertEquals(7, path.nodes.size(), "number of nodes wrong."); + assertEquals(6, path.links.size(), "number of links wrong."); + + assertEquals(network.getNodes().get(Id.create("S", Node.class)), path.nodes.get(0)); + assertEquals(network.getNodes().get(Id.create("1", Node.class)), path.nodes.get(1)); + assertEquals(network.getNodes().get(Id.create("2", Node.class)), path.nodes.get(2)); + assertEquals(network.getNodes().get(Id.create("3", Node.class)), path.nodes.get(3)); + assertEquals(network.getNodes().get(Id.create("4", Node.class)), path.nodes.get(4)); + assertEquals(network.getNodes().get(Id.create("5", Node.class)), path.nodes.get(5)); + assertEquals(network.getNodes().get(Id.create("T", Node.class)), path.nodes.get(6)); + + assertEquals(network.getLinks().get(Id.create("S1", Link.class)), path.links.get(0)); + assertEquals(network.getLinks().get(Id.create("12", Link.class)), path.links.get(1)); + assertEquals(network.getLinks().get(Id.create("23", Link.class)), path.links.get(2)); + assertEquals(network.getLinks().get(Id.create("34", Link.class)), path.links.get(3)); + assertEquals(network.getLinks().get(Id.create("45", Link.class)), path.links.get(4)); + assertEquals(network.getLinks().get(Id.create("5T", Link.class)), path.links.get(5)); + } + + /** + * Creates a test network where the shortest path is impossible due to turn restrictions + * + * S + * | + * \/ + * 2<---1--->T + * | / /\ + * \/ / | + * 3--->4--->5 + * + * Where S1 -> 1T and 23 -> 34 -> 4T are forbidden. + * + * @return + */ + private Network createTurnRestrictionsTestNetwork() { + Config config = utils.loadConfig((String)null); + Scenario scenario = ScenarioUtils.createScenario(config); + final Network network = scenario.getNetwork(); + + final Node nodeS = NetworkUtils.createAndAddNode(network, Id.createNodeId("S"), new Coord(1,2)); + final Node node1 = NetworkUtils.createAndAddNode(network, Id.createNodeId("1"), new Coord(1,1)); + final Node node2 = NetworkUtils.createAndAddNode(network, Id.createNodeId("2"), new Coord(0,1)); + final Node node3 = NetworkUtils.createAndAddNode(network, Id.createNodeId("3"), new Coord(0,0)); + final Node node4 = NetworkUtils.createAndAddNode(network, Id.createNodeId("4"), new Coord(1,0)); + final Node node5 = NetworkUtils.createAndAddNode(network, Id.createNodeId("5"), new Coord(2,0)); + final Node nodeT = NetworkUtils.createAndAddNode(network, Id.createNodeId("T"), new Coord(2,0)); + + final Link linkS1 = NetworkUtils.createAndAddLink(network, Id.createLinkId("S1"), nodeS, node1, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("12"), node1, node2, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("1T"), node1, nodeT, 1, 1,1, 1); + final Link link23 = NetworkUtils.createAndAddLink(network, Id.createLinkId("23"), node2, node3, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("34"), node3, node4, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("4T"), node4, nodeT, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("45"), node4, node5, 1, 1,1, 1); + NetworkUtils.createAndAddLink(network, Id.createLinkId("5T"), node5, nodeT, 1, 1,1, 1); + + NetworkUtils.addDisallowedNextLinks(linkS1, TransportMode.car, Arrays.asList(Id.createLinkId("1T"))); + NetworkUtils.addDisallowedNextLinks(link23, TransportMode.car, Arrays.asList(Id.createLinkId("34"), Id.createLinkId("4T"))); + + return network; + } +} diff --git a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyALTTest.java b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyALTTest.java index 959d902b321..c43d8c8825d 100644 --- a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyALTTest.java +++ b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyALTTest.java @@ -20,22 +20,24 @@ package org.matsim.core.router.speedy; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Network; import org.matsim.core.config.groups.ScoringConfigGroup; import org.matsim.core.router.AbstractLeastCostPathCalculatorTest; +import org.matsim.core.router.AbstractLeastCostPathCalculatorTestWithTurnRestrictions; import org.matsim.core.router.costcalculators.FreespeedTravelTimeAndDisutility; import org.matsim.core.router.util.LeastCostPathCalculator; /** * @author mrieser */ -public class SpeedyALTTest extends AbstractLeastCostPathCalculatorTest { +public class SpeedyALTTest extends AbstractLeastCostPathCalculatorTestWithTurnRestrictions { @Override protected LeastCostPathCalculator getLeastCostPathCalculator(final Network network) { FreespeedTravelTimeAndDisutility travelTimeCostCalculator = new FreespeedTravelTimeAndDisutility(new ScoringConfigGroup()); - SpeedyGraph g = new SpeedyGraph(network); - SpeedyALTData altData = new SpeedyALTData(g, 16, travelTimeCostCalculator); + SpeedyGraph g = SpeedyGraphBuilder.build(network); + SpeedyALTData altData = new SpeedyALTData(g, 4, travelTimeCostCalculator); return new SpeedyALT(altData, travelTimeCostCalculator, travelTimeCostCalculator); } diff --git a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyDijkstraTest.java b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyDijkstraTest.java index a27c16c2ec9..24190769b1d 100644 --- a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyDijkstraTest.java +++ b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyDijkstraTest.java @@ -20,21 +20,23 @@ package org.matsim.core.router.speedy; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Network; import org.matsim.core.config.groups.ScoringConfigGroup; import org.matsim.core.router.AbstractLeastCostPathCalculatorTest; +import org.matsim.core.router.AbstractLeastCostPathCalculatorTestWithTurnRestrictions; import org.matsim.core.router.costcalculators.FreespeedTravelTimeAndDisutility; import org.matsim.core.router.util.LeastCostPathCalculator; /** * @author mrieser */ -public class SpeedyDijkstraTest extends AbstractLeastCostPathCalculatorTest { +public class SpeedyDijkstraTest extends AbstractLeastCostPathCalculatorTestWithTurnRestrictions { @Override protected LeastCostPathCalculator getLeastCostPathCalculator(final Network network) { FreespeedTravelTimeAndDisutility travelTimeCostCalculator = new FreespeedTravelTimeAndDisutility(new ScoringConfigGroup()); - SpeedyGraph g = new SpeedyGraph(network); + SpeedyGraph g = SpeedyGraphBuilder.build(network); return new SpeedyDijkstra(g, travelTimeCostCalculator, travelTimeCostCalculator); } diff --git a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphBuilderTest.java b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphBuilderTest.java new file mode 100644 index 00000000000..36681cecbd4 --- /dev/null +++ b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphBuilderTest.java @@ -0,0 +1,351 @@ +package org.matsim.core.router.speedy; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.config.groups.ScoringConfigGroup; +import org.matsim.core.network.DisallowedNextLinks; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.router.costcalculators.FreespeedTravelTimeAndDisutility; +import org.matsim.core.router.util.LeastCostPathCalculator; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author mrieser / Simunto + */ +class SpeedyGraphBuilderTest { + + private static final Logger LOG = LogManager.getLogger(SpeedyGraphBuilderTest.class); + + @Test + public void testSingleDisallowedRightTurn() { + var f = new Fixture(); + // make some other links more expensive to prevent routing along them + f.overwriteLinkLength("jf", 10000); + f.overwriteLinkLength("ji", 10000); + + var graph = runTest(f.network, "J", "G", "jk", "kg"); + Assertions.assertEquals(16, graph.nodeCount); + Assertions.assertEquals(48, graph.linkCount); + + f.addTurnRestriction("jk", "kg"); + graph = runTest(f.network, "J", "G", "jk", "kl", "lh", "hg"); + Assertions.assertEquals(16 + 1, graph.nodeCount, "only node K should be duplicated: K'"); + Assertions.assertEquals(48 + 3, graph.linkCount, "expected the following new links: k'j, k'o, k'l"); + } + + @Test + public void testMultipleRestrictionsOnSameLink() { + var f = new Fixture(); + // make some other links more expensive to prevent routing along them + f.overwriteLinkLength("jf", 10000); + f.overwriteLinkLength("ji", 10000); + + var graph = runTest(f.network, "J", "G", "jk", "kg"); + Assertions.assertEquals(16, graph.nodeCount); + Assertions.assertEquals(48, graph.linkCount); + + // prevent u-turns by higher costs on some of those links + f.overwriteLinkLength("lk", 10000); + f.overwriteLinkLength("ko", 600); + + f.addTurnRestriction("jk", "kg"); + f.addTurnRestriction("jk", "kl", "lh"); + graph = runTest(f.network, "J", "G", "jn", "no", "ok", "kg"); + Assertions.assertEquals(16 + 2, graph.nodeCount, "expected 2 duplicated nodes: K', L'"); + Assertions.assertEquals(48 + 5, graph.linkCount, "expected the following duplicated links: k'j, k'o, k'l', l'p, l'k"); + } + + @Test + public void testMultipleRestrictionsOnMultipleLink() { + // corresponds to figure 5 in the documentation + // the "green" turn restriction starting on link kg will be applied first + var f = new Fixture(); + // make some other links more expensive to prevent routing along them + f.overwriteLinkLength("jf", 10000); + f.overwriteLinkLength("ie", 10000); + f.overwriteLinkLength("cg", 600); + f.overwriteLinkLength("cd", 600); + + var graph = runTest(f.network, "J", "B", "jk", "kg", "gc", "cb"); + Assertions.assertEquals(16, graph.nodeCount); + Assertions.assertEquals(48, graph.linkCount); + + f.addTurnRestriction("jk", "kg", "gf"); + f.addTurnRestriction("jk", "kl", "lp"); + + f.addTurnRestriction("kg", "gc", "cb"); + f.addTurnRestriction("kg", "gh"); + +// SpeedyGraphBuilder.build(f.network, TransportMode.car).printDebug(); + verifyGraph(f.network, "J", new String[][] { + new String[] {"jk", "kg", "gf"}, + new String[] {"jk", "kl", "lp"}, + new String[] {"kg", "gc", "cb"}, + new String[] {"kg", "gh"}, + new String[] {"jk", "kg", "gh"}, + new String[] {"jk", "kg", "gc", "cb"}, + }); + + graph = runTest(f.network, "J", "B", "jk", "kl", "lh", "hd", "dc", "cb"); + Assertions.assertEquals(16 + 5, graph.nodeCount, "expected duplicated nodes: G', C', K', L', G''"); + Assertions.assertEquals(48 + 13, graph.linkCount, "expected duplicated links: g'k, g'f, g'c', c'g, c'd, k'j, k'o, k'g'', g''k, g''c', k'l', l'k, l'h"); + } + + @Test + public void testMultipleRestrictionsOnMultipleLink_rotated() { + // corresponds to figure 5 in the documentation, but rotate by 90deg clockwise + // the "red" turn restriction starting on link fj (instead of jk) will be applied first + var f = new Fixture(); + // make some other links more expensive to prevent routing along them + f.overwriteLinkLength("fg", 10000); + f.overwriteLinkLength("bc", 10000); + f.overwriteLinkLength("lk", 600); + f.overwriteLinkLength("lp", 600); + f.overwriteLinkLength("gh", 550); + f.overwriteLinkLength("fe", 550); + f.overwriteLinkLength("ij", 600); + f.overwriteLinkLength("kj", 600); + f.overwriteLinkLength("ok", 600); + f.overwriteLinkLength("nj", 600); + + var graph = runTest(f.network, "F", "H", "fj", "jk", "kl", "lh"); + Assertions.assertEquals(16, graph.nodeCount); + Assertions.assertEquals(48, graph.linkCount); + + f.addTurnRestriction("fj", "jk", "kg"); + f.addTurnRestriction("fj", "jn", "nm"); + + f.addTurnRestriction("jk", "kl", "lh"); + f.addTurnRestriction("jk", "ko"); + + verifyGraph(f.network, "F", new String[][] { + new String[] {"fj", "jk", "kg"}, + new String[] {"fj", "jn", "nm"}, + new String[] {"jk", "kl", "lh"}, + new String[] {"jk", "ko"}, + new String[] {"fj", "jk", "ko"}, + new String[] {"fj", "jk", "kl", "lh"}, + }); + + graph = runTest(f.network, "F", "H", "fj", "jn", "no", "op", "pl", "lh"); + Assertions.assertEquals(16 + 7, graph.nodeCount, "expected duplicated nodes: J', N', K', K'', L', K''', L''"); + Assertions.assertEquals(48 + 18, graph.linkCount, "expected duplicated links: j'i, j'f, j'n', n'j, n'o, k'j, k'o, k'l - k''j, k''g, k''l', l'k, l'p, - jk''', k'''j, k'''l'', l''k, l''p"); + } + + @Test + public void testShortRestrictionsForReuse() { + // corresponds to figure 8 in the documentation + // the "red" turn restriction starting on link kg will be applied first + var f = new Fixture(); + // make some other links more expensive to prevent routing along them + f.overwriteLinkLength("jf", 10000); + f.overwriteLinkLength("ie", 10000); + // make link kg same as link lh + f.overwriteLinkLength("gh", 600); + f.overwriteLinkLength("cd", 600); + f.overwriteLinkLength("nj", 10000); + f.overwriteLinkLength("lp", 600); + // make link no cheaper than link jk + f.overwriteLinkLength("no", 500); + + var graph = runTest(f.network, "J", "D", "jk", "kl", "lh", "hd"); + Assertions.assertEquals(16, graph.nodeCount); + Assertions.assertEquals(48, graph.linkCount); + + f.addTurnRestriction("jk", "kg"); + + f.addTurnRestriction("kl", "lh"); + +// SpeedyGraphBuilder.build(f.network, TransportMode.car).printDebug(); + verifyGraph(f.network, "J", new String[][] { + new String[] {"jk", "kg"}, + new String[] {"kl", "lh"}, + new String[] {"jk", "kl", "lh"}, + }); + + graph = runTest(f.network, "J", "D", "jn", "no", "op", "pl", "lh", "hd"); + Assertions.assertEquals(16 + 2, graph.nodeCount, "expected duplicated nodes: K', L'"); + Assertions.assertEquals(48 + 5, graph.linkCount, "expected duplicated links: k'j, k'o, k'l', l'k, l'p"); + } + + /** Checks that none of the provided paths exist in the graph based on the provided network */ + private static void verifyGraph(Network network, String fromNode, String[][] forbiddenPaths) { + SpeedyGraph graph = SpeedyGraphBuilder.build(network); + Node realFromNode = network.getNodes().get(Id.create(fromNode, Node.class)); + + for (String[] path : forbiddenPaths) { + if (findPath(graph, realFromNode.getId().index(), path)) { + Assertions.fail("Found path that should not exist in graph"); + } + } + + SpeedyGraph.LinkIterator outLinkIterator = graph.getOutLinkIterator(); + outLinkIterator.reset(realFromNode.getId().index()); + } + + private static boolean findPath(SpeedyGraph graph, int nodeIndex, String[] path) { + Id nextLinkId = Id.create(path[0], Link.class); + SpeedyGraph.LinkIterator outLinkIterator = graph.getOutLinkIterator(); + outLinkIterator.reset(nodeIndex); + while (outLinkIterator.next()) { + int linkIndex = outLinkIterator.getLinkIndex(); + Link link = graph.getLink(linkIndex); + if (link.getId() == nextLinkId) { + if (path.length == 1) { + return true; + } + return findPath(graph, outLinkIterator.getToNodeIndex(), Arrays.copyOfRange(path, 1, path.length)); + } + } + return false; + } + + private static SpeedyGraph runTest(Network network, String fromNode, String toNode, String... expectedPath) { + SpeedyGraph graph = SpeedyGraphBuilder.build(network); + FreespeedTravelTimeAndDisutility freespeed = new FreespeedTravelTimeAndDisutility(new ScoringConfigGroup()); + SpeedyDijkstra dijkstra = new SpeedyDijkstra(graph, freespeed, freespeed); + + Node realFromNode = network.getNodes().get(Id.create(fromNode, Node.class)); + Node realToNode = network.getNodes().get(Id.create(toNode, Node.class)); + LeastCostPathCalculator.Path path = dijkstra.calcLeastCostPath(realFromNode, realToNode, 7*3600, null, null); + + assertPath(path, expectedPath); + return graph; + } + + + private static void assertPath(LeastCostPathCalculator.Path path, String... linkIds) { + var links = path.links; + LOG.info("expected path: " + Arrays.toString(linkIds) + "\nactual path: " + path.links.stream().map(link -> link.getId().toString()).collect(Collectors.joining(","))); + Assertions.assertEquals(linkIds.length, links.size()); + for (int i = 0; i < linkIds.length; i++) { + Assertions.assertEquals(linkIds[i], links.get(i).getId().toString()); + } + } + + private static class Fixture { + private final Network network; + + public Fixture() { + this.network = NetworkUtils.createNetwork(); + + Node a = createAndAddNode("A", 1000, 2500); + Node b = createAndAddNode("B", 1500, 2500); + Node c = createAndAddNode("C", 2000, 2500); + Node d = createAndAddNode("D", 2500, 2500); + Node e = createAndAddNode("E", 1000, 2000); + Node f = createAndAddNode("F", 1500, 2000); + Node g = createAndAddNode("G", 2000, 2000); + Node h = createAndAddNode("H", 2500, 2000); + Node i = createAndAddNode("I", 1000, 1500); + Node j = createAndAddNode("J", 1500, 1500); + Node k = createAndAddNode("K", 2000, 1500); + Node l = createAndAddNode("L", 2500, 1500); + Node m = createAndAddNode("M", 1000, 1000); + Node n = createAndAddNode("N", 1500, 1000); + Node o = createAndAddNode("O", 2000, 1000); + Node p = createAndAddNode("P", 2500, 1000); + + createAndAddLink("ab", a, b, 500); + createAndAddLink("ba", b, a, 501); + createAndAddLink("bc", b, c, 502); + createAndAddLink("cb", c, b, 503); + createAndAddLink("cd", c, d, 504); + createAndAddLink("dc", d, c, 505); + + createAndAddLink("ae", a, e, 506); + createAndAddLink("ea", e, a, 507); + createAndAddLink("bf", b, f, 508); + createAndAddLink("fb", f, b, 509); + createAndAddLink("cg", c, g, 510); + createAndAddLink("gc", g, c, 511); + createAndAddLink("dh", d, h, 512); + createAndAddLink("hd", h, d, 513); + + createAndAddLink("ef", e, f, 514); + createAndAddLink("fe", f, e, 515); + createAndAddLink("fg", f, g, 516); + createAndAddLink("gf", g, f, 517); + createAndAddLink("gh", g, h, 518); + createAndAddLink("hg", h, g, 519); + + createAndAddLink("ei", e, i, 520); + createAndAddLink("ie", i, e, 521); + createAndAddLink("fj", f, j, 522); + createAndAddLink("jf", j, f, 523); + createAndAddLink("gk", g, k, 524); + createAndAddLink("kg", k, g, 525); + createAndAddLink("hl", h, l, 526); + createAndAddLink("lh", l, h, 527); + + createAndAddLink("ij", i, j, 528); + createAndAddLink("ji", j, i, 529); + createAndAddLink("jk", j, k, 530); + createAndAddLink("kj", k, j, 531); + createAndAddLink("kl", k, l, 532); + createAndAddLink("lk", l, k, 533); + + createAndAddLink("im", i, m, 534); + createAndAddLink("mi", m, i, 535); + createAndAddLink("jn", j, n, 536); + createAndAddLink("nj", n, j, 537); + createAndAddLink("ko", k, o, 538); + createAndAddLink("ok", o, k, 539); + createAndAddLink("lp", l, p, 540); + createAndAddLink("pl", p, l, 541); + + createAndAddLink("mn", m, n, 542); + createAndAddLink("nm", n, m, 543); + createAndAddLink("no", n, o, 544); + createAndAddLink("on", o, n, 545); + createAndAddLink("op", o, p, 546); + createAndAddLink("po", p, o, 547); + } + + private Node createAndAddNode(String id, double x, double y) { + Node node = this.network.getFactory().createNode(Id.create(id, Node.class), new Coord(x, y)); + this.network.addNode(node); + return node; + } + + private void createAndAddLink(String id, Node fromNode, Node toNode, double length) { + Link link = this.network.getFactory().createLink(Id.create(id, Link.class), fromNode, toNode); + link.setLength(length); + link.setFreespeed(20.0); + link.setCapacity(2000); + link.setNumberOfLanes(1); + link.setAllowedModes(Set.of(TransportMode.car)); + this.network.addLink(link); + } + + private void addTurnRestriction(String linkId, String... disallowedLinks) { + Id realLinkId = Id.create(linkId, Link.class); + Link link = this.network.getLinks().get(realLinkId); + var restrictions = NetworkUtils.getDisallowedNextLinks(link); + if (restrictions == null) { + restrictions = new DisallowedNextLinks(); + NetworkUtils.setDisallowedNextLinks(link, restrictions); + } + + List> disallowedLinkIds = Arrays.stream(disallowedLinks).map(id -> Id.create(id, Link.class)).toList(); + restrictions.addDisallowedLinkSequence(TransportMode.car, disallowedLinkIds); + } + + private void overwriteLinkLength(String linkId, double length) { + this.network.getLinks().get(Id.create(linkId, Link.class)).setLength(length); + } + } + +} diff --git a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphTest.java b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphTest.java index 4085ebd3b25..162fc907e31 100644 --- a/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphTest.java +++ b/matsim/src/test/java/org/matsim/core/router/speedy/SpeedyGraphTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.NetworkFactory; @@ -23,7 +24,7 @@ void testConstruction() { Fixture f = new Fixture(); Network network = f.network; - SpeedyGraph graph = new SpeedyGraph(network); + SpeedyGraph graph = SpeedyGraphBuilder.build(network); // test out-links @@ -236,4 +237,4 @@ private Link createLink(NetworkFactory nf, String id, Node fromNode, Node toNode } } -} \ No newline at end of file +}