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

com.graphhopper.gtfs.TripFromLabel Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.graphhopper.gtfs;

import com.conveyal.gtfs.GTFSFeed;
import com.conveyal.gtfs.model.Stop;
import com.conveyal.gtfs.model.StopTime;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.transit.realtime.GtfsRealtime;
import com.graphhopper.ResponsePath;
import com.graphhopper.Trip;
import com.graphhopper.gtfs.fare.Fares;
import com.graphhopper.routing.InstructionsFromEdges;
import com.graphhopper.routing.Path;
import com.graphhopper.routing.ev.EncodedValueLookup;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
import com.graphhopper.util.*;
import com.graphhopper.util.details.PathDetail;
import com.graphhopper.util.details.PathDetailsBuilderFactory;
import com.graphhopper.util.details.PathDetailsFromEdges;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.time.temporal.ChronoUnit.SECONDS;

class TripFromLabel {

    private static final Logger logger = LoggerFactory.getLogger(TripFromLabel.class);

    private final Graph graph;
    private final EncodedValueLookup encodedValueLookup;
    private final GtfsStorage gtfsStorage;
    private final RealtimeFeed realtimeFeed;
    private final GeometryFactory geometryFactory = new GeometryFactory();
    private final PathDetailsBuilderFactory pathDetailsBuilderFactory;
    private final double walkSpeedKmH;

    TripFromLabel(Graph graph, EncodedValueLookup encodedValueLookup, GtfsStorage gtfsStorage, RealtimeFeed realtimeFeed, PathDetailsBuilderFactory pathDetailsBuilderFactory, double walkSpeedKmH) {
        this.graph = graph;
        this.encodedValueLookup = encodedValueLookup;
        this.gtfsStorage = gtfsStorage;
        this.realtimeFeed = realtimeFeed;
        this.pathDetailsBuilderFactory = pathDetailsBuilderFactory;
        this.walkSpeedKmH = walkSpeedKmH;
    }

