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