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