    ResponsePath createResponsePath(Translation tr, PointList waypoints, Graph queryGraph, Weighting accessWeighting, Weighting egressWeighting, Weighting transferWeighting, List solution, List requestedPathDetails) {
        final List> partitions = parsePathToPartitions(solution);

        final List legs = new ArrayList<>();
        for (int i = 0; i < partitions.size(); i++) {
            Weighting weighting;
            if (i == 0)
                weighting = accessWeighting;
            else if (i == partitions.size() - 1)
                weighting = egressWeighting;
            else
                weighting = transferWeighting;
            legs.addAll(parsePartitionToLegs(partitions.get(i), queryGraph, encodedValueLookup, weighting, tr, requestedPathDetails));
        }

        if (legs.size() > 1 && legs.get(0) instanceof Trip.WalkLeg) {
            final Trip.WalkLeg accessLeg = (Trip.WalkLeg) legs.get(0);
            legs.set(0, new Trip.WalkLeg(accessLeg.departureLocation, new Date(legs.get(1).getDepartureTime().getTime() - (accessLeg.getArrivalTime().getTime() - accessLeg.getDepartureTime().getTime())),
                    accessLeg.geometry, accessLeg.distance, accessLeg.instructions, accessLeg.details, legs.get(1).getDepartureTime()));
        }
        if (legs.size() > 1 && legs.get(legs.size() - 1) instanceof Trip.WalkLeg) {
            final Trip.WalkLeg egressLeg = (Trip.WalkLeg) legs.get(legs.size() - 1);
            legs.set(legs.size() - 1, new Trip.WalkLeg(egressLeg.departureLocation, legs.get(legs.size() - 2).getArrivalTime(),
                    egressLeg.geometry, egressLeg.distance, egressLeg.instructions,
                    egressLeg.details, new Date(legs.get(legs.size() - 2).getArrivalTime().getTime() + (egressLeg.getArrivalTime().getTime() - egressLeg.getDepartureTime().getTime()))));
        }

        ResponsePath path = new ResponsePath();
        path.setWaypoints(waypoints);
        path.getLegs().addAll(legs);

        final InstructionList instructions = new InstructionList(tr);
        final PointList pointsList = new PointList();
        Map> pathDetails = new HashMap<>();
        for (int i = 0; i < path.getLegs().size(); ++i) {
            Trip.Leg leg = path.getLegs().get(i);
            if (leg instanceof Trip.WalkLeg) {
                final Trip.WalkLeg walkLeg = ((Trip.WalkLeg) leg);
                List theseInstructions = walkLeg.instructions.subList(0, i < path.getLegs().size() - 1 ? walkLeg.instructions.size() - 1 : walkLeg.instructions.size());
                int previousPointsCount = pointsList.size();
                for (Instruction instruction : theseInstructions) {
                    pointsList.add(instruction.getPoints());
                }
                instructions.addAll(theseInstructions);
                Map> shiftedLegPathDetails = shift(((Trip.WalkLeg) leg).details, previousPointsCount);
                shiftedLegPathDetails.forEach((k, v) -> pathDetails.merge(k, shiftedLegPathDetails.get(k), (a, b) -> Lists.newArrayList(Iterables.concat(a, b))));
            } else if (leg instanceof Trip.PtLeg) {
                final Trip.PtLeg ptLeg = ((Trip.PtLeg) leg);
                final PointList pl;
                if (!ptLeg.isInSameVehicleAsPrevious) {
                    pl = new PointList();
                    final Instruction departureInstruction = new Instruction(Instruction.PT_START_TRIP, ptLeg.trip_headsign, pl);
                    departureInstruction.setDistance(leg.getDistance());
                    departureInstruction.setTime(ptLeg.travelTime);
                    instructions.add(departureInstruction);
                } else {
                    pl = instructions.get(instructions.size() - 2).getPoints();
                }
                pl.add(ptLeg.stops.get(0).geometry.getY(), ptLeg.stops.get(0).geometry.getX());
                pointsList.add(ptLeg.stops.get(0).geometry.getY(), ptLeg.stops.get(0).geometry.getX());
                for (Trip.Stop stop : ptLeg.stops.subList(0, ptLeg.stops.size() - 1)) {
                    pl.add(stop.geometry.getY(), stop.geometry.getX());
                    pointsList.add(stop.geometry.getY(), stop.geometry.getX());
                }
                final PointList arrivalPointList = new PointList();
                final Trip.Stop arrivalStop = ptLeg.stops.get(ptLeg.stops.size() - 1);
                arrivalPointList.add(arrivalStop.geometry.getY(), arrivalStop.geometry.getX());
                pointsList.add(arrivalStop.geometry.getY(), arrivalStop.geometry.getX());
                Instruction arrivalInstruction = new Instruction(Instruction.PT_END_TRIP, arrivalStop.stop_name, arrivalPointList);
                if (ptLeg.isInSameVehicleAsPrevious) {
                    instructions.set(instructions.size() - 1, arrivalInstruction);
                } else {
                    instructions.add(arrivalInstruction);
                }
            }
        }
        path.setInstructions(instructions);
        path.setPoints(pointsList);
        path.addPathDetails(pathDetails);
        path.setDistance(path.getLegs().stream().mapToDouble(Trip.Leg::getDistance).sum());
        path.setTime((legs.get(legs.size() - 1).getArrivalTime().toInstant().toEpochMilli() - legs.get(0).getDepartureTime().toInstant().toEpochMilli()));
        path.setNumChanges((int) path.getLegs().stream()
                .filter(l -> l instanceof Trip.PtLeg)
                .filter(l -> !((Trip.PtLeg) l).isInSameVehicleAsPrevious)
                .count() - 1);
        com.graphhopper.gtfs.fare.Trip faresTrip = new com.graphhopper.gtfs.fare.Trip();
        path.getLegs().stream()
                .filter(leg -> leg instanceof Trip.PtLeg)
                .map(leg -> (Trip.PtLeg) leg)
                .findFirst()
                .ifPresent(firstPtLeg -> {
                    LocalDateTime firstPtDepartureTime = GtfsHelper.localDateTimeFromDate(firstPtLeg.getDepartureTime());
                    path.getLegs().stream()
                            .filter(leg -> leg instanceof Trip.PtLeg)
                            .map(leg -> (Trip.PtLeg) leg)
                            .map(ptLeg -> {
                                final GTFSFeed gtfsFeed = gtfsStorage.getGtfsFeeds().get(ptLeg.feed_id);
                                return new com.graphhopper.gtfs.fare.Trip.Segment(ptLeg.feed_id, ptLeg.route_id,
                                        Duration.between(firstPtDepartureTime, GtfsHelper.localDateTimeFromDate(ptLeg.getDepartureTime())).getSeconds(),
                                        gtfsFeed.stops.get(ptLeg.stops.get(0).stop_id).zone_id, gtfsFeed.stops.get(ptLeg.stops.get(ptLeg.stops.size() - 1).stop_id).zone_id,
                                        ptLeg.stops.stream().map(s -> gtfsFeed.stops.get(s.stop_id).zone_id).collect(Collectors.toSet()));
                            })
                            .forEach(faresTrip.segments::add);
                    Fares.cheapestFare(gtfsStorage.getFares(), faresTrip)
                            .ifPresent(amount -> path.setFare(amount.getAmount()));
                });
        return path;
    }

    private Map> shift(Map> pathDetailss, int previousPointsCount) {
        return Maps.transformEntries(pathDetailss, (s, pathDetails) -> pathDetails.stream().map(p -> {
            PathDetail pathDetail = new PathDetail(p.getValue());
            pathDetail.setFirst(p.getFirst() + previousPointsCount);
            pathDetail.setLast(p.getLast() + previousPointsCount);
            return pathDetail;
        }).collect(Collectors.toList()));
    }

    private List> parsePathToPartitions(List path) {
        List> partitions = new ArrayList<>();
        partitions.add(new ArrayList<>());
        final Iterator iterator = path.iterator();
        partitions.get(partitions.size() - 1).add(iterator.next());
        iterator.forEachRemaining(transition -> {
            final List previous = partitions.get(partitions.size() - 1);
            final GraphExplorer.MultiModalEdge previousEdge = previous.get(previous.size() - 1).edge;
            if (previousEdge != null && (transition.edge.getType() == GtfsStorage.EdgeType.ENTER_PT || previousEdge.getType() == GtfsStorage.EdgeType.EXIT_PT)) {
                final ArrayList p = new ArrayList<>();
                p.add(new Label.Transition(previous.get(previous.size() - 1).label, null));
                partitions.add(p);
            }
            partitions.get(partitions.size() - 1).add(transition);
        });
        return partitions;
    }

    private class StopsFromBoardHopDwellEdges {

        private final GtfsRealtime.TripDescriptor tripDescriptor;
        private final List stops = new ArrayList<>();
        private final GTFSFeed gtfsFeed;
        private Instant boardTime;
        private Instant arrivalTimeFromHopEdge;
        private Optional updatedArrival;
        private StopTime stopTime = null;
        private GtfsReader.TripWithStopTimes tripUpdate = null;
        private int stopSequence = 0;

        StopsFromBoardHopDwellEdges(String feedId, GtfsRealtime.TripDescriptor tripDescriptor) {
            this.tripDescriptor = tripDescriptor;
            this.gtfsFeed = gtfsStorage.getGtfsFeeds().get(feedId);
            if (this.tripUpdate != null) {
                validateTripUpdate(this.tripUpdate);
            }
        }

