All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.routing.impl.OnBoardDepartServiceImpl Maven / Gradle / Ivy

package org.opentripplanner.routing.impl;

import java.util.List;

import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Trip;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.ServiceDay;
import org.opentripplanner.routing.edgetype.OnBoardDepartPatternHop;
import org.opentripplanner.routing.edgetype.PatternHop;
import org.opentripplanner.routing.edgetype.Timetable;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.services.OnBoardDepartService;
import org.opentripplanner.routing.trippattern.TripTimes;
import org.opentripplanner.routing.vertextype.OnboardDepartVertex;
import org.opentripplanner.routing.vertextype.PatternStopVertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;

/**
 * Graph service for depart-on-board mode.
 * 
 * Default implementation with initial position and date-time set.
 * 
 * We need departure date-time on top of initial position to determine the service day on which the
 * given trip is running.
 * 
 * Works only for standard trips: frequency-based is not (yet?) supported.
 * 
 * TODO The method is not optimal for looping trips, as several origin point can map to distinct
 * location on trip shape.
 * 
 * @author laurent
 */
public class OnBoardDepartServiceImpl implements OnBoardDepartService {
    private static final long serialVersionUID = -3729628250159575313L;

    private static final Logger LOG = LoggerFactory.getLogger(OnBoardDepartServiceImpl.class);

