/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.routing.trippattern;
import lombok.val;
/**
* An DelayedTripTimes applies an offset to arrival and departure times based on a report that a
* vehicle is a given number of seconds early or late, and reports that the vehicle has
* passed all stops up to a certain point based on a report of vehicle location.
*/
public class DecayingDelayTripTimes extends DelegatingTripTimes {
private final int currentStop;
private final int t0;
private final int delay;
private final boolean linear;
private final boolean readThrough;
private final double lambda;
public DecayingDelayTripTimes(ScheduledTripTimes sched, int currentStop, int delay) {
this(sched, currentStop, delay, 500, false, false);
}
public DecayingDelayTripTimes(ScheduledTripTimes sched, int currentStop, int delay,
double halfLife, boolean linear, boolean readThrough) {
super(sched);
this.t0 = sched.getDepartureTime(currentStop);
this.delay = delay;
this.currentStop = currentStop;
this.linear = linear;
this.readThrough = readThrough;
this.lambda = 1.0/halfLife;
}
@Override public int getDepartureTime(int hop) {
int stop = hop;
if (stop < currentStop) {
if (readThrough)
return super.getDepartureTime(hop);
return TripTimes.PASSED;
}
int t = super.getDepartureTime(hop);
int elapsed = t - t0;
return t + decayedDelay(elapsed);
}
@Override public int getArrivalTime(int hop) {
int stop = hop + 1;
if (stop < currentStop) {
if (readThrough)
return super.getArrivalTime(hop);
return TripTimes.PASSED;
}
int t = super.getArrivalTime(hop);
int elapsed = t - t0;
return t + decayedDelay(elapsed);
}
private int decayedDelay(int dt) {
if (delay == 0)
return 0;
// This would make the decay symmetric about the current stop. Not currently needed, as
// we are reporting PASSED for all stops before currentStop.
// n = Math.abs(n);
double decay;
if (linear) {
decay = 1 - dt * lambda * 0.5;
if (decay < 0)
decay = 0;
} else {
decay = Math.exp(-lambda * dt);
}
return (int) (decay * delay);
}
@Override public String toString() {
val sb = new StringBuilder();
sb.append(String.format("%s DecayingDelayTripTimes delay=%d stop=%d halfLife=%03.1f\n",
linear ? "Linear" : "Exponential", delay, currentStop, 1/lambda));
for (int i = 0; i < getNumHops(); i++) {
int td = super.getDepartureTime(i);
int ed = td - t0;
int ta = super.getArrivalTime(i);
int ea = ta - t0;
int dd = 0;
int da = 0;
if (i >= currentStop) {
dd = decayedDelay(ed);
da = decayedDelay(ea);
}
String s = String.format("(%d)%5d %5d", i, dd, da);
sb.append(s);
}
sb.append('\n');
sb.append(dumpTimes());
sb.append("\nbased on: ");
sb.append(super.toString());
return sb.toString();
}
@Override public boolean compact() {
// Nothing much to compact. Maybe compute a decay lookup table?
return false;
}
}