        void next(Label.Transition t) {
            switch (t.edge.getType()) {
                case BOARD: {
                    boardTime = Instant.ofEpochMilli(t.label.currentTime);
                    stopSequence = t.edge.getStopSequence();
                    stopTime = realtimeFeed.getStopTime(gtfsFeed, tripDescriptor, t, boardTime, stopSequence);
                    tripUpdate = realtimeFeed.getTripUpdate(gtfsFeed, tripDescriptor, boardTime).orElse(null);
                    Instant plannedDeparture = Instant.ofEpochMilli(t.label.currentTime);
                    Optional updatedDeparture = getDepartureDelay(stopSequence).map(delay -> plannedDeparture.plus(delay, SECONDS));
                    Stop stop = gtfsFeed.stops.get(stopTime.stop_id);
                    stops.add(new Trip.Stop(stop.stop_id, stop.stop_name, geometryFactory.createPoint(new Coordinate(stop.stop_lon, stop.stop_lat)),
                            null, null, null, isArrivalCancelled(stopSequence),
                            updatedDeparture.map(Date::from).orElse(Date.from(plannedDeparture)), Date.from(plannedDeparture),
                            updatedDeparture.map(Date::from).orElse(null), isDepartureCancelled(stopSequence)));
                    break;
                }
                case HOP: {
                    stopSequence = t.edge.getStopSequence();
                    stopTime = realtimeFeed.getStopTime(gtfsFeed, tripDescriptor, t, boardTime, stopSequence);
                    arrivalTimeFromHopEdge = Instant.ofEpochMilli(t.label.currentTime);
                    updatedArrival = getArrivalDelay(stopSequence).map(delay -> arrivalTimeFromHopEdge.plus(delay, SECONDS));
                    break;
                }
                case DWELL: {
                    Instant plannedDeparture = Instant.ofEpochMilli(t.label.currentTime);
                    Optional updatedDeparture = getDepartureDelay(stopTime.stop_sequence).map(delay -> plannedDeparture.plus(delay, SECONDS));
                    Stop stop = gtfsFeed.stops.get(stopTime.stop_id);
                    stops.add(new Trip.Stop(stop.stop_id, stop.stop_name, geometryFactory.createPoint(new Coordinate(stop.stop_lon, stop.stop_lat)),
                            updatedArrival.map(Date::from).orElse(Date.from(arrivalTimeFromHopEdge)), Date.from(arrivalTimeFromHopEdge),
                            updatedArrival.map(Date::from).orElse(null), isArrivalCancelled(stopSequence),
                            updatedDeparture.map(Date::from).orElse(Date.from(plannedDeparture)), Date.from(plannedDeparture),
                            updatedDeparture.map(Date::from).orElse(null), isDepartureCancelled(stopSequence)));
                    break;
                }
                default: {
                    throw new RuntimeException();
                }
            }
        }

        private Optional getArrivalDelay(int stopSequence) {
            if (tripUpdate != null) {
                int arrival_time = tripUpdate.stopTimes.stream().filter(st -> st.stop_sequence == stopSequence).findFirst().orElseThrow(() -> new RuntimeException("Stop time not found.")).arrival_time;
                logger.trace("stop_sequence {} scheduled arrival {} updated arrival {}", stopSequence, stopTime.arrival_time, arrival_time);
                return Optional.of(arrival_time - stopTime.arrival_time);
            } else {
                return Optional.empty();
            }
        }

        private boolean isArrivalCancelled(int stopSequence) {
            if (tripUpdate != null) {
                return tripUpdate.cancelledArrivals.contains(stopSequence);
            } else {
                return false;
            }
        }

        private Optional getDepartureDelay(int stopSequence) {
            if (tripUpdate != null) {
                int departure_time = tripUpdate.stopTimes.stream().filter(st -> st.stop_sequence == stopSequence).findFirst().orElseThrow(() -> new RuntimeException("Stop time not found.")).departure_time;
                logger.trace("stop_sequence {} scheduled departure {} updated departure {}", stopSequence, stopTime.departure_time, departure_time);
                return Optional.of(departure_time - stopTime.departure_time);
            } else {
                return Optional.empty();
            }
        }

