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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class Injections
84 {
85
86
87 public static final String TIME_COLUMN = "time";
88
89
90 public static final String ID_COLUMN = "id";
91
92
93 public static final String GTU_TYPE_COLUMN = "gtuType";
94
95
96 public static final String POSITION_COLUMN = "position";
97
98
99 public static final String LANE_COLUMN = "lane";
100
101
102 public static final String LINK_COLUMN = "link";
103
104
105 public static final String SPEED_COLUMN = "speed";
106
107
108 public static final String ORIGIN_COLUMN = "origin";
109
110
111 public static final String DESTINATION_COLUMN = "destination";
112
113
114 public static final String ROUTE_COLUMN = "route";
115
116
117 public static final String LENGTH_COLUMN = "length";
118
119
120 public static final String WIDTH_COLUMN = "width";
121
122
123 public static final String MAX_SPEED_COLUMN = "maxSpeed";
124
125
126 public static final String MAX_ACCELERATION_COLUMN = "maxAcceleration";
127
128
129 public static final String MAX_DECELERATION_COLUMN = "maxDeceleration";
130
131
132 public static final String FRONT_COLUMN = "front";
133
134
135 private final Network network;
136
137
138 private final ImmutableMap<String, GtuType> gtuTypes;
139
140
141 private final BiFunction<GtuType, StreamInterface, Optional<GtuTemplate>> gtuCharacteristicsGenerator;
142
143
144 private final LaneBasedStrategicalPlannerFactory<?> strategicalPlannerFactory;
145
146
147 private final Duration timeToCollision;
148
149
150 private final StreamInterface stream;
151
152
153 private final Map<String, Integer> columnNumbers = new LinkedHashMap<>();
154
155
156 private final Iterator<Row> idIterator;
157
158
159 private final Iterator<Row> speedIterator;
160
161
162 private Speed nextSpeed;
163
164
165 private final Iterator<Row> characteristicsIterator;
166
167
168 private Row characteristicsRow;
169
170
171 private Duration previousArrival = Duration.ZERO;
172
173
174 private MultiKeyMap<GeneratorLanePosition> lanePositions;
175
176
177 private Set<GeneratorLanePosition> allLanePositions;
178
179
180 private LaneBasedGtuCharacteristicsGenerator characteristicsGenerator;
181
182
183 private boolean readyForCharacteristicsDraw = false;
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
243
244
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
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
284
285
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
347
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
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
412
413
414
415 public boolean hasColumn(final String columnId)
416 {
417 return this.columnNumbers.containsKey(columnId);
418 }
419
420
421
422
423
424 public Supplier<String> asIdSupplier()
425 {
426 return new IdsWithCharacteristics()
427 {
428 @Override
429 public String get()
430 {
431
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
448
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;
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
475
476
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
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;
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
531
532
533
534
535
536
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
561
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
591
592
593 public RoomChecker asRoomChecker()
594 {
595 return new RoomChecker()
596 {
597
598
599
600
601
602
603
604
605
606
607
608
609
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
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
650
651
652
653 private Object getCharacteristic(final String column)
654 {
655 return this.characteristicsRow.getValue(this.columnNumbers.get(column));
656 }
657
658 }