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