        private boolean isDepartureCancelled(int stopSequence) {
            if (tripUpdate != null) {
                return tripUpdate.cancelledDeparture.contains(stopSequence);
            } else {
                return false;
            }
        }

        void finish() {
            Stop stop = gtfsFeed.stops.get(stopTime.stop_id);
            stops.add(new Trip.Stop(stop.stop_id, stop.stop_name, geometryFactory.createPoint(new Coordinate(stop.stop_lon, stop.stop_lat)),
                    updatedArrival.map(Date::from).orElse(Date.from(arrivalTimeFromHopEdge)), Date.from(arrivalTimeFromHopEdge),
                    updatedArrival.map(Date::from).orElse(null), isArrivalCancelled(stopSequence), null,
                    null, null, isDepartureCancelled(stopSequence)));
            for (Trip.Stop tripStop : stops) {
                logger.trace("{}", tripStop);
            }
        }

        private void validateTripUpdate(GtfsReader.TripWithStopTimes tripUpdate) {
            try {
                Iterable interpolatedStopTimesForTrip = gtfsFeed.getInterpolatedStopTimesForTrip(tripUpdate.trip.trip_id);
                long nStopTimes = StreamSupport.stream(interpolatedStopTimesForTrip.spliterator(), false).count();
                logger.trace("Original stop times: {} Updated stop times: {}", nStopTimes, tripUpdate.stopTimes.size());
                if (nStopTimes != tripUpdate.stopTimes.size()) {
                    logger.error("Original stop times: {} Updated stop times: {}", nStopTimes, tripUpdate.stopTimes.size());
                }
            } catch (GTFSFeed.FirstAndLastStopsDoNotHaveTimes firstAndLastStopsDoNotHaveTimes) {
                throw new RuntimeException(firstAndLastStopsDoNotHaveTimes);
            }
        }

    }

