View Javadoc
1   package org.opentrafficsim.road.gtu.generator;
2   
3   import java.util.Collections;
4   import java.util.Comparator;
5   import java.util.Iterator;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Map.Entry;
11  import java.util.NoSuchElementException;
12  import java.util.Set;
13  import java.util.SortedSet;
14  import java.util.function.Function;
15  import java.util.function.Supplier;
16  
17  import org.djunits.unit.DurationUnit;
18  import org.djunits.unit.LengthUnit;
19  import org.djunits.value.vdouble.scalar.Acceleration;
20  import org.djunits.value.vdouble.scalar.Duration;
21  import org.djunits.value.vdouble.scalar.Length;
22  import org.djunits.value.vdouble.scalar.Speed;
23  import org.djutils.data.Row;
24  import org.djutils.data.Table;
25  import org.djutils.exceptions.Throw;
26  import org.djutils.immutablecollections.ImmutableLinkedHashMap;
27  import org.djutils.immutablecollections.ImmutableMap;
28  import org.djutils.multikeymap.MultiKeyMap;
29  import org.opentrafficsim.base.parameters.ParameterException;
30  import org.opentrafficsim.core.distributions.Generator;
31  import org.opentrafficsim.core.distributions.ProbabilityException;
32  import org.opentrafficsim.core.gtu.GtuCharacteristics;
33  import org.opentrafficsim.core.gtu.GtuException;
34  import org.opentrafficsim.core.gtu.GtuType;
35  import org.opentrafficsim.core.network.Link;
36  import org.opentrafficsim.core.network.NetworkException;
37  import org.opentrafficsim.core.network.Node;
38  import org.opentrafficsim.core.network.Network;
39  import org.opentrafficsim.core.network.route.Route;
40  import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.Placement;
41  import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.RoomChecker;
42  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
43  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGenerator;
44  import org.opentrafficsim.road.gtu.lane.VehicleModel;
45  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
46  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
47  import org.opentrafficsim.road.network.lane.CrossSectionLink;
48  import org.opentrafficsim.road.network.lane.Lane;
49  import org.opentrafficsim.road.network.lane.LanePosition;
50  import org.pmw.tinylog.Logger;
51  
52  import nl.tudelft.simulation.jstats.streams.StreamInterface;
53  
54  /**
55   * Injections can be used to have a large degree of control over GTU generation. Depending on the information provided in an
56   * injections table, this class may be used in conjunction with {@code LaneBasedGtuGenerator} as a:
57   * <ol>
58   * <li>{@code Generator<Duration>} for inter-arrival times</li>
59   * <li>{@code LaneBasedGtuCharacteristicsGenerator} through {@code asLaneBasedGtuCharacteristicsGenerator}</li>
60   * <li>{@code GeneratorPositions}</li>
61   * <li>{@code RoomChecker}</li>
62   * <li>{@code Supplier<String>} for GTU ids</li>
63   * </ol>
64   * It is assumed that for each next GTU, first an inter-arrival time is requested. Functions 2 and 3 will not check order and
65   * simply return information from the current row in the injections table. Function 4 and 5 are tracked independently and
66   * asynchronous with the rest, as these occur at later times when GTUs are (attempted to be) placed.
67   * <p>
68   * Copyright (c) 2022-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
69   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
70   * </p>
71   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
72   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
73   * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
74   */
75  public class Injections implements Generator<Duration>, Supplier<String>, GeneratorPositions, RoomChecker
76  {
77  
78      /** Time column id. */
79      public static final String TIME_COLUMN = "time";
80  
81      /** Id column id. */
82      public static final String ID_COLUMN = "id";
83  
84      /** GTU type column id. */
85      public static final String GTU_TYPE_COLUMN = "gtuType";
86  
87      /** Position (on lane) column id. */
88      public static final String POSITION_COLUMN = "position";
89  
90      /** Lane column id. */
91      public static final String LANE_COLUMN = "lane";
92  
93      /** Link column id. */
94      public static final String LINK_COLUMN = "link";
95  
96      /** Speed column id. */
97      public static final String SPEED_COLUMN = "speed";
98  
99      /** Origin column id. */
100     public static final String ORIGIN_COLUMN = "origin";
101 
102     /** Destination column id. */
103     public static final String DESTINATION_COLUMN = "destination";
104 
105     /** Route column id. */
106     public static final String ROUTE_COLUMN = "route";
107 
108     /** Length column id. */
109     public static final String LENGTH_COLUMN = "length";
110 
111     /** Width column id. */
112     public static final String WIDTH_COLUMN = "width";
113 
114     /** Maximum speed column id. */
115     public static final String MAX_SPEED_COLUMN = "maxSpeed";
116 
117     /** Maximum acceleration column id. */
118     public static final String MAX_ACCELERATION_COLUMN = "maxAcceleration";
119 
120     /** Maximum deceleration column id. */
121     public static final String MAX_DECELERATION_COLUMN = "maxDeceleration";
122 
123     /** Front column id. */
124     public static final String FRONT_COLUMN = "front";
125 
126     /** Network. */
127     private final Network network;
128 
129     /** GTU types per their id. */
130     private final ImmutableMap<String, GtuType> gtuTypes;
131 
132     /** Strategical planner factory. */
133     private final LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory;
134 
135     /** Critical time-to-collision for GTU placement. */
136     private final Duration timeToCollision;
137 
138     /** Random number stream. */
139     private final StreamInterface stream;
140 
141     /** Stored column numbers for present columns in injection table. */
142     private final Map<String, Integer> columnNumbers = new LinkedHashMap<>();
143 
144     /** Separate iterator to obtain the id, as this is requested asynchronously with the other characteristics. */
145     private final Iterator<Row> idIterator;
146 
147     /** Separate iterator to obtain the speed, as this is requested asynchronously with the other characteristics. */
148     private final Iterator<Row> speedIterator;
149 
150     /** Next speed to generate next GTU with. */
151     private Speed nextSpeed;
152 
153     /** Iterator over all injections. */
154     private final Iterator<Row> characteristicsIterator;
155 
156     /** Current row with characteristics from injection. */
157     private Row characteristicsRow;
158 
159     /** Previous arrival time to calculate inter-arrival time. */
160     private Duration previousArrival = Duration.ZERO;
161 
162     /** Positions per link, lane and position (on lane). */
163     private MultiKeyMap<GeneratorLanePosition> lanePositions;
164 
165     /** All lane positions, returned as {@code GeneratorPositions}. */
166     private Set<GeneratorLanePosition> allLanePositions;
167 
168     /** Cached characteristics generator, to always return the same. */
169     private LaneBasedGtuCharacteristicsGenerator characteristicsGenerator;
170 
171     /** Boolean to check inter-arrival time and characteristics drawing consistency. */
172     private boolean readyForCharacteristicsDraw = false;
173 
174     /**
175      * Constructor. Depending on what information is provided in the injections table, some arguments may or should not be
176      * {@code null}. In particular:
177      * <ul>
178      * <li>"time": always required, allows the {@code Injections} to be used as a {@code Generator<Duration>}.</li>
179      * <li>"id": allows the {@code Injections} to be used as a {@code Supplier<String>} for GTU ids.</li>
180      * <li>"position", "lane", "link": allows the {@code Injections} to be used as a {@code GeneratorPositions}, requires
181      * <b>network</b>.</li>
182      * <li>"speed": allows the {@code Injections} to be used as a {@code RoomChecker}, requires <b>timeToCollision</b>.</li>
183      * <li><i>all other columns</i>: allows the {@code Injections} to be used as a {@code LaneBasedGtuCharacteristicsGenerator}
184      * through {@code asLaneBasedGtuCharacteristicsGenerator()}, requires <b>gtuTypes</b>, <b>network</b>,
185      * <b>strategicalPlannerFactory</b> and <b>stream</b>.
186      * </ul>
187      * Time should be in increasing order. If length is provided, but no front, front will be 75% of the length.
188      * @param table Table; table with at least a "time" column.
189      * @param network Network; network, may be {@code null}.
190      * @param gtuTypes ImmutableMap&lt;String, GtuType&gt;; GTU types, as obtained from {@code Definitions}, may be
191      *            {@code null}.
192      * @param strategicalPlannerFactory LaneBasedStrategicalPlannerFactory&lt;?&gt;; strategical planner factory, may be
193      *            {@code null}.
194      * @param stream StreamInterface; random number stream, may be {@code null}.
195      * @param timeToCollision Duration; critical time-to-collision to allow GTU generation, may be {@code null}.
196      * @throws IllegalArgumentException when the right arguments are not provided for the columns in the injection table.
197      */
198     public Injections(final Table table, final Network network, final ImmutableMap<String, GtuType> gtuTypes,
199             final LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory, final StreamInterface stream,
200             final Duration timeToCollision) throws IllegalArgumentException
201     {
202         Throw.whenNull(table, "Table may not be null.");
203         this.idIterator = table.iterator();
204         this.speedIterator = table.iterator();
205         this.characteristicsIterator = table.iterator();
206         this.network = network;
207         this.gtuTypes = gtuTypes == null ? new ImmutableLinkedHashMap<>(Collections.emptyMap()) : gtuTypes;
208         this.strategicalPlannerFactory = strategicalPlannerFactory;
209         this.timeToCollision = timeToCollision;
210         this.stream = stream;
211 
212         table.getColumns().forEach((c) -> this.columnNumbers.put(c.getId(), table.getColumnNumber(c)));
213         boolean needStrategicalPlannerFactory = checkColumnTypesNeedStrategicalPlannerFactory(table);
214         Throw.when(needStrategicalPlannerFactory && (gtuTypes == null || gtuTypes.isEmpty()), IllegalArgumentException.class,
215                 "Injection table contains columns that require GTU types.");
216         Throw.when(needStrategicalPlannerFactory && strategicalPlannerFactory == null, IllegalArgumentException.class,
217                 "Injection table contains columns that require a strategical planner factory.");
218         Throw.when(needStrategicalPlannerFactory && network == null, IllegalArgumentException.class,
219                 "Injection table contains columns that require a network.");
220         Throw.when(needStrategicalPlannerFactory && stream == null, IllegalArgumentException.class,
221                 "Injection table contains columns that require a stream of random numbers.");
222         Throw.when(!this.columnNumbers.containsKey(TIME_COLUMN), IllegalArgumentException.class,
223                 "Injection table contains no time column.");
224 
225         createLanePositions(table);
226     }
227 
228     /**
229      * Checks whether all columns have the right value type.
230      * @param table Table; injection table.
231      * @return boolean; whether columns are present that require a strategical planner factory in order to be processed.
232      */
233     private boolean checkColumnTypesNeedStrategicalPlannerFactory(final Table table)
234     {
235         boolean needStrategicalPlannerFactory = false;
236         for (Entry<String, Integer> entry : this.columnNumbers.entrySet())
237         {
238             Class<?> needClass;
239             switch (entry.getKey())
240             {
241                 case TIME_COLUMN:
242                     needClass = Duration.class;
243                     break;
244                 case ID_COLUMN:
245                 case LANE_COLUMN:
246                 case LINK_COLUMN:
247                     needClass = String.class;
248                     break;
249                 case GTU_TYPE_COLUMN:
250                 case ORIGIN_COLUMN:
251                 case DESTINATION_COLUMN:
252                 case ROUTE_COLUMN:
253                     needClass = String.class;
254                     needStrategicalPlannerFactory = true;
255                     break;
256                 case SPEED_COLUMN:
257                     needClass = Speed.class;
258                     break;
259                 case MAX_SPEED_COLUMN:
260                     needClass = Speed.class;
261                     needStrategicalPlannerFactory = true;
262                     break;
263                 case POSITION_COLUMN:
264                     needClass = Length.class;
265                     break;
266                 case LENGTH_COLUMN:
267                 case WIDTH_COLUMN:
268                 case FRONT_COLUMN:
269                     needClass = Length.class;
270                     needStrategicalPlannerFactory = true;
271                     break;
272                 case MAX_ACCELERATION_COLUMN:
273                 case MAX_DECELERATION_COLUMN:
274                     needClass = Acceleration.class;
275                     needStrategicalPlannerFactory = true;
276                     break;
277                 default:
278                     Logger.info("Column " + entry.getKey() + " for GTU injection not supported. It is ignored.");
279                     needClass = null;
280             }
281             if (needClass != null)
282             {
283                 Class<?> columnValueClass = table.getColumn(entry.getValue()).getValueType();
284                 Throw.when(!needClass.isAssignableFrom(columnValueClass), IllegalArgumentException.class,
285                         "Column %s has value type %s, but type %s is required.", entry.getKey(), columnValueClass, needClass);
286             }
287         }
288         return needStrategicalPlannerFactory;
289     }
290 
291     /**
292      * Creates all the lane positions for GTU generation.
293      * @param table Table; injection table.
294      */
295     private void createLanePositions(final Table table)
296     {
297         if (this.columnNumbers.containsKey(POSITION_COLUMN) && this.columnNumbers.containsKey(LANE_COLUMN)
298                 && this.columnNumbers.containsKey(LINK_COLUMN))
299         {
300             this.lanePositions = new MultiKeyMap<>(String.class, String.class, Length.class);
301             this.allLanePositions = new LinkedHashSet<>();
302             for (Row row : table)
303             {
304                 String linkId = (String) row.getValue(this.columnNumbers.get(LINK_COLUMN));
305                 Link link = this.network.getLink(linkId);
306                 Throw.when(!(link instanceof CrossSectionLink), IllegalArgumentException.class,
307                         "Injection table contains link that is not a CrossSectionLink.");
308 
309                 String laneId = (String) row.getValue(this.columnNumbers.get(LANE_COLUMN));
310                 // get and sort lanes to get the lane number (1 = right-most lane)
311                 List<Lane> lanes = ((CrossSectionLink) link).getLanes();
312                 Collections.sort(lanes, new Comparator<Lane>()
313                 {
314                     /** {@inheritDoc} */
315                     @Override
316                     public int compare(final Lane o1, final Lane o2)
317                     {
318                         return o1.getDesignLineOffsetAtBegin().compareTo(o2.getDesignLineOffsetAtBegin());
319                     }
320                 });
321                 int laneNumber = 0;
322                 for (int i = 0; i < lanes.size(); i++)
323                 {
324                     if (lanes.get(i).getId().equals(laneId))
325                     {
326                         laneNumber = i + 1;
327                         break;
328                     }
329                 }
330                 Throw.when(laneNumber == 0, IllegalArgumentException.class,
331                         "Injection table contains lane %s on link %s, but the link has no such lane.", laneId, linkId);
332 
333                 Length position = (Length) row.getValue(this.columnNumbers.get(POSITION_COLUMN));
334                 Throw.when(position.lt0() || position.gt(lanes.get(laneNumber - 1).getLength()), IllegalArgumentException.class,
335                         "Injection table contains position %s on lane %s on link %s, but the position is negative or "
336                                 + "beyond the length of the lane.",
337                         position, laneId, linkId);
338 
339                 GeneratorLanePosition generatorLanePosition = new GeneratorLanePosition(laneNumber,
340                         Set.of(new LanePosition(lanes.get(laneNumber - 1), position)), (CrossSectionLink) link);
341                 if (this.allLanePositions.add(generatorLanePosition))
342                 {
343                     this.lanePositions.put(generatorLanePosition, linkId, laneId, position);
344                 }
345             }
346         }
347         else if (this.columnNumbers.containsKey(POSITION_COLUMN) || this.columnNumbers.containsKey(LANE_COLUMN)
348                 || this.columnNumbers.containsKey(LINK_COLUMN))
349         {
350             // only partial information is provided (if none, we assume the user intends to use an external fixed position)
351             // as the user may still use another source for generator positions, we do not throw an exception
352             Logger.info("For injections to be used as GeneratorPositions, define a link, lane and position (on lane) column.");
353         }
354     }
355 
356     /** {@inheritDoc} */
357     @Override
358     public String get()
359     {
360         // This method implements Supplier<String> as an id generator.
361         Throw.when(!this.idIterator.hasNext(), NoSuchElementException.class, "No more ids to draw.");
362         Throw.when(!this.columnNumbers.containsKey(ID_COLUMN), IllegalStateException.class,
363                 "Using Injections as id generator, but the injection table has no id column.");
364         return (String) this.idIterator.next().getValue(this.columnNumbers.get(ID_COLUMN));
365     }
366 
367     /** {@inheritDoc} */
368     @Override
369     public synchronized Duration draw() throws ProbabilityException, ParameterException
370     {
371         if (!this.characteristicsIterator.hasNext())
372         {
373             return null; // stops LaneBasedGtuGenerator
374         }
375         this.characteristicsRow = this.characteristicsIterator.next();
376         this.readyForCharacteristicsDraw = true;
377         Duration t = (Duration) getCharacteristic(TIME_COLUMN);
378         Throw.when(t.lt(this.previousArrival), IllegalStateException.class, "Arrival times in injection not increasing.");
379         Duration interArrivalTime = t.minus(this.previousArrival);
380         this.previousArrival = t;
381         return interArrivalTime;
382     }
383 
384     /**
385      * Returns a characteristics generator view of the injections, as used by {@code LaneBasedGtuGenerator}. This requires at
386      * the least that a GTU type column, a strategical planner factory, a network, and a stream of random numbers are provided.
387      * @return LaneBasedGtuCharacteristicsGenerator; characteristics generator view of the injections.
388      */
389     public LaneBasedGtuCharacteristicsGenerator asLaneBasedGtuCharacteristicsGenerator()
390     {
391         if (this.characteristicsGenerator == null)
392         {
393             Throw.when(!this.columnNumbers.containsKey(GTU_TYPE_COLUMN), IllegalStateException.class,
394                     "A GTU type column is required for generation of characteristics.");
395 
396             this.characteristicsGenerator = new LaneBasedGtuCharacteristicsGenerator()
397             {
398                 /** Default characteristics, generated as needed. */
399                 private GtuCharacteristics defaultCharacteristics;
400 
401                 /** {@inheritDoc} */
402                 @Override
403                 public LaneBasedGtuCharacteristics draw() throws ProbabilityException, ParameterException, GtuException
404                 {
405                     synchronized (Injections.this)
406                     {
407                         Throw.when(Injections.this.characteristicsRow == null, IllegalStateException.class,
408                                 "Must draw inter-arrival time before drawing GTU characteristics.");
409                         Throw.when(!Injections.this.readyForCharacteristicsDraw, IllegalStateException.class,
410                                 "Should not draw GTU characteristics again before inter-arrival time was drawn in between.");
411                         Injections.this.readyForCharacteristicsDraw = false;
412                         GtuType gtuType = Injections.this.gtuTypes.get((String) getCharacteristic(GTU_TYPE_COLUMN));
413 
414                         Length length = (Length) assureCharacteristic(LENGTH_COLUMN, gtuType, (g) -> g.getLength());
415                         Length width = (Length) assureCharacteristic(WIDTH_COLUMN, gtuType, (g) -> g.getWidth());
416                         Speed maxSpeed = (Speed) assureCharacteristic(MAX_SPEED_COLUMN, gtuType, (g) -> g.getMaximumSpeed());
417                         Acceleration maxAcceleration = (Acceleration) assureCharacteristic(MAX_ACCELERATION_COLUMN, gtuType,
418                                 (g) -> g.getMaximumAcceleration());
419                         Acceleration maxDeceleration = (Acceleration) assureCharacteristic(MAX_DECELERATION_COLUMN, gtuType,
420                                 (g) -> g.getMaximumDeceleration());
421                         this.defaultCharacteristics = null; // reset for next draw
422                         Length front = Injections.this.columnNumbers.containsKey(FRONT_COLUMN)
423                                 ? (Length) getCharacteristic(FRONT_COLUMN) : length.times(0.75);
424                         GtuCharacteristics characteristics = new GtuCharacteristics(gtuType, length, width, maxSpeed,
425                                 maxAcceleration, maxDeceleration, front);
426 
427                         Route route = Injections.this.columnNumbers.containsKey(ROUTE_COLUMN)
428                                 ? (Route) Injections.this.network.getRoute((String) getCharacteristic(ROUTE_COLUMN)) : null;
429                         Node origin = Injections.this.columnNumbers.containsKey(ORIGIN_COLUMN)
430                                 ? (Node) Injections.this.network.getNode((String) getCharacteristic(ORIGIN_COLUMN)) : null;
431                         Node destination = Injections.this.columnNumbers.containsKey(DESTINATION_COLUMN)
432                                 ? (Node) Injections.this.network.getNode((String) getCharacteristic(DESTINATION_COLUMN)) : null;
433                         return new LaneBasedGtuCharacteristics(characteristics, Injections.this.strategicalPlannerFactory,
434                                 route, origin, destination, VehicleModel.MINMAX);
435                     }
436                 }
437 
438                 /**
439                  * Tries to obtain a column value. If it is not provided, takes the value from generated default
440                  * characteristics.
441                  * @param column String; characteristic column name.
442                  * @param gtuType GtuType; GTU type of the GTU to be generated.
443                  * @param supplier Function&lt;GtuCharacteristics, ?&gt;; takes value from default characteristics.
444                  * @return Object; object value for the characteristic.
445                  * @throws GtuException; if there are no default characteristics for the GTU type, but these are required.
446                  */
447                 private Object assureCharacteristic(final String column, final GtuType gtuType,
448                         final Function<GtuCharacteristics, ?> supplier) throws GtuException
449                 {
450                     if (Injections.this.columnNumbers.containsKey(column))
451                     {
452                         return getCharacteristic(column);
453                     }
454                     if (this.defaultCharacteristics == null)
455                     {
456                         this.defaultCharacteristics =
457                                 GtuType.defaultCharacteristics(gtuType, Injections.this.network, Injections.this.stream);
458                     }
459                     return supplier.apply(this.defaultCharacteristics);
460                 }
461             };
462         }
463         return this.characteristicsGenerator;
464     }
465 
466     /** {@inheritDoc} */
467     @Override
468     public GeneratorLanePosition draw(final GtuType gtuType, final LaneBasedGtuCharacteristics characteristics,
469             final Map<CrossSectionLink, Map<Integer, Integer>> unplaced) throws GtuException
470     {
471         Throw.when(this.lanePositions == null, IllegalStateException.class,
472                 "Injection table without position, lane and link column cannot be used to draw generator positions.");
473         String link = (String) getCharacteristic(LINK_COLUMN);
474         String lane = (String) getCharacteristic(LANE_COLUMN);
475         Length position = (Length) getCharacteristic(POSITION_COLUMN);
476         return this.lanePositions.get(link, lane, position);
477     }
478 
479     /** {@inheritDoc} */
480     @Override
481     public Set<GeneratorLanePosition> getAllPositions()
482     {
483         Throw.when(this.lanePositions == null, IllegalStateException.class,
484                 "Injection table without position, lane and link column cannot be used to draw generator positions.");
485         return this.allLanePositions;
486     }
487 
488     /**
489      * Returns placement for injected GTUs, as used by {@code LaneBasedGtuGenerator}. This needs speed to be provided in the
490      * injections, and a minimum time-to-collision value. Besides the time-to-collision value, the minimum headway for a
491      * successful placement is t*v + 3m, where t = 1s and v the generation speed.
492      * @param leaders SortedSet&lt;HeadwayGtu&gt;; leaders, usually 1, possibly more after a branch
493      * @param characteristics LaneBasedGtuCharacteristics; characteristics of the proposed new GTU
494      * @param since Duration; time since the GTU wanted to arrive
495      * @param initialPosition Set&lt;LanePosition&gt;; initial position
496      * @return Speed; maximum safe speed, or null if a GTU with the specified characteristics cannot be placed at the current
497      *         time
498      * @throws NetworkException this method may throw a NetworkException if it encounters an error in the network structure
499      * @throws GtuException on parameter exception
500      */
501     @Override
502     public Placement canPlace(final SortedSet<HeadwayGtu> leaders, final LaneBasedGtuCharacteristics characteristics,
503             final Duration since, final Set<LanePosition> initialPosition) throws NetworkException, GtuException
504     {
505         Throw.when(!this.columnNumbers.containsKey(SPEED_COLUMN), IllegalStateException.class,
506                 "Injection table without speed cannot be used to determine a GTU placement.");
507         Throw.when(this.timeToCollision == null, IllegalStateException.class,
508                 "Injections used to place GTUs, but no acceptable time-to-collision is provided.");
509         if (this.nextSpeed == null)
510         {
511             Throw.when(!this.speedIterator.hasNext(), NoSuchElementException.class, "No more speed to draw.");
512             this.nextSpeed = (Speed) this.speedIterator.next().getValue(this.columnNumbers.get(SPEED_COLUMN));
513         }
514         if (leaders.isEmpty())
515         {
516             // no leaders: free
517             Placement placement = new Placement(this.nextSpeed, initialPosition);
518             this.nextSpeed = null;
519             return placement;
520         }
521         HeadwayGtu leader = leaders.first();
522         if ((this.nextSpeed.le(leader.getSpeed())
523                 || leader.getDistance().divide(this.nextSpeed.minus(leader.getSpeed())).gt(this.timeToCollision))
524                 && leader.getDistance()
525                         .gt(this.nextSpeed.times(new Duration(1.0, DurationUnit.SI)).plus(new Length(3.0, LengthUnit.SI))))
526         {
527             Placement placement = new Placement(this.nextSpeed, initialPosition);
528             this.nextSpeed = null;
529             return placement;
530         }
531         return Placement.NO;
532     }
533 
534     /**
535      * Shorthand to retrieve a column value from the current characteristics row.
536      * @param column String; characteristic column name.
537      * @return Object; object value for the characteristic.
538      */
539     private Object getCharacteristic(final String column)
540     {
541         return this.characteristicsRow.getValue(this.columnNumbers.get(column));
542     }
543 
544 }