View Javadoc
1   package org.opentrafficsim.road.gtu.generator;
2   
3   import java.io.Serializable;
4   import java.rmi.RemoteException;
5   import java.util.Collections;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.LinkedList;
9   import java.util.Map;
10  import java.util.Queue;
11  import java.util.Set;
12  import java.util.SortedSet;
13  import java.util.TreeSet;
14  import java.util.UUID;
15  import java.util.function.Supplier;
16  
17  import javax.naming.NamingException;
18  
19  import org.djunits.unit.DurationUnit;
20  import org.djunits.value.vdouble.scalar.Duration;
21  import org.djunits.value.vdouble.scalar.Length;
22  import org.djunits.value.vdouble.scalar.Speed;
23  import org.djunits.value.vdouble.scalar.Time;
24  import org.djutils.draw.bounds.Bounds;
25  import org.djutils.draw.bounds.Bounds2d;
26  import org.djutils.draw.point.Point;
27  import org.djutils.event.EventType;
28  import org.djutils.event.LocalEventProducer;
29  import org.djutils.exceptions.Throw;
30  import org.djutils.metadata.MetaData;
31  import org.djutils.metadata.ObjectDescriptor;
32  import org.opentrafficsim.base.Identifiable;
33  import org.opentrafficsim.base.TimeStampedObject;
34  import org.opentrafficsim.base.parameters.ParameterException;
35  import org.opentrafficsim.core.distributions.Generator;
36  import org.opentrafficsim.core.distributions.ProbabilityException;
37  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
38  import org.opentrafficsim.core.geometry.DirectedPoint;
39  import org.opentrafficsim.core.geometry.OtsGeometryException;
40  import org.opentrafficsim.core.gtu.GtuErrorHandler;
41  import org.opentrafficsim.core.gtu.GtuException;
42  import org.opentrafficsim.core.gtu.GtuGenerator;
43  import org.opentrafficsim.core.gtu.GtuType;
44  import org.opentrafficsim.core.gtu.RelativePosition;
45  import org.opentrafficsim.core.network.NetworkException;
46  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.GeneratorLanePosition;
47  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
48  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGenerator;
49  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
50  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
51  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtuReal;
52  import org.opentrafficsim.road.network.RoadNetwork;
53  import org.opentrafficsim.road.network.lane.CrossSectionLink;
54  import org.opentrafficsim.road.network.lane.Lane;
55  import org.opentrafficsim.road.network.lane.LanePosition;
56  
57  import nl.tudelft.simulation.dsol.SimRuntimeException;
58  
59  /**
60   * Lane based GTU generator. This generator generates lane based GTUs using a LaneBasedTemplateGTUType. The template is used to
61   * generate a set of GTU characteristics at the times implied by the headway generator. These sets are queued until there is
62   * sufficient room to construct a GTU at the specified lane locations. The speed of a construction GTU may be reduced to ensure
63   * it does not run into its immediate leader GTU.
64   * <p>
65   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
66   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
67   * </p>
68   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
69   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
70   * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
71   */
72  public class LaneBasedGtuGenerator extends LocalEventProducer implements Serializable, Identifiable, GtuGenerator
73  {
74      /** */
75      private static final long serialVersionUID = 20160000L;
76  
77      /**
78       * Event of a generated GTU. Payload: LaneBasedGtu
79       */
80      public static final EventType GTU_GENERATED_EVENT = new EventType("GENERATOR.GTU_GENERATED", new MetaData("GTU generated",
81              "GTU was generated", new ObjectDescriptor("GTU", "The GTU itself", LaneBasedGtu.class)));
82  
83      /** FIFO for templates that have not been generated yet due to insufficient room/headway, per position, and per link. */
84      private final Map<CrossSectionLink,
85              Map<GeneratorLanePosition, Queue<TimeStampedObject<LaneBasedGtuCharacteristics>>>> unplacedTemplates =
86                      new LinkedHashMap<>();
87  
88      /** Name of the GTU generator. */
89      private final String id;
90  
91      /** Unique id in the network. */
92      private final String uniqueId;
93  
94      /** Time distribution that determines the interval times between GTUs. */
95      private final Generator<Duration> interarrivelTimeGenerator;
96  
97      /** Generates most properties of the GTUs. */
98      private final LaneBasedGtuCharacteristicsGenerator laneBasedGtuCharacteristicsGenerator;
99  
100     /** Total number of GTUs generated so far. */
101     private long generatedGTUs = 0;
102 
103     /** Retry interval for checking if a GTU can be placed. */
104     private Duration reTryInterval = new Duration(0.1, DurationUnit.SI);
105 
106     /** Location provider for all generated GTUs. */
107     private final GeneratorPositions generatorPositions;
108 
109     /** Network. */
110     private final RoadNetwork network;
111 
112     /** Simulator. */
113     private final OtsSimulatorInterface simulator;
114 
115     /** The way that this generator checks if it is safe to construct and place the next lane based GTU. */
116     private final RoomChecker roomChecker;
117 
118     /** ID generator. */
119     private final Supplier<String> idGenerator;
120 
121     /** Initial distance over which lane changes shouldn't be performed. */
122     private Length noLaneChangeDistance = null;
123 
124     /** Whether GTUs change lane instantaneously. */
125     private boolean instantaneousLaneChange = false;
126 
127     /** GTU error handler. */
128     private GtuErrorHandler errorHandler = GtuErrorHandler.THROW;
129 
130     /** Vehicle generation is ignored on these lanes. */
131     private Set<Lane> disabled = new LinkedHashSet<>();
132 
133     /**
134      * Construct a new lane base GTU generator.
135      * @param id String; name of the new GTU generator
136      * @param interarrivelTimeGenerator Generator&lt;Duration&gt;; generator for the interval times between GTUs
137      * @param laneBasedGtuCharacteristicsGenerator LaneBasedGtuCharacteristicsGenerator; generator of the characteristics of
138      *            each GTU
139      * @param generatorPositions GeneratorPositions; location and initial direction provider for all generated GTUs
140      * @param network RoadNetwork; the OTS network that owns the generated GTUs
141      * @param simulator OtsSimulatorInterface; simulator
142      * @param roomChecker RoomChecker; the way that this generator checks that there is sufficient room to place a new GTU
143      * @param idGenerator Supplier&lt;String&gt;; id generator
144      * @throws SimRuntimeException when <cite>startTime</cite> lies before the current simulation time
145      * @throws ProbabilityException pe
146      * @throws ParameterException if drawing from the interarrival generator fails
147      * @throws NetworkException if the object could not be added to the network
148      */
149     @SuppressWarnings("parameternumber")
150     public LaneBasedGtuGenerator(final String id, final Generator<Duration> interarrivelTimeGenerator,
151             final LaneBasedGtuCharacteristicsGenerator laneBasedGtuCharacteristicsGenerator,
152             final GeneratorPositions generatorPositions, final RoadNetwork network, final OtsSimulatorInterface simulator,
153             final RoomChecker roomChecker, final Supplier<String> idGenerator)
154             throws SimRuntimeException, ProbabilityException, ParameterException, NetworkException
155     {
156         this.id = id;
157         this.uniqueId = UUID.randomUUID().toString() + "_" + id;
158         this.interarrivelTimeGenerator = interarrivelTimeGenerator;
159         this.laneBasedGtuCharacteristicsGenerator = laneBasedGtuCharacteristicsGenerator;
160         this.generatorPositions = generatorPositions;
161         this.network = network;
162         this.simulator = simulator;
163         this.roomChecker = roomChecker;
164         this.idGenerator = idGenerator;
165         Duration headway = this.interarrivelTimeGenerator.draw();
166         if (headway != null) // otherwise no demand at all
167         {
168             simulator.scheduleEventRel(headway, this, "generateCharacteristics", new Object[] {});
169         }
170         this.network.addNonLocatedObject(this);
171     }
172 
173     /**
174      * Sets the initial distance over which lane changes shouldn't be performed.
175      * @param noLaneChangeDistance Length; initial distance over which lane changes shouldn't be performed
176      */
177     public void setNoLaneChangeDistance(final Length noLaneChangeDistance)
178     {
179         this.noLaneChangeDistance = noLaneChangeDistance;
180     }
181 
182     /**
183      * Sets whether GTUs will change lane instantaneously.
184      * @param instantaneous boolean; whether GTUs will change lane instantaneously
185      */
186     public void setInstantaneousLaneChange(final boolean instantaneous)
187     {
188         this.instantaneousLaneChange = instantaneous;
189     }
190 
191     /**
192      * Sets the GTU error handler.
193      * @param gtuErrorHandler GTUErrorHandler; GTU error handler
194      */
195     public void setErrorHandler(final GtuErrorHandler gtuErrorHandler)
196     {
197         this.errorHandler = gtuErrorHandler;
198     }
199 
200     /**
201      * Generate the characteristics of the next GTU.
202      * @throws ProbabilityException when something is wrongly defined in the LaneBasedTemplateGTUType
203      * @throws SimRuntimeException when this method fails to re-schedule itself or the call to the method that tries to place a
204      *             GTU on the road
205      * @throws ParameterException in case of a parameter problem
206      * @throws GtuException if strategical planner cannot generate a plan
207      */
208     @SuppressWarnings("unused")
209     private void generateCharacteristics() throws ProbabilityException, SimRuntimeException, ParameterException, GtuException
210     {
211         synchronized (this.unplacedTemplates)
212         {
213             LaneBasedGtuCharacteristics characteristics = this.laneBasedGtuCharacteristicsGenerator.draw();
214             GtuType gtuType = characteristics.getGtuType();
215             // gather information on number of unplaced templates per lane, and per link, for the drawing of a new position
216             Map<CrossSectionLink, Map<Integer, Integer>> unplaced = new LinkedHashMap<>();
217             for (CrossSectionLink link : this.unplacedTemplates.keySet())
218             {
219                 Map<Integer, Integer> linkMap = new LinkedHashMap<>();
220                 Map<GeneratorLanePosition, Queue<TimeStampedObject<LaneBasedGtuCharacteristics>>> linkTemplates =
221                         this.unplacedTemplates.get(link);
222                 for (GeneratorLanePosition lanePosition : linkTemplates.keySet())
223                 {
224                     linkMap.put(lanePosition.getLaneNumber(), linkTemplates.get(lanePosition).size());
225                 }
226                 unplaced.put(link, linkMap);
227             }
228             // position draw
229             GeneratorLanePosition lanePosition = this.generatorPositions.draw(gtuType, characteristics, unplaced);
230 
231             // skip if disabled at this lane-direction
232             Set<Lane> lanes = new LinkedHashSet<>();
233             for (LanePosition pos : lanePosition.getPosition())
234             {
235                 lanes.add(pos.getLane());
236             }
237             if (Collections.disjoint(this.disabled, lanes))
238             {
239                 queueGtu(lanePosition, characteristics);
240             }
241 
242         }
243         // @docs/02-model-structure/dsol.md#event-based-simulation
244         Duration headway = this.interarrivelTimeGenerator.draw();
245         if (headway != null)
246         {
247             this.simulator.scheduleEventRel(headway, this, "generateCharacteristics", new Object[] {});
248         }
249         // @end
250     }
251 
252     /**
253      * Check if the queue is non-empty and, if it is, try to place the GTUs in the queue on the road.
254      * @param position GeneratorLanePosition; position
255      * @throws SimRuntimeException should never happen
256      * @throws GtuException when something wrong in the definition of the GTU
257      * @throws OtsGeometryException when something is wrong in the definition of the GTU
258      * @throws NetworkException when something is wrong with the initial location of the GTU
259      * @throws NamingException ???
260      * @throws ProbabilityException pe
261      */
262     @SuppressWarnings("unused")
263     private void tryToPlaceGTU(final GeneratorLanePosition position) throws SimRuntimeException, GtuException, NamingException,
264             NetworkException, OtsGeometryException, ProbabilityException
265     {
266         TimeStampedObject<LaneBasedGtuCharacteristics> timedCharacteristics;
267         Queue<TimeStampedObject<LaneBasedGtuCharacteristics>> queue =
268                 this.unplacedTemplates.get(position.getLink()).get(position);
269 
270         synchronized (queue)
271         {
272             timedCharacteristics = queue.peek();
273         }
274         if (null == timedCharacteristics)
275         {
276             return; // Do not re-schedule this method
277         }
278 
279         LaneBasedGtuCharacteristics characteristics = timedCharacteristics.getObject();
280         SortedSet<HeadwayGtu> leaders = new TreeSet<>();
281         for (LanePosition dirPos : position.getPosition())
282         {
283             getFirstLeaders(dirPos.getLane(), dirPos.getPosition().neg().minus(characteristics.getFront()),
284                     dirPos.getPosition(), leaders);
285         }
286         Duration since = this.simulator.getSimulatorAbsTime().minus(timedCharacteristics.getTimestamp());
287         Placement placement = this.roomChecker.canPlace(leaders, characteristics, since, position.getPosition());
288         if (placement.canPlace())
289         {
290             // There is enough room; remove the template from the queue and construct the new GTU
291             synchronized (queue)
292             {
293                 queue.remove();
294             }
295             placeGtu(characteristics, placement.getPosition(), placement.getSpeed());
296             if (queue.size() > 0)
297             {
298                 this.simulator.scheduleEventNow(this, "tryToPlaceGTU", new Object[] {position});
299             }
300         }
301         // @docs/02-model-structure/dsol.md#event-based-simulation (without the 'else')
302         else if (queue.size() > 0)
303         {
304             this.simulator.scheduleEventRel(this.reTryInterval, this, "tryToPlaceGTU", new Object[] {position});
305         }
306         // @end
307     }
308 
309     /**
310      * Adds a GTU to the generation queue. This method ignores whether vehicle generation is enabled at the location. This
311      * allows an external party to govern (over some time) what vehicles are generated.
312      * @param characteristics LaneBasedGtuCharacteristics; characteristics of GTU to add to the queue
313      * @param position Set&lt;Lane&gt;; position to generate the GTU at
314      */
315     public final void queueGtu(final LaneBasedGtuCharacteristics characteristics, final Set<Lane> position)
316     {
317         // first find the correct GeneratorLanePosition
318         GeneratorLanePosition genPosition = null;
319         Set<Lane> genSet = new LinkedHashSet<>();
320         for (GeneratorLanePosition lanePosition : this.generatorPositions.getAllPositions())
321         {
322             for (LanePosition dirPos : lanePosition.getPosition())
323             {
324                 genSet.add(dirPos.getLane());
325             }
326             if (genSet.equals(position))
327             {
328                 genPosition = lanePosition;
329                 break;
330             }
331             genSet.clear();
332         }
333         Throw.when(genPosition == null, IllegalStateException.class, "Position %s is not part of the generation.", position);
334         try
335         {
336             queueGtu(genPosition, characteristics);
337         }
338         catch (SimRuntimeException exception)
339         {
340             throw new RuntimeException("Unexpected exception while scheduling tryToPlace event.", exception);
341         }
342     }
343 
344     /**
345      * Places the characteristics in the queue pertaining to the position, and schedules a call to {@code tryToPlace} now if the
346      * queue length is 1.
347      * @param lanePosition GeneratorLanePosition; position to generate the GTU at
348      * @param characteristics LaneBasedGtuCharacteristics; characteristics of GTU to add to the queue
349      * @throws SimRuntimeException when an event is scheduled in the past
350      */
351     private void queueGtu(final GeneratorLanePosition lanePosition, final LaneBasedGtuCharacteristics characteristics)
352             throws SimRuntimeException
353     {
354         if (!this.unplacedTemplates.containsKey(lanePosition.getLink()))
355         {
356             this.unplacedTemplates.put(lanePosition.getLink(), new LinkedHashMap<>());
357         }
358         Map<GeneratorLanePosition, Queue<TimeStampedObject<LaneBasedGtuCharacteristics>>> linkMap =
359                 this.unplacedTemplates.get(lanePosition.getLink());
360         if (!linkMap.containsKey(lanePosition))
361         {
362             linkMap.put(lanePosition, new LinkedList<>());
363         }
364         Queue<TimeStampedObject<LaneBasedGtuCharacteristics>> queue = linkMap.get(lanePosition);
365         queue.add(new TimeStampedObject<>(characteristics, this.simulator.getSimulatorAbsTime()));
366         // @docs/02-model-structure/dsol.md#event-based-simulation
367         if (queue.size() == 1)
368         {
369             this.simulator.scheduleEventNow(this, "tryToPlaceGTU", new Object[] {lanePosition});
370         }
371         // @end
372     }
373 
374     /**
375      * Places a GTU, regardless of whether it has room. The user of this method should verify this is the case.
376      * @param characteristics LaneBasedGtuCharacteristics; characteristics
377      * @param position Set&lt;LanePosition&gt;; position
378      * @param speed Speed; speed
379      * @throws NamingException on exception
380      * @throws GtuException on exception
381      * @throws NetworkException on exception
382      * @throws SimRuntimeException on exception
383      * @throws OtsGeometryException on exception
384      */
385     public final void placeGtu(final LaneBasedGtuCharacteristics characteristics, final Set<LanePosition> position,
386             final Speed speed) throws NamingException, GtuException, NetworkException, SimRuntimeException, OtsGeometryException
387     {
388         String gtuId = this.idGenerator.get();
389         LaneBasedGtu gtu = new LaneBasedGtu(gtuId, characteristics.getGtuType(), characteristics.getLength(),
390                 characteristics.getWidth(), characteristics.getMaximumSpeed(), characteristics.getFront(), this.network);
391         gtu.setMaximumAcceleration(characteristics.getMaximumAcceleration());
392         gtu.setMaximumDeceleration(characteristics.getMaximumDeceleration());
393         gtu.setVehicleModel(characteristics.getVehicleModel());
394         gtu.setNoLaneChangeDistance(this.noLaneChangeDistance);
395         gtu.setInstantaneousLaneChange(this.instantaneousLaneChange);
396         gtu.setErrorHandler(this.errorHandler);
397         gtu.init(characteristics.getStrategicalPlannerFactory().create(gtu, characteristics.getRoute(),
398                 characteristics.getOrigin(), characteristics.getDestination()), position, speed);
399         this.generatedGTUs++;
400         fireEvent(GTU_GENERATED_EVENT, gtu);
401     }
402 
403     /**
404      * Adds the first GTU on the lane to the set, or any number or leaders on downstream lane(s) if there is no GTU on the lane.
405      * @param lane Lane; lane to search on
406      * @param startDistance Length; distance from generator location (nose) to start of the lane
407      * @param beyond Length; location to search downstream of which is the generator position, or the start for downstream lanes
408      * @param set Set&lt;HeadwayGtu&gt;; set to add the GTU's to
409      * @throws GtuException if a GTU is incorrectly positioned on a lane
410      */
411     private void getFirstLeaders(final Lane lane, final Length startDistance, final Length beyond, final Set<HeadwayGtu> set)
412             throws GtuException
413     {
414         LaneBasedGtu next = lane.getGtuAhead(beyond, RelativePosition.FRONT, this.simulator.getSimulatorAbsTime());
415         if (next != null)
416         {
417             Length headway = startDistance.plus(next.position(lane, next.getRear()));
418             if (headway.si < 300)
419             {
420                 set.add(new HeadwayGtuReal(next, headway, true));
421             }
422             return;
423         }
424         Set<Lane> downstreamLanes = lane.nextLanes(null);
425         for (Lane downstreamLane : downstreamLanes)
426         {
427             Length startDistanceDownstream = startDistance.plus(lane.getLength());
428             if (startDistanceDownstream.si > 300)
429             {
430                 return;
431             }
432             Length beyondDownstream = Length.ZERO;
433             getFirstLeaders(downstreamLane, startDistanceDownstream, beyondDownstream, set);
434         }
435     }
436 
437     /** {@inheritDoc} */
438     @Override
439     public final String toString()
440     {
441         return "LaneBasedGtuGenerator " + this.id + " on " + this.generatorPositions.getAllPositions();
442     }
443 
444     /**
445      * @return generatedGTUs.
446      */
447     public final long getGeneratedGTUs()
448     {
449         return this.generatedGTUs;
450     }
451 
452     /**
453      * Retrieve the id of this LaneBasedGtuGenerator.
454      * @return String; the id of this LaneBasedGtuGenerator
455      */
456     @Override
457     public final String getId()
458     {
459         return this.id;
460     }
461 
462     /**
463      * Disable the vehicle generator during the specific time. Underlying processes such as drawing characteristics and headways
464      * are continued, but simply will not result in the queuing of the GTU.
465      * @param start Time; start time
466      * @param end Time; end time
467      * @param laneDirections Set&lt;Lane&gt;; lanes to disable generation on
468      * @throws SimRuntimeException if time is incorrect
469      */
470     public void disable(final Time start, final Time end, final Set<Lane> laneDirections) throws SimRuntimeException
471     {
472         Throw.when(end.lt(start), SimRuntimeException.class, "End time %s is before start time %s.", end, start);
473         this.simulator.scheduleEventAbsTime(start, this, "disable", new Object[] {laneDirections});
474         this.simulator.scheduleEventAbsTime(end, this, "enable", new Object[0]);
475     }
476 
477     /**
478      * Disables the generator.
479      * @param laneDirections Set&lt;Lane&gt;; lanes to disable generation on
480      */
481     @SuppressWarnings("unused")
482     private void disable(final Set<Lane> laneDirections)
483     {
484         Throw.when(this.disabled != null && !this.disabled.isEmpty(), IllegalStateException.class,
485                 "Disabling a generator that is already disabled is not allowed.");
486         this.disabled = laneDirections;
487     }
488 
489     /**
490      * Enables the generator.
491      */
492     @SuppressWarnings("unused")
493     private void enable()
494     {
495         this.disabled = new LinkedHashSet<>();
496     }
497 
498     /** {@inheritDoc} */
499     @Override
500     public String getFullId()
501     {
502         return this.uniqueId;
503     }
504 
505     /** {@inheritDoc} */
506     @Override
507     public Set<GtuGeneratorPosition> getPositions()
508     {
509         Set<GtuGeneratorPosition> set = new LinkedHashSet<>();
510         for (GeneratorLanePosition lanePosition : this.generatorPositions.getAllPositions())
511         {
512             DirectedPoint p = lanePosition.getPosition().iterator().next().getLocation();
513             set.add(new GtuGeneratorPosition()
514             {
515                 /** {@inheritDoc} */
516                 @Override
517                 public Point<?> getLocation() throws RemoteException
518                 {
519                     return p;
520                 }
521 
522                 /** {@inheritDoc} */
523                 @Override
524                 public Bounds<?, ?, ?> getBounds() throws RemoteException
525                 {
526                     return new Bounds2d(-2.0, 2.0, -2.0, 2.0);
527                 }
528 
529                 /** {@inheritDoc} */
530                 @Override
531                 public int getQueueCount()
532                 {
533                     return getQueueLength(lanePosition);
534                 }
535             });
536         }
537         return set;
538     }
539 
540     /**
541      * Returns the number of GTUs in queue at the position.
542      * @param position GeneratorLanePosition; position.
543      * @return int; number of GTUs in queue at the position.
544      */
545     private int getQueueLength(final GeneratorLanePosition position)
546     {
547         for (CrossSectionLink link : this.unplacedTemplates.keySet())
548         {
549             for (GeneratorLanePosition lanePosition : this.unplacedTemplates.get(link).keySet())
550             {
551                 if (lanePosition.equals(position))
552                 {
553                     return this.unplacedTemplates.get(link).get(lanePosition).size();
554                 }
555             }
556         }
557         return 0;
558     }
559 
560     /**
561      * Interface for class that checks that there is sufficient room for a proposed new GTU and returns the maximum safe speed
562      * and position for the proposed new GTU.
563      */
564     public interface RoomChecker
565     {
566         /**
567          * Return the maximum safe speed and position for a new GTU with the specified characteristics. Returns
568          * {@code Placement.NO} if there is no safe speed and position. This method might be called with an empty leader set
569          * such that the desired speed can be implemented.
570          * @param leaders SortedSet&lt;HeadwayGtu&gt;; leaders, usually 1, possibly more after a branch
571          * @param characteristics LaneBasedGtuCharacteristics; characteristics of the proposed new GTU
572          * @param since Duration; time since the GTU wanted to arrive
573          * @param initialPosition Set&lt;LanePosition&gt;; initial position
574          * @return Speed; maximum safe speed, or null if a GTU with the specified characteristics cannot be placed at the
575          *         current time
576          * @throws NetworkException this method may throw a NetworkException if it encounters an error in the network structure
577          * @throws GtuException on parameter exception
578          */
579         Placement canPlace(SortedSet<HeadwayGtu> leaders, LaneBasedGtuCharacteristics characteristics, Duration since,
580                 Set<LanePosition> initialPosition) throws NetworkException, GtuException;
581     }
582 
583     /**
584      * Placement contains the information that a {@code RoomChecker} returns.
585      * <p>
586      * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
587      * <br>
588      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
589      * </p>
590      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
591      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
592      * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
593      */
594     public static final class Placement
595     {
596 
597         /** Value if the GTU cannot be placed. */
598         public static final Placement NO = new Placement();
599 
600         /** Speed. */
601         private final Speed speed;
602 
603         /** Position. */
604         private final Set<LanePosition> position;
605 
606         /**
607          * Constructor for NO.
608          */
609         private Placement()
610         {
611             this.speed = null;
612             this.position = null;
613         }
614 
615         /**
616          * Constructor.
617          * @param speed Speed; speed
618          * @param position Set&lt;LanePosition&gt;; position
619          */
620         public Placement(final Speed speed, final Set<LanePosition> position)
621         {
622             Throw.whenNull(speed, "Speed may not be null. Use Placement.NO if the GTU cannot be placed.");
623             Throw.whenNull(position, "Position may not be null. Use Placement.NO if the GTU cannot be placed.");
624             this.speed = speed;
625             this.position = position;
626         }
627 
628         /**
629          * Returns whether the GTU can be placed.
630          * @return whether the GTU can be placed
631          */
632         public boolean canPlace()
633         {
634             return this.speed != null && this.position != null;
635         }
636 
637         /**
638          * Returns the speed.
639          * @return Speed; speed
640          */
641         public Speed getSpeed()
642         {
643             return this.speed;
644         }
645 
646         /**
647          * Returns the position.
648          * @return Set&lt;LanePosition&gt;; position
649          */
650         public Set<LanePosition> getPosition()
651         {
652             return this.position;
653         }
654 
655         /** {@inheritDoc} */
656         @Override
657         public String toString()
658         {
659             return "Placement [speed=" + this.speed + ", position=" + this.position + "]";
660         }
661 
662     }
663 
664 }