diff --git a/matsim/src/main/java/org/matsim/core/network/algorithms/NetworkSimplifier.java b/matsim/src/main/java/org/matsim/core/network/algorithms/NetworkSimplifier.java index e3b450a3c94..e5329bc4717 100644 --- a/matsim/src/main/java/org/matsim/core/network/algorithms/NetworkSimplifier.java +++ b/matsim/src/main/java/org/matsim/core/network/algorithms/NetworkSimplifier.java @@ -62,7 +62,7 @@ */ public final class NetworkSimplifier { - private static final Logger log = LogManager.getLogger(NetworkSimplifier.class); + private static final Logger LOG = LogManager.getLogger(NetworkSimplifier.class); private boolean mergeLinksWithDifferentAttributes = false; private Collection nodeTopoToMerge = Arrays.asList( NetworkCalcTopoType.PASS1WAY , NetworkCalcTopoType.PASS2WAY ); @@ -134,11 +134,11 @@ private void run(final Network network, double thresholdLength, ThresholdExceede private void run(final Network network, double thresholdLength, ThresholdExceeded type, final BiPredicate isMergeable, final BiConsumer, Link> transferAttributes) { - if(this.nodeTopoToMerge.size() == 0){ + if(this.nodeTopoToMerge.isEmpty()){ throw new RuntimeException("No types of node specified. Please use setNodesToMerge to specify which nodes should be merged"); } - log.info("running " + this.getClass().getName() + " algorithm..."); + LOG.info("running {} algorithm...", this.getClass().getName()); NetworkCalcTopoType nodeTopo = new NetworkCalcTopoType(); nodeTopo.run(network); @@ -259,9 +259,9 @@ private void run(final Network network, double thresholdLength, ThresholdExceede } } - log.info(" resulting network contains " + network.getNodes().size() + " nodes and " + - network.getLinks().size() + " links."); - log.info("done."); + LOG.info(" resulting network contains {} nodes and {} links.", network.getNodes().size(), + network.getLinks().size()); + LOG.info("done."); // writes stats as a side effect nodeTopo = new NetworkCalcTopoType(); @@ -360,7 +360,7 @@ private boolean eitherLinkIsShorterThanThreshold(Link linkA, Link linkB, double /** * Compare link attributes. Return whether they are the same or not. */ - private boolean bothLinksHaveSameLinkStats(Link linkA, Link linkB){ + public static boolean bothLinksHaveSameLinkStats(Link linkA, Link linkB) { boolean bothLinksHaveSameLinkStats = true; @@ -376,6 +376,10 @@ private boolean bothLinksHaveSameLinkStats(Link linkA, Link linkB){ } public static void main(String[] args) { + if (args.length < 2) { + LOG.error("Required arguments: inNetworkFile outNetworkFile"); + return; + } final String inNetworkFile = args[ 0 ]; final String outNetworkFile = args[ 1 ]; diff --git a/matsim/src/main/java/org/matsim/pt/utils/PtNetworkSimplifier.java b/matsim/src/main/java/org/matsim/pt/utils/PtNetworkSimplifier.java new file mode 100644 index 00000000000..0f2880f3f1b --- /dev/null +++ b/matsim/src/main/java/org/matsim/pt/utils/PtNetworkSimplifier.java @@ -0,0 +1,364 @@ +/* *********************************************************************** * + * project: org.matsim.* + * CreateVehiclesForSchedule.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2022 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.pt.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.BiConsumer; + +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.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.NetworkWriter; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.algorithms.NetworkCalcTopoType; +import org.matsim.core.network.algorithms.NetworkSimplifier; +import org.matsim.core.network.io.MatsimNetworkReader; +import org.matsim.core.population.routes.LinkNetworkRouteFactory; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.collections.Tuple; +import org.matsim.core.utils.misc.Counter; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitSchedule; +import org.matsim.pt.transitSchedule.api.TransitScheduleReader; +import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +/** + * Simplifies a given network, by merging links. For all links with + * {@link TransportMode.pt}, the transit schedule is considered and adjusted + * when merging links. + * + * Copied and adapted from + * {@link playground.vsp.andreas.utils.pt.PTNetworkSimplifier} + * + * @author aneumann + * + */ +public class PtNetworkSimplifier { + + private static final Logger LOG = LogManager.getLogger(PtNetworkSimplifier.class); + + private final Counter counter = new Counter( + "[" + PtNetworkSimplifier.class.getSimpleName() + "] processed node # "); + + private boolean mergeLinkStats = false; + private TransitSchedule transitSchedule; + private TreeSet> linksNeededByTransitSchedule = null; + private Network network; + + private Set nodeTypesToMerge = new TreeSet<>( + Arrays.asList(NetworkCalcTopoType.PASS1WAY, NetworkCalcTopoType.PASS2WAY)); + + public static final BiConsumer, Link> DEFAULT_TRANSFER_ATTRIBUTES_CONSUMER = (inOutLinks, + newLink) -> { + // do nothing + }; + + /** + * Create PTNetworkSimplifier + * + * @param network + * @param transitSchedule + */ + public PtNetworkSimplifier(final Network network, final TransitSchedule transitSchedule) { + this.network = network; + this.transitSchedule = transitSchedule; + } + + /** + * Merges all qualifying links. + */ + public void run() { + run(DEFAULT_TRANSFER_ATTRIBUTES_CONSUMER); + } + + /** + * Merges all qualifying links + * + * @param transferAttributes consumer(Tuple.of(inLink, outLink), newLink) to + * customize merging of non-standard attributes + */ + public void run(final BiConsumer, Link> transferAttributes) { + + if (this.nodeTypesToMerge.isEmpty()) { + throw new RuntimeException("No types of node specified. Please use setNodesToMerge to specify which nodes should be merged"); + } + + LOG.info("running {} algorithm...", this.getClass().getName()); + + NetworkCalcTopoType nodeTopo = new NetworkCalcTopoType(); + nodeTopo.run(this.network); + + TreeSet> nodesConnectedToTransitStop = new TreeSet<>(); + for (Node node : this.network.getNodes().values()) { + this.counter.incCounter(); + + if (nodesConnectedToTransitStop.contains(node.getId())) { + continue; + } + + if (this.nodeTypesToMerge.contains(Integer.valueOf(nodeTopo.getTopoType(node)))) { + List iLinks = new ArrayList<>(node.getInLinks().values()); + + for (Link inLink : iLinks) { + List otLinks = new ArrayList<>(node.getOutLinks().values()); + + for (Link outLink : otLinks) { + + if (inLink != null && outLink != null && !outLink.getToNode().getId().equals(inLink.getFromNode().getId())) { + + if (!linkNeededByTransitStop(inLink, outLink)) { + Link link = null; + + if (this.mergeLinkStats) { + + // Try to merge both links by guessing the resulting links attributes + link = this.network.getFactory().createLink( + Id.create(inLink.getId() + "-" + outLink.getId(), Link.class), + inLink.getFromNode(), outLink.getToNode()); + + // length can be summed up + link.setLength(inLink.getLength() + outLink.getLength()); + + // freespeed depends on total length and time needed for inLink and outLink + link.setFreespeed((inLink.getLength() + outLink.getLength()) + / (NetworkUtils.getFreespeedTravelTime(inLink) + + NetworkUtils.getFreespeedTravelTime(outLink))); + + // the capacity and the new links end is important, thus it will be set to the + // minimum + link.setCapacity(Math.min(inLink.getCapacity(), outLink.getCapacity())); + + // number of lanes can be derived from the storage capacity of both links + link.setNumberOfLanes((inLink.getLength() * inLink.getNumberOfLanes() + + outLink.getLength() * outLink.getNumberOfLanes()) + / (inLink.getLength() + outLink.getLength())); + if (NetworkUtils.getOrigId(inLink) != null || NetworkUtils.getOrigId(outLink) != null) { + NetworkUtils.setOrigId(link, NetworkUtils.getOrigId(inLink) + "-" + NetworkUtils.getOrigId(outLink)); + } + + } else { + // Only merge links with same attributes + if (NetworkSimplifier.bothLinksHaveSameLinkStats(inLink, outLink)) { + link = this.network.getFactory().createLink( + Id.create(inLink.getId() + "-" + outLink.getId(), Link.class), + inLink.getFromNode(), outLink.getToNode()); + if (NetworkUtils.getOrigId(inLink) != null || NetworkUtils.getOrigId(outLink) != null) { + NetworkUtils.setOrigId(link, NetworkUtils.getOrigId(inLink) + "-" + NetworkUtils.getOrigId(outLink)); + } + link.setAllowedModes(inLink.getAllowedModes()); + link.setLength(inLink.getLength() + outLink.getLength()); + link.setFreespeed(inLink.getFreespeed()); + link.setCapacity(inLink.getCapacity()); + link.setNumberOfLanes(inLink.getNumberOfLanes()); + } + } + + if (link != null && !nodesConnectedToTransitStop.contains(node.getId()) + && !nodesConnectedToTransitStop.contains(link.getFromNode().getId()) + && !nodesConnectedToTransitStop.contains(link.getToNode().getId())) { + + transferAttributes.accept(Tuple.of(inLink, outLink), link); + if (inLink.getAllowedModes().contains(TransportMode.pt) || outLink.getAllowedModes().contains(TransportMode.pt)) { + if (removeLinksFromTransitSchedule(link, inLink, outLink)) { + this.network.addLink(link); + this.network.removeLink(inLink.getId()); + this.network.removeLink(outLink.getId()); + } + } else { + this.network.addLink(link); + this.network.removeLink(inLink.getId()); + this.network.removeLink(outLink.getId()); + } + } + + } else { + nodesConnectedToTransitStop.add(node.getId()); + } + } + } + } + } + } + + LOG.info(" resulting network contains {} nodes and {} links.", this.network.getNodes().size(), + this.network.getLinks().size()); + LOG.info("done."); + + // writes stats as a side effect + nodeTopo = new NetworkCalcTopoType(); + nodeTopo.run(this.network); + } + + private boolean areLinksMergeable(final Link inLink, final Link outLink) { + return this.transitSchedule.getTransitLines().values().parallelStream() + .allMatch(transitLine -> areLinksMergeable(transitLine, inLink, outLink)); + } + + private static boolean areLinksMergeable(final TransitLine transitLine, final Link inLink, final Link outLink) { + for (TransitRoute transitRoute : transitLine.getRoutes().values()) { + if (transitRoute.getRoute().getLinkIds().contains(inLink.getId()) + || transitRoute.getRoute().getLinkIds().contains(outLink.getId())) { + + LinkedList> routeLinkIds = new LinkedList<>(); + routeLinkIds.add(transitRoute.getRoute().getStartLinkId()); + for (Id id : transitRoute.getRoute().getLinkIds()) { + routeLinkIds.add(id); + } + routeLinkIds.add(transitRoute.getRoute().getEndLinkId()); + + for (Iterator> iterator = routeLinkIds.iterator(); iterator.hasNext();) { + Id id = iterator.next(); + if (id.equals(inLink.getId())) { + Id nextId = iterator.next(); + if (nextId.equals(outLink.getId())) { + // everything okay + break; + } else { + // inLink and outLink ar not followers, thus they should not be touched + return false; + } + } else if (id.equals(outLink.getId())) { + // if we find the outLink before/without the inLink, we should not merge + return false; + } + } + } + } + return true; + } + + private boolean removeLinksFromTransitSchedule(Link link, Link inLink, Link outLink) { + // check if links can be merged considering all transit routes + if (!areLinksMergeable(inLink, outLink)) { + return false; + } + + // merge links + for (TransitLine transitLine : this.transitSchedule.getTransitLines().values()) { + for (TransitRoute transitRoute : transitLine.getRoutes().values()) { + + if (transitRoute.getRoute().getLinkIds().contains(inLink.getId()) + && transitRoute.getRoute().getLinkIds().contains(outLink.getId())) { + + LinkedList> routeLinkIds = new LinkedList<>(); + routeLinkIds.add(transitRoute.getRoute().getStartLinkId()); + for (Id id : transitRoute.getRoute().getLinkIds()) { + routeLinkIds.add(id); + } + routeLinkIds.add(transitRoute.getRoute().getEndLinkId()); + + if (routeLinkIds.contains(inLink.getId()) && routeLinkIds.contains(outLink.getId())) { + routeLinkIds.add(routeLinkIds.indexOf(inLink.getId()), link.getId()); + routeLinkIds.remove(inLink.getId()); + routeLinkIds.remove(outLink.getId()); + } + + NetworkRoute newRoute = (NetworkRoute) new LinkNetworkRouteFactory() + .createRoute(routeLinkIds.getFirst(), routeLinkIds.getLast()); + Id startLink = routeLinkIds.pollFirst(); + Id endLink = routeLinkIds.pollLast(); + newRoute.setLinkIds(startLink, routeLinkIds, endLink); + transitRoute.setRoute(newRoute); + } + } + } + return true; + } + + private boolean linkNeededByTransitStop(final Link inLink, final Link outLink) { + if (this.linksNeededByTransitSchedule == null) { + this.linksNeededByTransitSchedule = new TreeSet<>(); + for (TransitStopFacility transitStopFacility : this.transitSchedule.getFacilities().values()) { + this.linksNeededByTransitSchedule.add(transitStopFacility.getLinkId()); + } + } + return this.linksNeededByTransitSchedule.contains(inLink.getId()) || this.linksNeededByTransitSchedule.contains(outLink.getId()); + } + + /** + * Specify the types of node which should be merged. + * + * See {@link NetworkCalcTopoType} for a list of available classifications. + * + * @param nodeTypesToMerge A set of integers indicating the node types as + * specified by {@link NetworkCalcTopoType} + */ + public void setNodesToMerge(Set nodeTypesToMerge) { + this.nodeTypesToMerge.addAll(nodeTypesToMerge); + } + + /** + * Set, whether links with different standard attributes should be merged. + * + * @param mergeLinkStats If set true, links will be merged despite their + * different attributes. + * If set false (default), only links with the same + * attributes will be merged, thus preserving as much + * information as possible. + */ + public void setMergeLinkStats(boolean mergeLinkStats) { + this.mergeLinkStats = mergeLinkStats; + } + + public static void main(String[] args) { + if (args.length < 4) { + LOG.error("Required arguments: inNetworkFile inScheduleFile outNetworkFile outScheduleFile"); + return; + } + final String inNetworkFile = args[0]; + final String inScheduleFile = args[1]; + final String outNetworkFile = args[2]; + final String outScheduleFile = args[3]; + + Set nodeTypesToMerge = new TreeSet<>(); + nodeTypesToMerge.add(NetworkCalcTopoType.PASS1WAY); + nodeTypesToMerge.add(NetworkCalcTopoType.PASS2WAY); + + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); + new MatsimNetworkReader(scenario.getNetwork()).readFile(inNetworkFile); + new TransitScheduleReader(scenario).readFile(inScheduleFile); + + PtNetworkSimplifier ptSimplifier = new PtNetworkSimplifier(scenario.getNetwork(), + scenario.getTransitSchedule()); + ptSimplifier.setNodesToMerge(nodeTypesToMerge); + LOG.info("Simplifying network and merging node types: {}", nodeTypesToMerge); + ptSimplifier.run(); + + new NetworkWriter(scenario.getNetwork()).write(outNetworkFile); + new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(outScheduleFile); + } + +} \ No newline at end of file diff --git a/matsim/src/test/java/org/matsim/pt/utils/PtNetworkSimplifierTest.java b/matsim/src/test/java/org/matsim/pt/utils/PtNetworkSimplifierTest.java new file mode 100644 index 00000000000..e5fb918cdba --- /dev/null +++ b/matsim/src/test/java/org/matsim/pt/utils/PtNetworkSimplifierTest.java @@ -0,0 +1,216 @@ +package org.matsim.pt.utils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.Assert; +import org.junit.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.ConfigUtils; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.algorithms.NetworkCalcTopoType; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.core.population.routes.RouteUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.geometry.CoordUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitSchedule; +import org.matsim.pt.transitSchedule.api.TransitScheduleFactory; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +public class PtNetworkSimplifierTest { + + @Test + public void testPtNetworkSimplifier() { + Scenario scenario = buildScenario(); + + Set nodeTypesToMerge = new TreeSet<>(); + nodeTypesToMerge.add(NetworkCalcTopoType.PASS1WAY); + nodeTypesToMerge.add(NetworkCalcTopoType.PASS2WAY); + + PtNetworkSimplifier ptSimplifier = new PtNetworkSimplifier(scenario.getNetwork(), + scenario.getTransitSchedule()); + ptSimplifier.setNodesToMerge(nodeTypesToMerge); + ptSimplifier.run(); + + // remove unused nodes + Set> nodeIdsToRemove = new HashSet<>(); + for (Node node : scenario.getNetwork().getNodes().values()) { + if (node.getInLinks().isEmpty() && node.getOutLinks().isEmpty()) { + nodeIdsToRemove.add(node.getId()); + } + } + nodeIdsToRemove.stream().forEach(scenario.getNetwork()::removeNode); + + // check network and transit route changes + List> addedLinkIds = List.of( + Id.createLinkId("FG-GH"), + Id.createLinkId("CX-XY")); + List> removedLinkIds = List.of( + Id.createLinkId("FG"), + Id.createLinkId("GH"), + Id.createLinkId("CX"), + Id.createLinkId("XY")); + List> removedNodeIds = List.of( + Id.createNodeId("G"), + Id.createNodeId("X")); + + // check network + for (Id linkId : addedLinkIds) { + Assert.assertTrue(scenario.getNetwork().getLinks().keySet().contains(linkId)); + } + for (Id linkId : removedLinkIds) { + Assert.assertFalse(scenario.getNetwork().getLinks().keySet().contains(linkId)); + } + for (Id nodeId : removedNodeIds) { + Assert.assertFalse(scenario.getNetwork().getNodes().keySet().contains(nodeId)); + } + + // check transit routes + for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { + for (TransitRoute route : line.getRoutes().values()) { + for (Id linkId : removedLinkIds) { + Assert.assertFalse(route.getRoute().getLinkIds().contains(linkId)); + } + } + } + Assert.assertTrue( + scenario.getTransitSchedule().getTransitLines().get(Id.create("ABCDEFGHI", TransitLine.class)) + .getRoutes().get(Id.create("ABCDEFGHI-route", TransitRoute.class)) + .getRoute().getLinkIds().contains(Id.createLinkId("FG-GH"))); + Assert.assertTrue( + scenario.getTransitSchedule().getTransitLines().get(Id.create("ABCXYZ", TransitLine.class)) + .getRoutes().get(Id.create("ABCXYZ-route", TransitRoute.class)) + .getRoute().getLinkIds().contains(Id.createLinkId("CX-XY"))); + } + + /** + * Creates a scenario with a pt network where * are stop facilities used by + * transit lines ABCDEFGHI and ABCXYZ. + * + * A -*-> B ---> C ---> D -*-> E ---> F ---> G ---> H -*-> I + * | + * | + * | + * v + * X + * | + * | + * | + * v + * Y + * | + * * + * | + * v + * Z + * + */ + static Scenario buildScenario() { + + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); + + Network network = scenario.getNetwork(); + Node a = NetworkUtils.createAndAddNode(network, Id.createNodeId("A"), CoordUtils.createCoord(0.0, 0.0)); + Node b = NetworkUtils.createAndAddNode(network, Id.createNodeId("B"), CoordUtils.createCoord(10.0, 0.0)); + Node c = NetworkUtils.createAndAddNode(network, Id.createNodeId("C"), CoordUtils.createCoord(20.0, 0.0)); + Node d = NetworkUtils.createAndAddNode(network, Id.createNodeId("D"), CoordUtils.createCoord(30.0, 0.0)); + Node e = NetworkUtils.createAndAddNode(network, Id.createNodeId("E"), CoordUtils.createCoord(40.0, 0.0)); + Node f = NetworkUtils.createAndAddNode(network, Id.createNodeId("F"), CoordUtils.createCoord(50.0, 0.0)); + Node g = NetworkUtils.createAndAddNode(network, Id.createNodeId("G"), CoordUtils.createCoord(60.0, 0.0)); + Node h = NetworkUtils.createAndAddNode(network, Id.createNodeId("H"), CoordUtils.createCoord(70.0, 0.0)); + Node i = NetworkUtils.createAndAddNode(network, Id.createNodeId("I"), CoordUtils.createCoord(80.0, 0.0)); + Node x = NetworkUtils.createAndAddNode(network, Id.createNodeId("X"), CoordUtils.createCoord(20.0, -10.0)); + Node y = NetworkUtils.createAndAddNode(network, Id.createNodeId("Y"), CoordUtils.createCoord(20.0, -20.0)); + Node z = NetworkUtils.createAndAddNode(network, Id.createNodeId("Z"), CoordUtils.createCoord(20.0, -30.0)); + Id linkIdAB = Id.createLinkId("AB"); + Id linkIdBC = Id.createLinkId("BC"); + Id linkIdCD = Id.createLinkId("CD"); + Id linkIdDE = Id.createLinkId("DE"); + Id linkIdEF = Id.createLinkId("EF"); + Id linkIdFG = Id.createLinkId("FG"); + Id linkIdGH = Id.createLinkId("GH"); + Id linkIdHI = Id.createLinkId("HI"); + Id linkIdCX = Id.createLinkId("CX"); + Id linkIdXY = Id.createLinkId("XY"); + Id linkIdYZ = Id.createLinkId("YZ"); + Link linkAB = NetworkUtils.createAndAddLink(network, linkIdAB, a, b, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkBC = NetworkUtils.createAndAddLink(network, linkIdBC, b, c, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkCD = NetworkUtils.createAndAddLink(network, linkIdCD, c, d, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkDE = NetworkUtils.createAndAddLink(network, linkIdDE, d, e, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkEF = NetworkUtils.createAndAddLink(network, linkIdEF, e, f, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkFG = NetworkUtils.createAndAddLink(network, linkIdFG, f, g, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkGH = NetworkUtils.createAndAddLink(network, linkIdGH, g, h, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkHI = NetworkUtils.createAndAddLink(network, linkIdHI, h, i, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkCX = NetworkUtils.createAndAddLink(network, linkIdCX, c, x, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkXY = NetworkUtils.createAndAddLink(network, linkIdXY, x, y, 10.0, 80.0 / 3.6, 9999.0, 1); + Link linkYZ = NetworkUtils.createAndAddLink(network, linkIdYZ, y, z, 10.0, 80.0 / 3.6, 9999.0, 1); + linkAB.setAllowedModes(Set.of(TransportMode.pt)); + linkBC.setAllowedModes(Set.of(TransportMode.pt)); + linkCD.setAllowedModes(Set.of(TransportMode.pt)); + linkDE.setAllowedModes(Set.of(TransportMode.pt)); + linkEF.setAllowedModes(Set.of(TransportMode.pt)); + linkFG.setAllowedModes(Set.of(TransportMode.pt)); + linkGH.setAllowedModes(Set.of(TransportMode.pt)); + linkHI.setAllowedModes(Set.of(TransportMode.pt)); + linkCX.setAllowedModes(Set.of(TransportMode.pt)); + linkXY.setAllowedModes(Set.of(TransportMode.pt)); + linkYZ.setAllowedModes(Set.of(TransportMode.pt)); + + TransitSchedule schedule = scenario.getTransitSchedule(); + TransitScheduleFactory scheduleFactory = schedule.getFactory(); + TransitStopFacility stopAB = scheduleFactory + .createTransitStopFacility(Id.create("AB", TransitStopFacility.class), new Coord(5., 0.), false); + stopAB.setLinkId(linkIdAB); + schedule.addStopFacility(stopAB); + TransitStopFacility stopDE = scheduleFactory + .createTransitStopFacility(Id.create("DE", TransitStopFacility.class), new Coord(35., 0.), false); + stopDE.setLinkId(linkIdDE); + schedule.addStopFacility(stopDE); + TransitStopFacility stopHI = scheduleFactory + .createTransitStopFacility(Id.create("HI", TransitStopFacility.class), new Coord(75., 0.), false); + stopHI.setLinkId(linkIdHI); + schedule.addStopFacility(stopHI); + TransitStopFacility stopYZ = scheduleFactory + .createTransitStopFacility(Id.create("YZ", TransitStopFacility.class), new Coord(20., -25.), false); + stopYZ.setLinkId(linkIdYZ); + schedule.addStopFacility(stopYZ); + + TransitLine line1 = scheduleFactory.createTransitLine(Id.create("ABCDEFGHI", TransitLine.class)); + NetworkRoute networkRoute1 = RouteUtils.createLinkNetworkRouteImpl(linkIdAB, + List.of(linkIdBC, linkIdCD, linkIdDE, linkIdEF, linkIdFG, linkIdGH), linkIdHI); + List route1Stops = List.of( + scheduleFactory.createTransitRouteStop(stopAB, 0, 3), + scheduleFactory.createTransitRouteStop(stopDE, 10, 13), + scheduleFactory.createTransitRouteStop(stopHI, 20, 23)); + TransitRoute transitRoute1 = scheduleFactory.createTransitRoute( + Id.create("ABCDEFGHI-route", TransitRoute.class), + networkRoute1, route1Stops, TransportMode.pt); + line1.addRoute(transitRoute1); + schedule.addTransitLine(line1); + + TransitLine line2 = scheduleFactory.createTransitLine(Id.create("ABCXYZ", TransitLine.class)); + NetworkRoute networkRoute2 = RouteUtils.createLinkNetworkRouteImpl(linkIdAB, + List.of(linkIdBC, linkIdCX, linkIdXY), linkIdYZ); + List route2Stops = List.of( + scheduleFactory.createTransitRouteStop(stopAB, 0, 3), + scheduleFactory.createTransitRouteStop(stopYZ, 10, 13)); + TransitRoute transitRoute2 = scheduleFactory.createTransitRoute(Id.create("ABCXYZ-route", TransitRoute.class), + networkRoute2, route2Stops, TransportMode.pt); + line2.addRoute(transitRoute2); + schedule.addTransitLine(line2); + + return scenario; + } + +}