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

org.opentripplanner.model.StopPattern Maven / Gradle / Ivy

package org.opentripplanner.model;

import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

/**
 * This class represents what is called a JourneyPattern in Transmodel: the sequence of stops at
 * which a trip (GTFS) or vehicle journey (Transmodel) calls, irrespective of the day on which 
 * service runs.
 * 
 * An important detail: Routes in GTFS are not a structurally important element, they just serve as
 * user-facing information. It is possible for the same journey pattern to appear in more than one
 * route. 
 * 
 * OTP already has several classes that represent this same thing: A TripPattern in the context of
 * routing. It represents all trips with the same stop pattern A ScheduledStopPattern in the GTFS
 * loading process. A RouteVariant in the TransitIndex, which has a unique human-readable name and
 * belongs to a particular route.
 * 
 * We would like to combine all these different classes into one.
 * 
 * Any two trips with the same stops in the same order, and that operate on the same days, can be
 * combined using a TripPattern to simplify the graph. This saves memory and reduces search
 * complexity since we only consider the trip that departs soonest for each pattern. Field
 * calendarId has been removed. See issue #1320.
 * 
 * A StopPattern is very closely related to a TripPattern -- it essentially serves as the unique
 * key for a TripPattern. Should the route be included in the StopPattern?
 */
public final class StopPattern implements Serializable {

    private static final long serialVersionUID = 20140101L;
    public static final int NOT_FOUND = -1;

    private final StopLocation[] stops;
    private final PickDrop[]  pickups;
    private final PickDrop[]  dropoffs;

    private StopPattern (int size) {
        stops     = new StopLocation[size];
        pickups   = new PickDrop[size];
        dropoffs  = new PickDrop[size];
    }

    /** Assumes that stopTimes are already sorted by time. */
    public StopPattern (Collection stopTimes) {
        this (stopTimes.size());
        int size = stopTimes.size();
        if (size == 0) return;
        Iterator stopTimeIterator = stopTimes.iterator();

        for (int i = 0; i < size; ++i) {
            StopTime stopTime = stopTimeIterator.next();
            stops[i] = stopTime.getStop();
            // should these just be booleans? anything but 1 means pick/drop is allowed.
            // pick/drop messages could be stored in individual trips
            pickups[i] = computePickDrop(stopTime.getStop(), stopTime.getPickupType());
            dropoffs[i] = computePickDrop(stopTime.getStop(), stopTime.getDropOffType());
        }
    }

    int getSize() {
        return stops.length;
    }

    /** Find the given stop position in the sequence, return -1 if not found. */
    int findStopPosition(StopLocation stop) {
        for (int i=0; i s == stop);
    }

    int findAlightPosition(StopLocation stop) {
        return findStopPosition(1, stops.length, (s) -> s == stop);
    }

    int findBoardingPosition(Station station) {
        return findStopPosition(0, stops.length - 1, station::includes);
    }

    int findAlightPosition(Station station) {
        return findStopPosition(1, stops.length, station::includes);
    }

    public boolean equals(Object other) {
        if (other instanceof StopPattern) {
            StopPattern that = (StopPattern) other;
            return Arrays.equals(this.stops, that.stops) &&
                   Arrays.equals(this.pickups, that.pickups) &&
                   Arrays.equals(this.dropoffs, that.dropoffs);
        } else {
            return false;
        }
    }

    public int hashCode() {
        int hash = stops.length;
        hash += Arrays.hashCode(this.stops);
        hash *= 31;
        hash += Arrays.hashCode(this.pickups);
        hash *= 31;
        hash += Arrays.hashCode(this.dropoffs);
        return hash;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("StopPattern: ");
        for (int i = 0, j = stops.length; i < j; ++i) {
            sb.append(String.format("%s_%s%s ", stops[i].getCode(), pickups[i], dropoffs[i]));
        }
        return sb.toString();
    }

    /**
     * In most cases we want to use identity equality for StopPatterns. There is a single
     * StopPattern instance for each semantic StopPattern, and we don't want to calculate
     * complicated hashes or equality values during normal execution. However, in some cases we
     * want a way to consistently identify trips across versions of a GTFS feed, when the feed
     * publisher cannot ensure stable trip IDs. Therefore we define some additional hash functions.
     */
    HashCode semanticHash(HashFunction hashFunction) {
        Hasher hasher = hashFunction.newHasher();
        int size = stops.length;
        for (int s = 0; s < size; s++) {
            StopLocation stop = stops[s];
            // Truncate the lat and lon to 6 decimal places in case they move slightly between
            // feed versions
            hasher.putLong((long) (stop.getLat() * 1000000));
            hasher.putLong((long) (stop.getLon() * 1000000));
        }
        // Use hops rather than stops because drop-off at stop 0 and pick-up at last stop are
        // not important and have changed between OTP versions.
        for (int hop = 0; hop < size - 1; hop++) {
            hasher.putInt(pickups[hop].getGtfsCode());
            hasher.putInt(dropoffs[hop + 1].getGtfsCode());
        }
        return hasher.hash();
    }

    /** Get a copy of the internal collection of stops. */
    List getStops() {
        return List.of(stops);
    }

    StopLocation getStop(int stopPosInPattern) {
        return stops[stopPosInPattern];
    }

    PickDrop getPickup(int stopPosInPattern) {
        return pickups[stopPosInPattern];
    }

    PickDrop getDropoff(int stopPosInPattern) {
        return dropoffs[stopPosInPattern];
    }

    /** Returns whether passengers can alight at a given stop */
    boolean canAlight(int stopPosInPattern) {
        return dropoffs[stopPosInPattern].isRoutable();
    }

    /** Returns whether passengers can board at a given stop */
    boolean canBoard(int stopPosInPattern) {
        return pickups[stopPosInPattern].isRoutable();
    }

    /**
     * Returns whether passengers can board at a given stop.
     * This is an inefficient method iterating over the stops, do not use it in routing.
     */
    boolean canBoard(StopLocation stop) {
        // We skip the last stop, not allowed for boarding
        for (int i=0; i match
    ) {
        for (int i = start; i < end; ++i) {
            if (match.test(stops[i])) { return i; }
        }
        return -1;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy