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