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