    // We are parsing a string of edges into a hierarchical trip.
    // One could argue that one should never write a parser
    // by hand, because it is always ugly, but use a parser library.
    // The code would then read like a specification of what paths through the graph mean.
    private List parsePartitionToLegs(List path, Graph graph, EncodedValueLookup encodedValueLookup, Weighting weighting, Translation tr, List requestedPathDetails) {
        if (path.size() <= 1) {
            return Collections.emptyList();
        }
        if (GtfsStorage.EdgeType.ENTER_PT == path.get(1).edge.getType()) {
            String feedId = path.get(1).edge.getPlatformDescriptor().feed_id;
            List result = new ArrayList<>();
            long boardTime = -1;
            List partition = null;
            for (int i = 1; i < path.size(); i++) {
                Label.Transition transition = path.get(i);
                GraphExplorer.MultiModalEdge edge = path.get(i).edge;
                if (edge.getType() == GtfsStorage.EdgeType.BOARD) {
                    boardTime = transition.label.currentTime;
                    partition = new ArrayList<>();
                }
                if (partition != null) {
                    partition.add(path.get(i));
                }
                if (EnumSet.of(GtfsStorage.EdgeType.TRANSFER, GtfsStorage.EdgeType.LEAVE_TIME_EXPANDED_NETWORK).contains(edge.getType())) {
                    GtfsRealtime.TripDescriptor tripDescriptor = partition.get(0).edge.getTripDescriptor();
                    final StopsFromBoardHopDwellEdges stopsFromBoardHopDwellEdges = new StopsFromBoardHopDwellEdges(feedId, tripDescriptor);
                    partition.stream()
                            .filter(e -> EnumSet.of(GtfsStorage.EdgeType.HOP, GtfsStorage.EdgeType.BOARD, GtfsStorage.EdgeType.DWELL).contains(e.edge.getType()))
                            .forEach(stopsFromBoardHopDwellEdges::next);
                    stopsFromBoardHopDwellEdges.finish();
                    List stops = stopsFromBoardHopDwellEdges.stops;

                    result.add(new Trip.PtLeg(
                            feedId, partition.get(0).edge.getTransfers() == 0,
                            tripDescriptor.getTripId(),
                            tripDescriptor.getRouteId(),
                            Optional.ofNullable(gtfsStorage.getGtfsFeeds().get(feedId).trips.get(tripDescriptor.getTripId())).map(t -> t.trip_headsign).orElse("extra"),
                            stops,
                            partition.stream().mapToDouble(t -> t.edge.getDistance()).sum(),
                            path.get(i - 1).label.currentTime - boardTime,
                            geometryFactory.createLineString(stops.stream().map(s -> s.geometry.getCoordinate()).toArray(Coordinate[]::new))));
                    partition = null;
                    if (edge.getType() == GtfsStorage.EdgeType.TRANSFER) {
                        feedId = edge.getPlatformDescriptor().feed_id;
                        int[] skippedEdgesForTransfer = gtfsStorage.getSkippedEdgesForTransfer().get(edge.getId());
                        if (skippedEdgesForTransfer != null) {
                            List legs = parsePartitionToLegs(transferPath(skippedEdgesForTransfer, weighting, path.get(i - 1).label.currentTime), graph, encodedValueLookup, weighting, tr, requestedPathDetails);
                            result.add(legs.get(0));
                        }
                    }
                }
            }
            return result;
        } else {
            InstructionList instructions = new InstructionList(tr);
            InstructionsFromEdges instructionsFromEdges = new InstructionsFromEdges(graph,
                    weighting, encodedValueLookup, instructions);
            int prevEdgeId = -1;
            for (int i = 1; i < path.size(); i++) {
                if (path.get(i).edge.getType() != GtfsStorage.EdgeType.HIGHWAY) {
                    throw new IllegalStateException("Got a transit edge where I think I must be on a road.");
                }
                EdgeIteratorState edge = graph.getEdgeIteratorState(path.get(i).edge.getId(), path.get(i).label.node.streetNode);
                instructionsFromEdges.next(edge, i, prevEdgeId);
                prevEdgeId = edge.getEdge();
            }
            instructionsFromEdges.finish();

            Path pathh = new Path(graph);
            for (Label.Transition transition : path) {
                if (transition.edge != null)
                    pathh.addEdge(transition.edge.getId());
            }
            pathh.setFromNode(path.get(0).label.node.streetNode);
            pathh.setEndNode(path.get(path.size() - 1).label.node.streetNode);
            pathh.setFound(true);
            Map> pathDetails = PathDetailsFromEdges.calcDetails(pathh, encodedValueLookup, weighting, requestedPathDetails, pathDetailsBuilderFactory, 0, graph);

            final Instant departureTime = Instant.ofEpochMilli(path.get(0).label.currentTime);
            final Instant arrivalTime = Instant.ofEpochMilli(path.get(path.size() - 1).label.currentTime);
            return Collections.singletonList(new Trip.WalkLeg(
                    "Walk",
                    Date.from(departureTime),
                    lineStringFromInstructions(instructions),
                    edges(path).mapToDouble(edgeLabel -> edgeLabel.getDistance()).sum(),
                    instructions,
                    pathDetails,
                    Date.from(arrivalTime)));
        }
    }

    public List transferPath(int[] skippedEdgesForTransfer, Weighting transferWeighting, long currentTime) {
        GraphExplorer graphExplorer = new GraphExplorer(graph, gtfsStorage.getPtGraph(), transferWeighting, gtfsStorage, realtimeFeed, false, true, false, walkSpeedKmH, false, 0);
        return graphExplorer.walkPath(skippedEdgesForTransfer, currentTime);
    }

    private Stream edges(List path) {
        return path.stream().filter(t -> t.edge != null).map(t -> t.edge);
    }

    private Geometry lineStringFromInstructions(InstructionList instructions) {
        final PointList pointsList = new PointList();
        for (Instruction instruction : instructions) {
            pointsList.add(instruction.getPoints());
        }
        return pointsList.toLineString(false);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy