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