View Javadoc
1   package org.opentrafficsim.demo;
2   
3   import java.io.Serializable;
4   import java.rmi.RemoteException;
5   import java.util.ArrayList;
6   import java.util.LinkedHashSet;
7   import java.util.List;
8   import java.util.Set;
9   import java.util.SortedMap;
10  import java.util.TreeMap;
11  
12  import javax.naming.NamingException;
13  
14  import org.djunits.unit.DurationUnit;
15  import org.djunits.unit.LengthUnit;
16  import org.djunits.unit.SpeedUnit;
17  import org.djunits.unit.util.UNITS;
18  import org.djunits.value.vdouble.scalar.Direction;
19  import org.djunits.value.vdouble.scalar.Duration;
20  import org.djunits.value.vdouble.scalar.Length;
21  import org.djunits.value.vdouble.scalar.Speed;
22  import org.djunits.value.vdouble.scalar.base.DoubleScalar;
23  import org.djutils.event.EventInterface;
24  import org.djutils.event.EventListenerInterface;
25  import org.djutils.event.EventTypeInterface;
26  import org.opentrafficsim.base.parameters.ParameterException;
27  import org.opentrafficsim.core.compatibility.Compatible;
28  import org.opentrafficsim.core.distributions.Distribution;
29  import org.opentrafficsim.core.distributions.Distribution.FrequencyAndObject;
30  import org.opentrafficsim.core.distributions.Generator;
31  import org.opentrafficsim.core.distributions.ProbabilityException;
32  import org.opentrafficsim.core.dsol.AbstractOTSModel;
33  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
34  import org.opentrafficsim.core.geometry.OTSGeometryException;
35  import org.opentrafficsim.core.geometry.OTSPoint3D;
36  import org.opentrafficsim.core.gtu.GTU;
37  import org.opentrafficsim.core.gtu.GTUDirectionality;
38  import org.opentrafficsim.core.gtu.GTUException;
39  import org.opentrafficsim.core.gtu.GTUType;
40  import org.opentrafficsim.core.idgenerator.IdGenerator;
41  import org.opentrafficsim.core.network.Network;
42  import org.opentrafficsim.core.network.NetworkException;
43  import org.opentrafficsim.core.network.Node;
44  import org.opentrafficsim.core.network.route.FixedRouteGenerator;
45  import org.opentrafficsim.core.network.route.ProbabilisticRouteGenerator;
46  import org.opentrafficsim.core.network.route.Route;
47  import org.opentrafficsim.core.parameters.ParameterFactory;
48  import org.opentrafficsim.core.units.distributions.ContinuousDistDoubleScalar;
49  import org.opentrafficsim.road.gtu.generator.CFRoomChecker;
50  import org.opentrafficsim.road.gtu.generator.GeneratorPositions;
51  import org.opentrafficsim.road.gtu.generator.LaneBasedGTUGenerator;
52  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedTemplateGTUType;
53  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedTemplateGTUTypeDistribution;
54  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusFactory;
55  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLMRSPerceptionFactory;
56  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRSFactory;
57  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
58  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
59  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlannerFactory;
60  import org.opentrafficsim.road.network.OTSRoadNetwork;
61  import org.opentrafficsim.road.network.factory.LaneFactory;
62  import org.opentrafficsim.road.network.lane.CrossSectionLink;
63  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
64  import org.opentrafficsim.road.network.lane.Lane;
65  import org.opentrafficsim.road.network.lane.LaneType;
66  import org.opentrafficsim.road.network.lane.OTSRoadNode;
67  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
68  
69  import nl.tudelft.simulation.dsol.SimRuntimeException;
70  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterDouble;
71  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterException;
72  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterMap;
73  import nl.tudelft.simulation.dsol.model.inputparameters.InputParameterSelectionMap;
74  import nl.tudelft.simulation.jstats.distributions.DistContinuous;
75  import nl.tudelft.simulation.jstats.distributions.DistErlang;
76  import nl.tudelft.simulation.jstats.distributions.DistUniform;
77  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
78  import nl.tudelft.simulation.jstats.streams.StreamInterface;
79  
80  /**
81   * Simulate a single lane road of 5 km length. Vehicles are generated at a constant rate of 1500 veh/hour. At time 300s a
82   * blockade is inserted at position 4 km; this blockade is removed at time 500s. The used car following algorithm is IDM+
83   * <a href="http://opentrafficsim.org/downloads/MOTUS%20reference.pdf"><i>Integrated Lane Change Model with Relaxation and
84   * Synchronization</i>, by Wouter J. Schakel, Victor L. Knoop and Bart van Arem, 2012</a>. <br>
85   * Output is a set of block charts:
86   * <ul>
87   * <li>Traffic density</li>
88   * <li>Speed</li>
89   * <li>Flow</li>
90   * <li>Acceleration</li>
91   * </ul>
92   * All these graphs display simulation time along the horizontal axis and distance along the road along the vertical axis.
93   * <p>
94   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
95   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
96   * <p>
97   * $LastChangedDate: 2019-01-06 01:35:05 +0100 (Sun, 06 Jan 2019) $, @version $Revision: 4831 $, by $Author: averbraeck $,
98   * initial version ug 1, 2014 <br>
99   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
100  */
101 public class NetworksModel extends AbstractOTSModel implements EventListenerInterface, UNITS
102 {
103     /** */
104     private static final long serialVersionUID = 20140815L;
105 
106     /** The network. */
107     private final OTSRoadNetwork network = new OTSRoadNetwork("network", true, getSimulator());
108 
109     /** Strategical planner generator for cars. */
110     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerFactoryCars = null;
111 
112     /** Strategical planner generator for trucks. */
113     private LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerFactoryTrucks = null;
114 
115     /** The probability that the next generated GTU is a passenger car. */
116     private double carProbability;
117 
118     /** Minimum distance. */
119     private Length minimumDistance = new Length(0, METER);
120 
121     /** Maximum distance. */
122     private Length maximumDistance = new Length(5000, METER);
123 
124     /** The random number generator used to decide what kind of GTU to generate. */
125     private StreamInterface stream = new MersenneTwister(12345);
126 
127     /** The route generator for the main line. */
128     private Generator<Route> routeGeneratorMain;
129 
130     /** The route generator for the onramp. */
131     private Generator<Route> routeGeneratorRamp;
132 
133     /** The speed limit. */
134     private Speed speedLimit = new Speed(60, KM_PER_HOUR);
135 
136     /** The sequence of Lanes that all vehicles will follow. */
137     private List<List<Lane>> paths = new ArrayList<>();
138 
139     /** Id generator (used by all generators). */
140     private IdGenerator idGenerator = new IdGenerator("");
141 
142     /** The probability distribution for the variable part of the headway. */
143     private DistContinuous headwayGenerator;
144 
145     /**
146      * @param simulator OTSSimulatorInterface; the simulator for this model
147      */
148     public NetworksModel(final OTSSimulatorInterface simulator)
149     {
150         super(simulator);
151         createInputParameters();
152     }
153 
154     /**
155      * Create input parameters for the networks demo.
156      */
157     private void createInputParameters()
158     {
159         InputParameterHelper.makeInputParameterMapCarTruck(this.inputParameterMap, 1.0);
160         try
161         {
162             InputParameterMap genericMap = (InputParameterMap) this.inputParameterMap.get("generic");
163 
164             genericMap.add(new InputParameterDouble("flow", "Flow per input lane", "Traffic flow per input lane", 500d, 0d,
165                     3000d, true, true, "%.0f veh/h", 1.5));
166 
167             SortedMap<String, String> networks = new TreeMap<>();
168             networks.put("Merge 1 plus 1 into 1", "M111");
169             networks.put("Merge 2 plus 1 into 2", "M212");
170             networks.put("Merge 2 plus 2 into 4", "M224");
171             networks.put("Split 1 into 1 plus 1", "S111");
172             networks.put("Split 2 into 1 plus 2", "S212");
173             networks.put("Split 4 into 2 plus 2", "S422");
174             InputParameterSelectionMap<String, String> paramSelect = new InputParameterSelectionMap<String, String>("network",
175                     "Network to run simulation for", "Network to run simulaton for", networks, "M111", 2.0);
176             genericMap.add(paramSelect);
177         }
178         catch (InputParameterException exception)
179         {
180             exception.printStackTrace();
181         }
182 
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     @SuppressWarnings("checkstyle:methodlength")
188     public final void constructModel() throws SimRuntimeException
189     {
190         this.network.addListener(this, Network.GTU_ADD_EVENT);
191         this.network.addListener(this, Network.GTU_REMOVE_EVENT);
192         try
193         {
194             GTUType car = this.network.getGtuType(GTUType.DEFAULTS.CAR);
195             this.carProbability = (double) getInputParameter("generic.carProbability");
196 
197             ParameterFactory params = new InputParameterHelper(getInputParameterMap());
198             this.strategicalPlannerFactoryCars = new LaneBasedStrategicalRoutePlannerFactory(
199                     new LMRSFactory(new IDMPlusFactory(this.stream), new DefaultLMRSPerceptionFactory()), params);
200             this.strategicalPlannerFactoryTrucks = new LaneBasedStrategicalRoutePlannerFactory(
201                     new LMRSFactory(new IDMPlusFactory(this.stream), new DefaultLMRSPerceptionFactory()), params);
202 
203             OTSPoint3D pFrom2a = new OTSPoint3D(0, -50, 0);
204             OTSPoint3D pFrom2b = new OTSPoint3D(490, -0.5, 0);
205             Direction onrampDirection = pFrom2a.horizontalDirection(pFrom2b);
206             OTSRoadNode from = new OTSRoadNode(this.network, "From", new OTSPoint3D(0, 0, 0), Direction.ZERO);
207             OTSRoadNode end = new OTSRoadNode(this.network, "End", new OTSPoint3D(2000, 0, 0), Direction.ZERO);
208             OTSRoadNode from2a = new OTSRoadNode(this.network, "From2a", pFrom2a, onrampDirection);
209             OTSRoadNode from2b = new OTSRoadNode(this.network, "From2b", pFrom2b, onrampDirection);
210             OTSRoadNode firstVia = new OTSRoadNode(this.network, "Via1", new OTSPoint3D(500, 0, 0), Direction.ZERO);
211             OTSPoint3D pEnd2a = new OTSPoint3D(1020, -0.5, 0);
212             OTSPoint3D pEnd2b = new OTSPoint3D(2000, -50, 0);
213             Direction offrampDirection = pEnd2a.horizontalDirection(pEnd2b);
214             OTSRoadNode end2a = new OTSRoadNode(this.network, "End2a", pEnd2a, offrampDirection);
215             OTSRoadNode end2b = new OTSRoadNode(this.network, "End2b", pEnd2b, offrampDirection);
216             OTSRoadNode secondVia = new OTSRoadNode(this.network, "Via2", new OTSPoint3D(1000, 0, 0), Direction.ZERO);
217 
218             String networkType = getInputParameter("generic.network").toString();
219             boolean merge = networkType.startsWith("M");
220             int lanesOnMain = Integer.parseInt("" + networkType.charAt(merge ? 1 : 3));
221             int lanesOnBranch = Integer.parseInt("" + networkType.charAt(2));
222             int lanesOnCommon = lanesOnMain + lanesOnBranch;
223             int lanesOnCommonCompressed = Integer.parseInt("" + networkType.charAt(merge ? 3 : 1));
224 
225             double contP = (double) getInputParameter("generic.flow");
226             Duration averageHeadway = new Duration(3600.0 / contP, SECOND);
227             Duration minimumHeadway = new Duration(3, SECOND);
228             this.headwayGenerator =
229                     new DistErlang(new MersenneTwister(1234), 4, DoubleScalar.minus(averageHeadway, minimumHeadway).getSI());
230 
231             LaneType laneType = this.network.getLaneType(LaneType.DEFAULTS.TWO_WAY_LANE);
232             if (merge)
233             {
234                 // provide a route -- at the merge point, the GTU can otherwise decide to "go back"
235                 ArrayList<Node> mainRouteNodes = new ArrayList<>();
236                 mainRouteNodes.add(from);
237                 mainRouteNodes.add(firstVia);
238                 mainRouteNodes.add(secondVia);
239                 mainRouteNodes.add(end);
240                 Route mainRoute = new Route("main", mainRouteNodes);
241                 this.routeGeneratorMain = new FixedRouteGenerator(mainRoute);
242 
243                 ArrayList<Node> rampRouteNodes = new ArrayList<>();
244                 rampRouteNodes.add(from2a);
245                 rampRouteNodes.add(from2b);
246                 rampRouteNodes.add(firstVia);
247                 rampRouteNodes.add(secondVia);
248                 rampRouteNodes.add(end);
249                 Route rampRoute = new Route("ramp", rampRouteNodes);
250                 this.routeGeneratorRamp = new FixedRouteGenerator(rampRoute);
251             }
252             else
253             {
254                 // determine the routes
255                 List<FrequencyAndObject<Route>> routeProbabilities = new ArrayList<>();
256 
257                 ArrayList<Node> mainRouteNodes = new ArrayList<>();
258                 mainRouteNodes.add(from);
259                 mainRouteNodes.add(firstVia);
260                 mainRouteNodes.add(secondVia);
261                 mainRouteNodes.add(end);
262                 Route mainRoute = new Route("main", mainRouteNodes);
263                 routeProbabilities.add(new FrequencyAndObject<>(lanesOnMain, mainRoute));
264 
265                 ArrayList<Node> sideRouteNodes = new ArrayList<>();
266                 sideRouteNodes.add(from);
267                 sideRouteNodes.add(firstVia);
268                 sideRouteNodes.add(secondVia);
269                 sideRouteNodes.add(end2a);
270                 sideRouteNodes.add(end2b);
271                 Route sideRoute = new Route("side", sideRouteNodes);
272                 routeProbabilities.add(new FrequencyAndObject<>(lanesOnBranch, sideRoute));
273                 try
274                 {
275                     this.routeGeneratorMain = new ProbabilisticRouteGenerator(routeProbabilities, new MersenneTwister(1234));
276                 }
277                 catch (ProbabilityException exception)
278                 {
279                     exception.printStackTrace();
280                 }
281             }
282 
283             if (merge)
284             {
285                 setupGenerator(LaneFactory.makeMultiLane(this.network, "From2a to From2b", from2a, from2b, null, lanesOnBranch,
286                         0, lanesOnCommon - lanesOnBranch, laneType, this.speedLimit, this.simulator));
287                 LaneFactory.makeMultiLaneBezier(this.network, "From2b to FirstVia", from2a, from2b, firstVia, secondVia,
288                         lanesOnBranch, lanesOnCommon - lanesOnBranch, lanesOnCommon - lanesOnBranch, laneType, this.speedLimit,
289                         this.simulator);
290             }
291             else
292             {
293                 LaneFactory.makeMultiLaneBezier(this.network, "SecondVia to end2a", firstVia, secondVia, end2a, end2b,
294                         lanesOnBranch, lanesOnCommon - lanesOnBranch, lanesOnCommon - lanesOnBranch, laneType, this.speedLimit,
295                         this.simulator);
296                 setupSink(LaneFactory.makeMultiLane(this.network, "end2a to end2b", end2a, end2b, null, lanesOnBranch,
297                         lanesOnCommon - lanesOnBranch, 0, laneType, this.speedLimit, this.simulator), laneType);
298             }
299 
300             Lane[] startLanes = LaneFactory.makeMultiLane(this.network, "From to FirstVia", from, firstVia, null,
301                     merge ? lanesOnMain : lanesOnCommonCompressed, laneType, this.speedLimit, this.simulator);
302             setupGenerator(startLanes);
303             Lane[] common = LaneFactory.makeMultiLane(this.network, "FirstVia to SecondVia", firstVia, secondVia, null,
304                     lanesOnCommon, laneType, this.speedLimit, this.simulator);
305             setupSink(
306                     LaneFactory.makeMultiLane(this.network, "SecondVia to end", secondVia, end, null,
307                             merge ? lanesOnCommonCompressed : lanesOnMain, laneType, this.speedLimit, this.simulator),
308                     laneType);
309 
310             for (int index = 0; index < lanesOnCommon; index++)
311             {
312                 this.paths.add(new ArrayList<Lane>());
313                 Lane lane = common[index];
314                 // Follow back
315                 while (lane.prevLanes(car).size() > 0)
316                 {
317                     if (lane.prevLanes(car).size() > 1)
318                     {
319                         throw new NetworkException("This network should not have lane merge points");
320                     }
321                     lane = lane.prevLanes(car).keySet().iterator().next();
322                 }
323                 // Follow forward
324                 while (true)
325                 {
326                     this.paths.get(index).add(lane);
327                     int branching = lane.nextLanes(car).size();
328                     if (branching == 0)
329                     {
330                         break;
331                     }
332                     if (branching > 1)
333                     {
334                         throw new NetworkException("This network should not have lane split points");
335                     }
336                     lane = lane.nextLanes(car).keySet().iterator().next();
337                 }
338             }
339         }
340         catch (SimRuntimeException | NetworkException | OTSGeometryException | InputParameterException | GTUException
341                 | ParameterException | NamingException | ProbabilityException exception)
342         {
343             exception.printStackTrace();
344         }
345     }
346 
347     /**
348      * Add a generator to an array of Lane.
349      * @param lanes Lane[]; the lanes that must get a generator at the start
350      * @return Lane[]; the lanes
351      * @throws GTUException when lane position out of bounds
352      * @throws SimRuntimeException when generation scheduling fails
353      * @throws ProbabilityException when probability distribution is wrong
354      * @throws ParameterException when a parameter is missing for the perception of the GTU
355      */
356     private Lane[] setupGenerator(final Lane[] lanes)
357             throws SimRuntimeException, GTUException, ProbabilityException, ParameterException
358     {
359         for (Lane lane : lanes)
360         {
361             makeGenerator(lane);
362         }
363         return lanes;
364     }
365 
366     /**
367      * Build a generator.
368      * @param lane Lane; the lane on which the generated GTUs are placed
369      * @return LaneBasedGTUGenerator
370      * @throws GTUException when lane position out of bounds
371      * @throws SimRuntimeException when generation scheduling fails
372      * @throws ProbabilityException when probability distribution is wrong
373      * @throws ParameterException when a parameter is missing for the perception of the GTU
374      */
375     private LaneBasedGTUGenerator makeGenerator(final Lane lane)
376             throws GTUException, SimRuntimeException, ProbabilityException, ParameterException
377     {
378         Distribution<LaneBasedTemplateGTUType> distribution = new Distribution<>(this.stream);
379         Length initialPosition = new Length(16, METER);
380         Set<DirectedLanePosition> initialPositions = new LinkedHashSet<>(1);
381         initialPositions.add(new DirectedLanePosition(lane, initialPosition, GTUDirectionality.DIR_PLUS));
382 
383         LaneBasedTemplateGTUType template = makeTemplate(this.stream, lane,
384                 new ContinuousDistDoubleScalar.Rel<Length, LengthUnit>(new DistUniform(this.stream, 3, 6), METER),
385                 new ContinuousDistDoubleScalar.Rel<Length, LengthUnit>(new DistUniform(this.stream, 1.6, 2.0), METER),
386                 new ContinuousDistDoubleScalar.Rel<Speed, SpeedUnit>(new DistUniform(this.stream, 140, 180), KM_PER_HOUR),
387                 initialPositions, this.strategicalPlannerFactoryCars);
388         // System.out.println("Constructed template " + template);
389         distribution.add(new FrequencyAndObject<>(this.carProbability, template));
390         template = makeTemplate(this.stream, lane,
391                 new ContinuousDistDoubleScalar.Rel<Length, LengthUnit>(new DistUniform(this.stream, 8, 14), METER),
392                 new ContinuousDistDoubleScalar.Rel<Length, LengthUnit>(new DistUniform(this.stream, 2.0, 2.5), METER),
393                 new ContinuousDistDoubleScalar.Rel<Speed, SpeedUnit>(new DistUniform(this.stream, 100, 140), KM_PER_HOUR),
394                 initialPositions, this.strategicalPlannerFactoryTrucks);
395         // System.out.println("Constructed template " + template);
396         distribution.add(new FrequencyAndObject<>(1.0 - this.carProbability, template));
397         LaneBasedTemplateGTUTypeDistribution templateDistribution = new LaneBasedTemplateGTUTypeDistribution(distribution);
398         LaneBasedGTUGenerator.RoomChecker roomChecker = new CFRoomChecker();
399         return new LaneBasedGTUGenerator(lane.getId(), new Generator<Duration>()
400         {
401             @SuppressWarnings("synthetic-access")
402             @Override
403             public Duration draw()
404             {
405                 return new Duration(NetworksModel.this.headwayGenerator.draw(), DurationUnit.SI);
406             }
407         }, templateDistribution, GeneratorPositions.create(initialPositions, this.stream), this.network, this.simulator,
408                 roomChecker, this.idGenerator);
409     }
410 
411     /**
412      * @param randStream StreamInterface; the random stream to use
413      * @param lane Lane; reference lane to generate GTUs on
414      * @param lengthDistribution ContinuousDistDoubleScalar.Rel&lt;Length,LengthUnit&gt;; distribution of the GTU length
415      * @param widthDistribution ContinuousDistDoubleScalar.Rel&lt;Length,LengthUnit&gt;; distribution of the GTU width
416      * @param maximumSpeedDistribution ContinuousDistDoubleScalar.Rel&lt;Speed,SpeedUnit&gt;; distribution of the GTU's maximum
417      *            speed
418      * @param initialPositions Set&lt;DirectedLanePosition&gt;; initial position(s) of the GTU on the Lane(s)
419      * @param strategicalPlannerFactory LaneBasedStrategicalPlannerFactory&lt;LaneBasedStrategicalPlanner&gt;; factory to
420      *            generate the strategical planner for the GTU
421      * @return template for a GTU
422      * @throws GTUException when characteristics cannot be initialized
423      */
424     LaneBasedTemplateGTUType makeTemplate(final StreamInterface randStream, final Lane lane,
425             final ContinuousDistDoubleScalar.Rel<Length, LengthUnit> lengthDistribution,
426             final ContinuousDistDoubleScalar.Rel<Length, LengthUnit> widthDistribution,
427             final ContinuousDistDoubleScalar.Rel<Speed, SpeedUnit> maximumSpeedDistribution,
428             final Set<DirectedLanePosition> initialPositions,
429             final LaneBasedStrategicalPlannerFactory<LaneBasedStrategicalPlanner> strategicalPlannerFactory) throws GTUException
430     {
431         return new LaneBasedTemplateGTUType(this.network.getGtuType(GTUType.DEFAULTS.CAR), new Generator<Length>()
432         {
433             @Override
434             public Length draw()
435             {
436                 return lengthDistribution.draw();
437             }
438         }, new Generator<Length>()
439         {
440             @Override
441             public Length draw()
442             {
443                 return widthDistribution.draw();
444             }
445         }, new Generator<Speed>()
446         {
447             @Override
448             public Speed draw()
449             {
450                 return maximumSpeedDistribution.draw();
451             }
452         }, strategicalPlannerFactory,
453                 lane.getParentLink().getStartNode().getId().equals("From") ? this.routeGeneratorMain : this.routeGeneratorRamp);
454 
455     }
456 
457     /**
458      * Append a sink to each lane of an array of Lanes.
459      * @param lanes Lane[]; the array of lanes
460      * @param laneType LaneType; the LaneType for cars
461      * @return Lane[]; the lanes
462      * @throws NetworkException on network inconsistency
463      * @throws OTSGeometryException on problem making the path for a link
464      */
465     private Lane[] setupSink(final Lane[] lanes, final LaneType laneType) throws NetworkException, OTSGeometryException
466     {
467         CrossSectionLink link = lanes[0].getParentLink();
468         OTSRoadNode to = (OTSRoadNode) link.getEndNode();
469         OTSRoadNode from = (OTSRoadNode) link.getStartNode();
470         double endLinkLength = 50; // [m]
471         double endX = to.getPoint().x + (endLinkLength / link.getLength().getSI()) * (to.getPoint().x - from.getPoint().x);
472         double endY = to.getPoint().y + (endLinkLength / link.getLength().getSI()) * (to.getPoint().y - from.getPoint().y);
473         OTSRoadNode end = new OTSRoadNode(this.network, link.getId() + "END", new OTSPoint3D(endX, endY, to.getPoint().z), 
474                 Direction.instantiateSI(Math.atan2(to.getPoint().y - from.getPoint().y, to.getPoint().x - from.getPoint().x)));
475         CrossSectionLink endLink = LaneFactory.makeLink(this.network, link.getId() + "endLink", to, end, null, this.simulator);
476         for (Lane lane : lanes)
477         {
478             // Overtaking left and right allowed on the sinkLane
479             Lane sinkLane = new Lane(endLink, lane.getId() + "." + "sinkLane", lane.getLateralCenterPosition(1.0),
480                     lane.getLateralCenterPosition(1.0), lane.getWidth(1.0), lane.getWidth(1.0), laneType, this.speedLimit);
481             new SinkSensor(sinkLane, new Length(10.0, METER), Compatible.EVERYTHING, this.simulator);
482         }
483         return lanes;
484     }
485 
486     /** The set of GTUs that we want to sample regularly. */
487     private Set<GTU> knownGTUs = new LinkedHashSet<>();
488 
489     /** {@inheritDoc} */
490     @Override
491     public void notify(final EventInterface event) throws RemoteException
492     {
493         EventTypeInterface eventType = event.getType();
494         if (Network.GTU_ADD_EVENT.equals(eventType))
495         {
496             System.out.println("A GTU was created (id " + (String) event.getContent() + ")");
497             this.knownGTUs.add(this.network.getGTU((String) event.getContent()));
498         }
499         else if (Network.GTU_REMOVE_EVENT.equals(eventType))
500         {
501             System.out.println("A GTU was removed (id " + ((String) event.getContent()) + ")");
502             this.knownGTUs.remove(this.network.getGTU((String) event.getContent()));
503         }
504     }
505 
506     /** {@inheritDoc} */
507     @Override
508     public OTSRoadNetwork getNetwork()
509     {
510         return this.network;
511     }
512 
513     /**
514      * @param index int; the rank number of the path
515      * @return List&lt;Lane&gt;; the set of lanes for the specified index
516      */
517     public final List<Lane> getPath(final int index)
518     {
519         return this.paths.get(index);
520     }
521 
522     /**
523      * Return the number of paths that can be used to show graphs.
524      * @return int; the number of paths that can be used to show graphs
525      */
526     public final int pathCount()
527     {
528         return this.paths.size();
529     }
530 
531     /**
532      * @return minimumDistance
533      */
534     public final Length getMinimumDistance()
535     {
536         return this.minimumDistance;
537     }
538 
539     /**
540      * @return maximumDistance
541      */
542     public final Length getMaximumDistance()
543     {
544         return this.maximumDistance;
545     }
546 
547     /** {@inheritDoc} */
548     @Override
549     public Serializable getSourceId()
550     {
551         return "NetworksModel";
552     }
553 
554 }