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