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