MATSIM
TravelTimeCalculator.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * TravelTimeCalculator.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2009 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 package org.matsim.core.trafficmonitoring;
21 
22 import com.google.inject.Inject;
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.matsim.api.core.v01.Id;
26 import org.matsim.api.core.v01.IdMap;
45 import org.matsim.core.gbl.Gbl;
50 import org.matsim.vehicles.Vehicle;
52 
53 import java.util.HashSet;
54 import java.util.Map;
55 import java.util.Set;
56 import java.util.concurrent.ConcurrentHashMap;
57 
72  private static final Logger log = LogManager.getLogger(TravelTimeCalculator.class);
73 
74  private static final String ERROR_STUCK_AND_LINKTOLINK = "Using the stuck feature with turning move travel times is not available. As the next link of a stucked" +
75  "agent is not known the turning move travel time cannot be calculated!";
76 
77  private final double timeSlice;
78  private final int numSlots;
79  TimeSlotComputation aggregator;
80 
81  private final Network network;
83 
84  private Map<Tuple<Id<Link>, Id<Link>>, TravelTimeDataArray> linkToLinkData;
85 
86  private final Map<Id<Vehicle>, LinkEnterEvent> linkEnterEvents;
87 
88  private final Set<Id<Vehicle>> vehiclesToIgnore;
89  private final Set<String> analyzedModes;
90 
91  private final boolean filterAnalyzedModes;
92 
93  private final boolean calculateLinkTravelTimes;
94 
95  private final boolean calculateLinkToLinkTravelTimes;
96 
97  @Inject private QSimConfigGroup qsimConfig ;
98  TravelTimeGetter travelTimeGetter ;
99 
100  @Deprecated // use builder instead. kai, feb'19
102  TravelTimeCalculator calculator = new TravelTimeCalculator(network, group);
103  configure(calculator, group, network);
104  return calculator;
105  }
106 
108  // This should be replaced by a builder if we need the functionality. kai/mads, feb'19
109 
110  switch( config.getTravelTimeGetterType() ){
111  case "average":
112  calculator.travelTimeGetter = new AveragingTravelTimeGetter( calculator.aggregator );
113  break;
114  case "linearinterpolation":
115  calculator.travelTimeGetter = new LinearInterpolatingTravelTimeGetter( calculator.numSlots, calculator.timeSlice, calculator.aggregator );
116  break;
117  default:
118  throw new RuntimeException( config.getTravelTimeGetterType() + " is unknown!" );
119  }
120  return calculator;
121  }
122 
123  @Deprecated // user builder instead. kai, feb'19
124  @Inject // yyyy why is this needed? In general, this class is NOT injected, but explicitly constructed in TravelTimeCalculatorModule. kai, feb'19
125  TravelTimeCalculator(TravelTimeCalculatorConfigGroup ttconfigGroup, EventsManager eventsManager, Network network) {
126  // this injected constructor is not used when getSeparateModes is true
127  this(network, ttconfigGroup.getTraveltimeBinSize(), ttconfigGroup.getMaxTime(), ttconfigGroup.isCalculateLinkTravelTimes(),
128  ttconfigGroup.isCalculateLinkToLinkTravelTimes(), ttconfigGroup.isFilterModes(), CollectionUtils.stringToSet(ttconfigGroup.getAnalyzedModesAsString() ) );
129  eventsManager.addHandler(this);
130  configure(this, ttconfigGroup, network);
131  }
132 
133  @Deprecated // user builder instead. kai, feb'19
134  public TravelTimeCalculator( final Network network, TravelTimeCalculatorConfigGroup ttconfigGroup ) {
135  // one tests needs this public
136  // some tests currently use this. they are also quite happy without an events manager. kai, feb'19
137  this(network, ttconfigGroup.getTraveltimeBinSize(), ttconfigGroup.getMaxTime(), ttconfigGroup);
138  }
139 
140  @Deprecated // user builder instead. kai, feb'19
141  public TravelTimeCalculator(final Network network, final double timeslice, final int maxTime, TravelTimeCalculatorConfigGroup ttconfigGroup) {
142  this(network, timeslice, maxTime, ttconfigGroup.isCalculateLinkTravelTimes(), ttconfigGroup.isCalculateLinkToLinkTravelTimes(), ttconfigGroup.isFilterModes(),
144  }
145 
146  public final static class Builder {
147  // The idea here is that the config group will NOT be passed into this object any more. kai, feb'19
148 
149  private final Network network ;
150  private double timeslice = 900 ;
151  private int maxTime = 36*3600 ; // yy replace by long or double!
152  private boolean calculateLinkTravelTimes = true ;
153  private boolean calculateLinkToLinkTravelTimes = false ;
154  private boolean filterModes = false ;
155  private Set<String> analyzedModes = null ;
157  private boolean toBeConfigured = false ;
158 
159  public Builder( Network network ) {
160  this.network = network ;
161  }
162 
163  public void setTimeslice( double timeslice ){
164  this.timeslice = timeslice;
165  }
166 
167  public void setMaxTime( int maxTime ){
168  this.maxTime = maxTime;
169  }
170 
171  public void setCalculateLinkTravelTimes( boolean calculateLinkTravelTimes ){
172  this.calculateLinkTravelTimes = calculateLinkTravelTimes;
173  }
174 
175  public void setCalculateLinkToLinkTravelTimes( boolean calculateLinkToLinkTravelTimes ){
176  this.calculateLinkToLinkTravelTimes = calculateLinkToLinkTravelTimes;
177  }
178 
179  public void setFilterModes( boolean filterModes ){
180  this.filterModes = filterModes;
181  }
182 
183  public void setAnalyzedModes( Set<String> analyzedModes ){
184  this.analyzedModes = analyzedModes;
185  }
186 
187  public void configure ( TravelTimeCalculatorConfigGroup ttcConfig ) {
188  // yyyyyy this is a fix to get the outward API sorted out somewhat better. kai, feb'19
189  // yyyyyy presumably would like to replace this with setters for {@link TravelTimeDataFactory} and {@link TravelTimeGetter}. But it ain't that easy because
190  // they again depend on material that (currently) is only available _after_ construction of {@link TravelTimeCalculator}. kai, feb'19
191 
192  this.ttcConfig = ttcConfig ;
193  this.toBeConfigured = true ;
194  }
195 
197  TravelTimeCalculator abc = new TravelTimeCalculator( network, timeslice, maxTime, calculateLinkTravelTimes, calculateLinkToLinkTravelTimes, filterModes,
198  analyzedModes );
199  if( toBeConfigured ){
200  TravelTimeCalculator.configure( abc, this.ttcConfig, this.network );
201  }
202  return abc ;
203  }
204 
205  }
206 
207  private TravelTimeCalculator(final Network network, final double timeslice, final int maxTime,
208  boolean calculateLinkTravelTimes, boolean calculateLinkToLinkTravelTimes, boolean filterModes, Set<String> analyzedModes) {
209  this.calculateLinkTravelTimes = calculateLinkTravelTimes;
210  this.calculateLinkToLinkTravelTimes = calculateLinkToLinkTravelTimes;
211  this.filterAnalyzedModes = filterModes;
212  this.analyzedModes = analyzedModes;
213  this.network = network;
214  this.timeSlice = timeslice;
215  this.numSlots = TimeBinUtils.getTimeBinCount(maxTime, timeslice);
216  this.aggregator = new TimeSlotComputation(this.numSlots, this.timeSlice);
217  this.travelTimeGetter = new AveragingTravelTimeGetter( this.aggregator ) ;
218  if (this.calculateLinkTravelTimes) {
219  this.linkData = new IdMap<>(Link.class);
220  }
221  if (this.calculateLinkToLinkTravelTimes){
222  // assume that every link has 2 outgoing links as default
223  this.linkToLinkData = new ConcurrentHashMap<>((int) (network.getLinks().size() * 1.4 * 2));
224  }
225  this.linkEnterEvents = new ConcurrentHashMap<>();
226 
227  // if we just look at one mode, we need to ignore all vehicles with a different mode. However, the info re the mode is only in
228  // the vehicleEntersTraffic event. So we need to memorize the ignored vehicles from there ...
229  this.vehiclesToIgnore = new HashSet<>();
230 
231  this.reset(0);
232  }
233 
234  @Override
235  public void handleEvent(final LinkEnterEvent e) {
236  /* if only some modes are analyzed, we check whether the vehicles
237  * performs a trip with one of those modes. if not, we skip the event. */
238  if (filterAnalyzedModes && vehiclesToIgnore.contains(e.getVehicleId())) return;
239 
240  LinkEnterEvent oldEvent = this.linkEnterEvents.put(e.getVehicleId(), e);
241  if ((oldEvent != null) && this.calculateLinkToLinkTravelTimes) {
242  Tuple<Id<Link>, Id<Link>> fromToLink = new Tuple<>(oldEvent.getLinkId(), e.getLinkId());
243  TravelTimeData data = getLinkToLinkTravelTimeData(fromToLink );
244  double enterTime = oldEvent.getTime();
245 
246  final int timeSlot = this.aggregator.getTimeSlotIndex(enterTime );
247  data.addTravelTime(timeSlot, e.getTime() - enterTime );
248  data.setNeedsConsolidation( true );
249  }
250  }
251 
252  @Override
253  public void handleEvent(final LinkLeaveEvent e) {
254  if (this.calculateLinkTravelTimes) {
255  LinkEnterEvent oldEvent = this.linkEnterEvents.get(e.getVehicleId());
256  if (oldEvent != null) {
257  TravelTimeData data = this.getTravelTimeData(e.getLinkId(), true);
258  double enterTime = oldEvent.getTime();
259 
260  final int timeSlot = this.aggregator.getTimeSlotIndex(enterTime );
261  data.addTravelTime(timeSlot, e.getTime() - enterTime );
262  data.setNeedsConsolidation( true );
263  }
264  }
265  }
266 
267  @Override
269  /* if filtering transport modes is enabled and the vehicles
270  * starts a leg on a non analyzed transport mode, add the vehicle
271  * to the filtered vehicles set. */
272  if (filterAnalyzedModes && !analyzedModes.contains(event.getNetworkMode())) {
273  this.vehiclesToIgnore.add(event.getVehicleId());
274  }
275  }
276 
277  @Override
278  public void handleEvent(final VehicleLeavesTrafficEvent event) {
279  /* remove EnterEvents from list when a vehicle arrives.
280  * otherwise, the activity duration would be counted as travel time, when the
281  * vehicle departs again and leaves the link! */
282  this.linkEnterEvents.remove(event.getVehicleId());
283 
284  // try to remove vehicles from set with filtered vehicles
285  if (filterAnalyzedModes) this.vehiclesToIgnore.remove(event.getVehicleId());
286  }
287 
288  @Override
290  /* remove EnterEvents from list when a bus stops on a link.
291  * otherwise, the stop time would be counted as travel time, when the
292  * bus departs again and leaves the link! */
293  this.linkEnterEvents.remove(event.getVehicleId());
294  }
295 
296  @Override
297  public void handleEvent(VehicleAbortsEvent event) {
298  LinkEnterEvent e = this.linkEnterEvents.remove(event.getVehicleId());
299  if (e != null) {
300  TravelTimeData data = this.getTravelTimeData(e.getLinkId(), true);
301  data.setNeedsConsolidation( true );
302 
303  // this.aggregator.addStuckEventTravelTime(data, e.getTime(), event.getTime());
304  // this functionality is no longer there.
305 
306  if (this.calculateLinkToLinkTravelTimes
307  && event.getTime() < qsimConfig.getEndTime().seconds()
308  // (we think that this only makes problems when the abort is not just because of mobsim end time. kai & theresa, jan'17)
309  ){
310  log.error(ERROR_STUCK_AND_LINKTOLINK);
311  throw new IllegalStateException(ERROR_STUCK_AND_LINKTOLINK);
312  }
313  }
314 
315  // try to remove vehicle from set with filtered vehicles
316  if (filterAnalyzedModes) this.vehiclesToIgnore.remove(event.getVehicleId());
317  }
318 
319  private TravelTimeDataArray getTravelTimeData(final Id<Link> linkId, final boolean createIfMissing) {
320  TravelTimeDataArray data = this.linkData.get(linkId);
321  if ((null == data) && createIfMissing) {
322  data = this.createTravelTimeData(linkId);
323  this.linkData.put(linkId, data);
324  }
325  return data;
326  }
327 
328  private TravelTimeDataArray getLinkToLinkTravelTimeData( Tuple<Id<Link>, Id<Link>> fromLinkToLink ) {
329  TravelTimeDataArray data = this.linkToLinkData.get(fromLinkToLink);
330  if ( null == data ) {
331  data = this.createTravelTimeData(fromLinkToLink.getFirst()) ;
332  this.linkToLinkData.put(fromLinkToLink, data);
333  }
334  return data;
335  }
336 
337  private TravelTimeDataArray createTravelTimeData(Id<Link> linkId) {
338  return new TravelTimeDataArray(this.network.getLinks().get(linkId), this.numSlots);
339  }
340 
341  private double getLinkTravelTime(final Id<Link> linkId, final double time) {
342  if (this.calculateLinkTravelTimes) {
343 
344  TravelTimeData data = this.getTravelTimeData(linkId, true);
345  if ( data.isNeedingConsolidation() ) {
346  consolidateData(data);
347  }
348  return this.travelTimeGetter.getTravelTime( data, time );
349  }
350  throw new IllegalStateException("No link travel time is available " +
351  "if calculation is switched off by config option!");
352  }
353 
354  private double getLinkToLinkTravelTime(final Id<Link> fromLinkId, final Id<Link> toLinkId, double time) {
355  if (!this.calculateLinkToLinkTravelTimes) {
356  throw new IllegalStateException("No link to link travel time is available " +
357  "if calculation is switched off by config option!");
358  }
359  TravelTimeData data = this.getLinkToLinkTravelTimeData(new Tuple<>(fromLinkId, toLinkId) );
360  if ( data.isNeedingConsolidation() ) {
361  consolidateData(data);
362  }
363  return this.travelTimeGetter.getTravelTime( data, time );
364  }
365 
366  @Override
367  public void reset(int iteration) {
368  if (this.calculateLinkTravelTimes) {
369  for (TravelTimeData data : this.linkData.values()){
370  data.resetTravelTimes();
371  data.setNeedsConsolidation( false );
372  }
373  }
374  if (this.calculateLinkToLinkTravelTimes){
375  for (TravelTimeData data : this.linkToLinkData.values()){
376  data.resetTravelTimes();
377  data.setNeedsConsolidation( false );
378  }
379  }
380  this.linkEnterEvents.clear();
381  this.vehiclesToIgnore.clear();
382  }
383 
402  private void consolidateData(final TravelTimeData data) {
403  synchronized(data) {
404  if ( data.isNeedingConsolidation() ) {
405 
406  // initialize prevTravelTime with ttime from time bin 0 and time 0. (The interface comment already states that
407  // having both as argument does not make sense.)
408  double prevTravelTime = data.getTravelTime(0, 0.0 );
409  // changed (1, 0.0) to (0, 0.0) since Michal has convinced me (by a test) that using "1" is wrong
410  // because you get the wrong result for time slot number 1. This change does not affect the existing
411  // unit tests. kai, oct'11
412 
413  // go from time slot 1 forward in time:
414  for (int i = 1; i < this.numSlots; i++) {
415 
416  // once more the getter is weird since it needs both the time slot and the time:
417  double travelTime = data.getTravelTime(i, i * this.timeSlice );
418 
419  // if the travel time in the previous time slice was X, then now it is X-S, where S is the time slice:
420  double minTravelTime = prevTravelTime - this.timeSlice;
421 
422  // if the travel time that has been measured so far is less than that minimum travel time, then do something:
423  if (travelTime < minTravelTime) {
424  // (set the travel time to the smallest possible travel time that makes sense according to the argument above)
425  travelTime = minTravelTime;
426  data.setTravelTime(i, travelTime);
427  }
428  prevTravelTime = travelTime;
429  }
430  data.setNeedsConsolidation( false );
431  }
432  }
433  }
434 
435  private static int cnt = 0 ;
436 
438  return new TravelTime() {
439 
440  @Override
441  public double getLinkTravelTime(Link link, double time, Person person, Vehicle vehicle) {
442  // right now, the link speed limit comes from the travel time calculator, and this here just overrides it. One might consider doing all of this here;
443  // possibly would make the code easier to read. kai/mads, feb'19
444 
445  double linkTtimeFromVehicle = 0. ;
446  if ( vehicle!=null ){
447  final VehicleType vehicleType = vehicle.getType();
448  if ( vehicleType==null ){
449  if( cnt < 1 ){
450  cnt++;
451  log.warn( "encountered vehicle where vehicle.getType() returns null. That should be repaired (whereever it comes from)." );
452  log.warn( Gbl.ONLYONCE );
453  }
454  } else{
455  linkTtimeFromVehicle = link.getLength() / vehicleType.getMaximumVelocity();
456  }
457  }
458  double linkTTimeFromObservation = TravelTimeCalculator.this.getLinkTravelTime(link.getId(), time);
459  return Math.max( linkTtimeFromVehicle, linkTTimeFromObservation) ;
460  // yyyyyy should this not be min? kai/janek, may'19
461  // No, it is correct. It is preventing the router to route with an empirical speed from
462  // the previous iteration that exceeds the maximum vehicle speed.
463  // Thus, the lowest speed (highest travel time) of the two should be used. Mads, Nov'19
464  }
465 
466  };
467 
468  }
469 
471  return new LinkToLinkTravelTime() {
472 
473  @Override
474  public double getLinkToLinkTravelTime(Link fromLink, Link toLink, double time, Person person, Vehicle vehicle) {
475  double linkTtimeFromVehicle = 0. ;
476  if ( vehicle!=null ){
477  final VehicleType vehicleType = vehicle.getType();
478  if ( vehicleType==null ){
479  if( cnt < 1 ){
480  cnt++;
481  log.warn( "encountered vehicle where vehicle.getType() returns null. That should be repaired (whereever it comes from)." );
482  log.warn( Gbl.ONLYONCE );
483  }
484  } else{
485  linkTtimeFromVehicle = fromLink.getLength() / vehicleType.getMaximumVelocity();
486  }
487  }
488  double linkTTimeFromObservation = TravelTimeCalculator.this.getLinkToLinkTravelTime(fromLink.getId(), toLink.getId(), time);
489 
490  return Math.max(linkTTimeFromObservation, linkTtimeFromVehicle);
491  }
492  };
493  }
494 
495 }
void handleEvent(VehicleArrivesAtFacilityEvent event)
static final String ONLYONCE
Definition: Gbl.java:42
double getLinkToLinkTravelTime(final Id< Link > fromLinkId, final Id< Link > toLinkId, double time)
TravelTimeDataArray createTravelTimeData(Id< Link > linkId)
static Set< String > stringToSet(final String values)
TravelTimeCalculator(final Network network, TravelTimeCalculatorConfigGroup ttconfigGroup)
void configure(TravelTimeCalculatorConfigGroup ttcConfig)
Collection< V > values()
Definition: IdMap.java:204
static TravelTimeCalculator configure(TravelTimeCalculator calculator, TravelTimeCalculatorConfigGroup config, Network network)
void addHandler(final EventHandler handler)
final Map< Id< Vehicle >, LinkEnterEvent > linkEnterEvents
Map< Tuple< Id< Link >, Id< Link > >, TravelTimeDataArray > linkToLinkData
TravelTimeDataArray getTravelTimeData(final Id< Link > linkId, final boolean createIfMissing)
TravelTimeDataArray getLinkToLinkTravelTimeData(Tuple< Id< Link >, Id< Link >> fromLinkToLink)
void setCalculateLinkToLinkTravelTimes(boolean calculateLinkToLinkTravelTimes)
double getLinkTravelTime(final Id< Link > linkId, final double time)
static TravelTimeCalculator create(Network network, TravelTimeCalculatorConfigGroup group)
Map< Id< Link >, ? extends Link > getLinks()
TravelTimeCalculator(final Network network, final double timeslice, final int maxTime, boolean calculateLinkTravelTimes, boolean calculateLinkToLinkTravelTimes, boolean filterModes, Set< String > analyzedModes)
static int getTimeBinCount(int maxTime, double travelTimeBinSize)
V put(Id< T > key, V value)
Definition: IdMap.java:141
TravelTimeCalculator(final Network network, final double timeslice, final int maxTime, TravelTimeCalculatorConfigGroup ttconfigGroup)
void handleEvent(final VehicleLeavesTrafficEvent event)