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

org.opentripplanner.ext.siri.SiriTripPatternCache Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package org.opentripplanner.ext.siri;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Stop;
import org.opentripplanner.model.StopPattern;
import org.opentripplanner.model.Trip;
import org.opentripplanner.model.TripPattern;
import org.opentripplanner.model.calendar.ServiceDate;
import org.opentripplanner.routing.graph.Graph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A synchronized cache of trip patterns that are added to the graph due to GTFS-realtime messages.
 */
public class SiriTripPatternCache {

    private static final Logger log = LoggerFactory.getLogger(SiriTripPatternCache.class);

    private int counter = 0;

    private final Map cache = new HashMap<>();

    private final ListMultimap patternsForStop = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());

    private final Map updatedTripPatternsForTripCache = new HashMap<>();

    /**
     * Get cached trip pattern or create one if it doesn't exist yet. If a trip pattern is created, vertices
     * and edges for this trip pattern are also created in the graph.
     * 
     * @param stopPattern stop pattern to retrieve/create trip pattern
     * @param trip Trip containing route of new trip pattern in case a new trip pattern will be created
     * @param graph graph to add vertices and edges in case a new trip pattern will be created
     * @param serviceDate
     * @return cached or newly created trip pattern
     */
    public synchronized TripPattern getOrCreateTripPattern(
            @NotNull final StopPattern stopPattern,
            @NotNull final Trip trip,
            @NotNull final Graph graph,
            @NotNull ServiceDate serviceDate) {
        // Check cache for trip pattern
        StopPatternServiceDateKey key = new StopPatternServiceDateKey(stopPattern, serviceDate);
        TripPattern tripPattern = cache.get(key);
        
        // Create TripPattern if it doesn't exist yet
        if (tripPattern == null) {
            tripPattern = new TripPattern(trip.getRoute(), stopPattern);

            // Generate unique code for trip pattern
            //TODO - SIRI: Is this a good way to generate new trippPattern.id?
            tripPattern.setId(new FeedScopedId(trip.getId().getFeedId(), generateUniqueTripPatternCode(tripPattern)));
            
            // Create an empty bitset for service codes (because the new pattern does not contain any trips)
            tripPattern.setServiceCodes(graph.getServiceCodes());
            
            // Finish scheduled time table
            tripPattern.scheduledTimetable.finish();
            
            // Create vertices and edges for new TripPattern
            // TODO: purge these vertices and edges once in a while?
//            tripPattern.makePatternVerticesAndEdges(graph, graph.index.stopVertexForStop);
            
            // TODO - SIRI: Add pattern to graph index?

            TripPattern originalTripPattern = graph.index.getPatternForTrip().get(trip);

            // Copy information from the TripPattern this is replacing
            if (originalTripPattern != null) {
                tripPattern.setId(originalTripPattern.getId());
                tripPattern.setHopGeometriesFromPattern(originalTripPattern);
            }
            
            // Add pattern to cache
            cache.put(key, tripPattern);

        }

        /**
         *
         * When the StopPattern is first modified (e.g. change of platform), then updated (or vice versa), the stopPattern is altered, and
         * the StopPattern-object for the different states will not be equal.
         *
         * This causes both tripPatterns to be added to all unchanged stops along the route, which again causes duplicate results
         * in departureRow-searches (one departure for "updated", one for "modified").
         *
         * Full example:
         *      Planned stops: Stop 1 - Platform 1, Stop 2 - Platform 1
         *
         *      StopPattern #rt1: "updated" stopPattern cached in 'patternsForStop':
         *          - Stop 1, Platform 1
         *          	- StopPattern #rt1
         *          - Stop 2, Platform 1
         *          	- StopPattern #rt1
         *
         *      "modified" stopPattern: Stop 1 - Platform 1, Stop 2 - Platform 2
         *
         *      StopPattern #rt2: "modified" stopPattern cached in 'patternsForStop' will then be:
         *          - Stop 1, Platform 1
         *          	- StopPattern #rt1, StopPattern #rt2
         *          - Stop 2, Platform 1
         *          	- StopPattern #rt1
         *          - Stop 2, Platform 2
         *          	- StopPattern #rt2
         *
         *
         * Therefore, we must cleanup the duplicates by deleting the previously added (and thus outdated)
         * tripPattern for all affected stops. In example above, "StopPattern #rt1" should be removed from all stops
         *
         */
        TripServiceDateKey tripServiceDateKey = new TripServiceDateKey(trip, serviceDate);
        if (updatedTripPatternsForTripCache.containsKey(tripServiceDateKey)) {
            /**
             * Remove previously added TripPatterns for the trip currently being updated - if the stopPattern does not match
             */
            TripPattern cachedTripPattern = updatedTripPatternsForTripCache.get(tripServiceDateKey);
            if (cachedTripPattern != null && !tripPattern.stopPattern.equals(cachedTripPattern.stopPattern)) {
                int sizeBefore = patternsForStop.values().size();
                long t1 = System.currentTimeMillis();
                patternsForStop.values().removeAll(Arrays.asList(cachedTripPattern));
                int sizeAfter = patternsForStop.values().size();

                log.info("Removed outdated TripPattern for {} stops in {} ms - tripId: {}", (sizeBefore-sizeAfter),  (System.currentTimeMillis()-t1), trip.getId());
                /*
                  TODO: Also remove previously updated - now outdated - TripPattern from cache ?
                  cache.remove(new StopPatternServiceDateKey(cachedTripPattern.stopPattern, serviceDate));
                */
            }
        }

        // To make these trip patterns visible for departureRow searches.
        for (Stop stop: tripPattern.getStops()) {
            if (!patternsForStop.containsEntry(stop, tripPattern)) {
                patternsForStop.put(stop, tripPattern);
            }
        }

        /**
         * Cache the last added tripPattern that has been used to update a specific trip
         */
        updatedTripPatternsForTripCache.put(tripServiceDateKey, tripPattern);

        return tripPattern;
    }

    /**
     * Generate unique trip pattern code for real-time added trip pattern. This function roughly
     * follows the format of {@link TripPattern#generateUniqueIds(java.util.Collection)}.
     * 
     * @param tripPattern trip pattern to generate code for
     * @return unique trip pattern code
     */
    private String generateUniqueTripPatternCode(TripPattern tripPattern) {
        FeedScopedId routeId = tripPattern.route.getId();
        String direction = tripPattern.directionId != -1 ? String.valueOf(tripPattern.directionId) : "";
        if (counter == Integer.MAX_VALUE) {
            counter = 0;
        } else {
            counter++;
        }
        // OBA library uses underscore as separator, we're moving toward colon.
        String code = String.format("%s:%s:rt#%d", routeId.getId(), direction, counter);
        return code;
    }

    /**
     * Returns any new TripPatterns added by real time information for a given stop.
     *
     * @param stop the stop
     * @return list of TripPatterns created by real time sources for the stop.
     */
    public List getAddedTripPatternsForStop(Stop stop) {
        return patternsForStop.get(stop);
    }


}
class StopPatternServiceDateKey {
    StopPattern stopPattern;
    ServiceDate serviceDate;

    public StopPatternServiceDateKey(StopPattern stopPattern, ServiceDate serviceDate) {
        this.stopPattern = stopPattern;
        this.serviceDate = serviceDate;
    }

    @Override
    public boolean equals(Object thatObject) {
        if (!(thatObject instanceof StopPatternServiceDateKey)) return false;
        StopPatternServiceDateKey that = (StopPatternServiceDateKey) thatObject;
        return (this.stopPattern.equals(that.stopPattern) & this.serviceDate.equals(that.serviceDate));
    }

    @Override
    public int hashCode() {
        return stopPattern.hashCode()+serviceDate.hashCode();
    }
}
class TripServiceDateKey {
    Trip trip;
    ServiceDate serviceDate;

    public TripServiceDateKey(Trip trip, ServiceDate serviceDate) {
        this.trip = trip;
        this.serviceDate = serviceDate;
    }

    @Override
    public boolean equals(Object thatObject) {
        if (!(thatObject instanceof TripServiceDateKey)) return false;
        TripServiceDateKey that = (TripServiceDateKey) thatObject;
        return (this.trip.equals(that.trip) & this.serviceDate.equals(that.serviceDate));
    }

    @Override
    public int hashCode() {
        return trip.hashCode()+serviceDate.hashCode();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy