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