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