    @Override
    public Vertex setupDepartOnBoard(RoutingContext ctx) {

        RoutingRequest opt = ctx.opt;
        opt.rctx = ctx;

        /* 1. Get the list of PatternHop for the given trip ID. */
        FeedScopedId tripId = opt.startingTransitTripId;
        Trip trip = ctx.graph.index.tripForId.get(tripId);
        TripPattern tripPattern = ctx.graph.index.patternForTrip.get(trip);
        if (tripPattern == null) {
            // TODO Shouldn't we bailout on a normal trip plan here, returning null ?
            throw new IllegalArgumentException("Unknown/invalid trip ID: " + tripId);
        }
        List hops = tripPattern.getPatternHops();

        Double lon = opt.from.lng; // Origin point, optional
        Double lat = opt.from.lat;
        PatternStopVertex nextStop;
        TripTimes bestTripTimes = null;
        ServiceDay bestServiceDay = null;
        int bestStopIndex = 0;
        double fractionCovered;
        LineString geomRemaining;

        Coordinate point = lon == null || lat == null ? null : new Coordinate(lon, lat);
        if (point != null) {
            /*
             * 2. Get the best hop from the list, given the parameters. Currently look for nearest hop,
             * taking into account shape if available. If no shape are present, the computed hop and
             * fraction may be a bit away from what it should be.
             */
            PatternHop bestHop = null;
            double minDist = Double.MAX_VALUE;
            for (PatternHop hop : hops) {
                LineString line = hop.getGeometry();
                double dist = SphericalDistanceLibrary.fastDistance(point, line);
                if (dist < minDist) {
                    minDist = dist;
                    bestHop = hop;
                }
            }
            if (minDist > 1000) LOG.warn(
                    "On-board depart: origin point suspiciously away from nearest trip shape ({} meters)",
                    minDist);
            else LOG.info("On-board depart: origin point {} meters away from hop shape", minDist);

            /*
             * 3. Compute the fraction covered percentage of the current hop. This assume a constant
             * trip speed alongside the whole hop: this should be quite precise for small hops
             * (buses), a bit less for longer ones (long distance train). Shape linear distance is
             * of no help here, as the unit is arbitrary (and probably usually a distance).
             */
            LineString geometry = bestHop.getGeometry();
            P2 geomPair = GeometryUtils.splitGeometryAtPoint(geometry, point);
            geomRemaining = geomPair.second;
            double total = SphericalDistanceLibrary.fastLength(geometry);
            double remaining = SphericalDistanceLibrary.fastLength(geomRemaining);
            fractionCovered = total > 0.0 ? (double) (1.0 - remaining / total) : 0.0;

            nextStop = (PatternStopVertex) bestHop.getToVertex();
            bestStopIndex = bestHop.getStopIndex();

            /*
             * 4. Compute service day based on given departure day/time relative to
             * scheduled/real-time trip time for hop. This is needed as for some trips any service
             * day can apply.
             */
            int minDelta = Integer.MAX_VALUE;
            int actDelta = 0;
            for (ServiceDay serviceDay : ctx.serviceDays) {
                TripPattern pattern = nextStop.getTripPattern();
                Timetable timetable = pattern.getUpdatedTimetable(opt, serviceDay);
                // Get the tripTimes including real-time updates for the serviceDay
                TripTimes tripTimes = timetable.getTripTimes(timetable.getTripIndex(tripId));

                int depTime = tripTimes.getDepartureTime(bestStopIndex);
                int arrTime = tripTimes.getArrivalTime(bestStopIndex + 1);
                int estTime = (int) Math.round(
                        depTime * fractionCovered + arrTime * (1 - fractionCovered));

                int time = serviceDay.secondsSinceMidnight(opt.dateTime);
                /*
                 * TODO Weight differently early vs late time, as the probability of any transit
                 * being late is higher than being early. However, this has impact if your bus is
                 * more than 12h late, I don't think this would happen really often.
                 */
                int deltaTime = Math.abs(time - estTime);
                if (deltaTime < minDelta) {
                    minDelta = deltaTime;
                    actDelta = time - estTime;
                    bestTripTimes = tripTimes;
                    bestServiceDay = serviceDay;
                }
            }
            if (minDelta > 60000) LOG.warn(       // Being more than 1h late should not happen often
                    "On-board depart: delta between scheduled/real-time and actual time suspiciously large: {} seconds.",
                    actDelta);
            else LOG.info(
                    "On-board depart: delta between scheduled/real-time and actual time is {} seconds.",
                    actDelta);
        } else {
            /* 2. Compute service day */
            for (ServiceDay serviceDay : ctx.serviceDays) {
                Timetable timetable = tripPattern.getUpdatedTimetable(opt, serviceDay);
                // Get the tripTimes including real-time updates for the serviceDay
                TripTimes tripTimes = timetable.getTripTimes(timetable.getTripIndex(tripId));

                int depTime = tripTimes.getDepartureTime(0);
                int arrTime = tripTimes.getArrivalTime(tripTimes.getNumStops() - 1);

                int time = serviceDay.secondsSinceMidnight(opt.dateTime);

                if (depTime <= time && time <= arrTime) {
                    bestTripTimes = tripTimes;
                    bestServiceDay = serviceDay;
                }
            }

            if (bestServiceDay == null) {
                throw new RuntimeException("Unable to determine on-board depart service day.");
            }

            int time = bestServiceDay.secondsSinceMidnight(opt.dateTime);

            /*
             * 3. Get the best hop from the list, given the parameters. This is done by finding the
             * last hop that has not yet departed.
             */

            PatternHop bestHop = null;

            for (PatternHop hop : hops) {
                int stopIndex = hop.getStopIndex();
                int depTime = bestTripTimes.getDepartureTime(stopIndex);
                int arrTime = bestTripTimes.getArrivalTime(stopIndex + 1);

                if (time == arrTime) {
                    return ctx.graph.getVertex(hop.getEndStop().getId().toString());
                } else if (depTime < time) {
                    bestHop = hop;
                    bestStopIndex = stopIndex;
                } else if (time == depTime || bestTripTimes.getArrivalTime(bestStopIndex + 1) < time) {
                    return ctx.graph.getVertex(hop.getBeginStop().getId().toString());
                } else {
                    break;
                }
            }

            nextStop = (PatternStopVertex) bestHop.getToVertex();

            LineString geometry = bestHop.getGeometry();

            /*
             * 4. Compute the fraction covered percentage of the current hop. Once again a constant
             * trip speed is assumed. The linear distance of the shape is used, so the results are
             * not 100% accurate. On the flip side, they are easy to compute and very well testable.
             */
            int depTime = bestTripTimes.getDepartureTime(bestStopIndex);
            int arrTime = bestTripTimes.getArrivalTime(bestStopIndex + 1);
            fractionCovered =  ((double) (time - depTime)) / ((double) (arrTime - depTime));

            P2 geomPair =
                    GeometryUtils.splitGeometryAtFraction(geometry, fractionCovered);
            geomRemaining = geomPair.second;

            if (geometry.isEmpty()) {
                lon = Double.NaN;
                lat = Double.NaN;
            } else {
                Coordinate start;
                if (geomRemaining.isEmpty()) {
                    start = geometry.getCoordinateN(geometry.getNumPoints() - 1);
                } else {
                    start = geomRemaining.getCoordinateN(0);
                }
                lon = start.x;
                lat = start.y;
            }
        }

        OnboardDepartVertex onboardDepart = new OnboardDepartVertex("on_board_depart", lon, lat);
        OnBoardDepartPatternHop startHop = new OnBoardDepartPatternHop(onboardDepart, nextStop,
                bestTripTimes, bestServiceDay, bestStopIndex, fractionCovered);
        startHop.setGeometry(geomRemaining);
        return onboardDepart;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy