src/main/java/com/conveyal/gtfs/stats/StopStats.java
public List<TransferPerformanceSummary> getTransferPerformance (String stop_id, LocalDate date) {
        List<StopTime> stopTimes = feed.getStopTimesForStop(stop_id);

        Map<String, List<StopTime>> routeStopTimeMap  = new HashMap<>();
        List<TransferPerformanceSummary> transferPerformanceMap = new ArrayList<>();
        // TODO: do we need to handle interpolated stop times???

        // first stream stopTimes for stop into route -> list of stopTimes map
        stopTimes.stream()
                .forEach(st -> {
                    Trip trip = feed.trips.get(st.trip_id);
                    Service service = feed.services.get(trip.service_id);
                    // only add to map if trip is active on given date
                    if (service != null && service.activeOn(date)) {
                        Route route = feed.routes.get(trip.route_id);

                        List<StopTime> times = new ArrayList<>();
                        if (routeStopTimeMap.containsKey(route.route_id)) {
                            times.addAll(routeStopTimeMap.get(route.route_id));
                        }
                        times.add(st);
                        routeStopTimeMap.put(route.route_id, times);
                    }
                });
        Map<Fun.Tuple2<String, String>, TIntList> waitTimesByRoute = new HashMap<>();
        Map<Fun.Tuple2<String, String>, Set<Fun.Tuple2<StopTime, StopTime>>> missedTransfers = new HashMap<>();
        // iterate over every entry in route -> list of stopTimes map
        for (Map.Entry<String, List<StopTime>> entry : routeStopTimeMap.entrySet()) {
            final int MISSED_TRANSFER_THRESHOLD = 60 * 10;
            List<StopTime> currentTimes = entry.getValue();

            String currentRoute = entry.getKey();

            for (StopTime currentTime : currentTimes) {
                if (currentTime.arrival_time > 0) {
                    // cycle through all other routes that stop here.
                    for (Map.Entry<String, List<StopTime>> entry2 : routeStopTimeMap.entrySet()) {

                        List<StopTime> compareTimes = entry2.getValue();
                        String compareRoute = entry2.getKey();
                        Fun.Tuple2<String, String> routeKey = new Fun.Tuple2(currentRoute, compareRoute);
                        if (compareRoute.equals(currentRoute)) {
                            continue;
                        }
                        if (!waitTimesByRoute.containsKey(routeKey)) {
                            waitTimesByRoute.put(routeKey, new TIntArrayList());
                        }

                        int shortestWait = Integer.MAX_VALUE;
                        // compare current time against departure times for route
                        for (StopTime compareTime : compareTimes) {
                            if (compareTime.departure_time > 0) {
                                int waitTime = compareTime.departure_time - currentTime.arrival_time;

                                // if non-negative and shortest, save for later
                                if (waitTime >= 0 && waitTime < shortestWait){
                                    shortestWait = waitTime;
                                }
                                // otherwise, check for missed near-transfer opportunities
                                else {
                                    if (waitTime < 0 && waitTime * -1 <= MISSED_TRANSFER_THRESHOLD) {
                                        Fun.Tuple2<StopTime, StopTime> missedTransfer = new Fun.Tuple2(compareTime, currentTime);
//                                        missedTransfer.add(currentTime);
//                                        missedTransfer.add(compareTime);
                                        if (!missedTransfers.containsKey(routeKey)) {
                                            missedTransfers.put(routeKey, new HashSet<>());
                                        }
                                        missedTransfers.get(routeKey).add(missedTransfer);
                                    }
                                }
                            }
                        }
                        // add shortestWait for currentTime to map
                        if (shortestWait < Integer.MAX_VALUE)
                            waitTimesByRoute.get(routeKey).add(shortestWait);
                    }
                }
            }
        }
        for (Map.Entry<Fun.Tuple2<String, String>, TIntList> entry : waitTimesByRoute.entrySet()) {
            Fun.Tuple2<String, String> routeKey = entry.getKey();
            TIntList waitTimes = entry.getValue();
            if (waitTimes.isEmpty()) {
                continue;
            }
            int min = waitTimes.min();
            int max = waitTimes.max();
            waitTimes.sort();
            int median = waitTimes.get(waitTimes.size() / 2);
            TransferPerformanceSummary routeTransferPerformance = new TransferPerformanceSummary(routeKey.a, routeKey.b, min, max, median, missedTransfers.get(routeKey));
            transferPerformanceMap.add(routeTransferPerformance);
        }

        return transferPerformanceMap;
    }
public List<TransferPerformanceSummary> getTransferPerformance (String stop_id, LocalDate date) {
        List<StopTime> stopTimes = feed.getStopTimesForStop(stop_id);

        Map<String, List<StopTime>> routeStopTimeMap  = new HashMap<>();
        List<TransferPerformanceSummary> transferPerformanceMap = new ArrayList<>();
        // TODO: do we need to handle interpolated stop times???

        // first stream stopTimes for stop into route -> list of stopTimes map
        stopTimes.stream()
                .forEach(st -> {
                    Trip trip = feed.trips.get(st.trip_id);
                    Service service = feed.services.get(trip.service_id);
                    // only add to map if trip is active on given date
                    if (service != null && service.activeOn(date)) {
                        Route route = feed.routes.get(trip.route_id);

                        List<StopTime> times = new ArrayList<>();
                        if (routeStopTimeMap.containsKey(route.route_id)) {
                            times.addAll(routeStopTimeMap.get(route.route_id));
                        }
                        times.add(st);
                        routeStopTimeMap.put(route.route_id, times);
                    }
                });
        Map<Fun.Tuple2<String, String>, TIntList> waitTimesByRoute = new HashMap<>();
        Map<Fun.Tuple2<String, String>, Set<Fun.Tuple2<StopTime, StopTime>>> missedTransfers = new HashMap<>();
        // iterate over every entry in route -> list of stopTimes map
        for (Map.Entry<String, List<StopTime>> entry : routeStopTimeMap.entrySet()) {
            final int MISSED_TRANSFER_THRESHOLD = 60 * 10;
            List<StopTime> currentTimes = entry.getValue();

            String currentRoute = entry.getKey();

            for (StopTime currentTime : currentTimes) {
                if (currentTime.arrival_time > 0) {
                    // cycle through all other routes that stop here.
                    for (Map.Entry<String, List<StopTime>> entry2 : routeStopTimeMap.entrySet()) {

                        List<StopTime> compareTimes = entry2.getValue();
                        String compareRoute = entry2.getKey();
                        Fun.Tuple2<String, String> routeKey = new Fun.Tuple2(currentRoute, compareRoute);
                        if (compareRoute.equals(currentRoute)) {
                            continue;
                        }
                        if (!waitTimesByRoute.containsKey(routeKey)) {
                            waitTimesByRoute.put(routeKey, new TIntArrayList());
                        }

                        int shortestWait = Integer.MAX_VALUE;
                        // compare current time against departure times for route
                        for (StopTime compareTime : compareTimes) {
                            if (compareTime.departure_time > 0) {
                                int waitTime = compareTime.departure_time - currentTime.arrival_time;

                                // if non-negative and shortest, save for later
                                if (waitTime >= 0 && waitTime < shortestWait){
                                    shortestWait = waitTime;
                                }
                                // otherwise, check for missed near-transfer opportunities
                                else {
                                    if (waitTime < 0 && waitTime * -1 <= MISSED_TRANSFER_THRESHOLD) {
                                        Fun.Tuple2<StopTime, StopTime> missedTransfer = new Fun.Tuple2(compareTime, currentTime);
//                                        missedTransfer.add(currentTime);
//                                        missedTransfer.add(compareTime);
                                        if (!missedTransfers.containsKey(routeKey)) {
                                            missedTransfers.put(routeKey, new HashSet<>());
                                        }
                                        missedTransfers.get(routeKey).add(missedTransfer);
                                    }
                                }
                            }
                        }
                        // add shortestWait for currentTime to map
                        if (shortestWait < Integer.MAX_VALUE)
                            waitTimesByRoute.get(routeKey).add(shortestWait);
                    }
                }
            }
        }
        for (Map.Entry<Fun.Tuple2<String, String>, TIntList> entry : waitTimesByRoute.entrySet()) {
            Fun.Tuple2<String, String> routeKey = entry.getKey();
            TIntList waitTimes = entry.getValue();
            if (waitTimes.isEmpty()) {
                continue;
            }
            int min = waitTimes.min();
            int max = waitTimes.max();
            waitTimes.sort();
            int median = waitTimes.get(waitTimes.size() / 2);
            TransferPerformanceSummary routeTransferPerformance = new TransferPerformanceSummary(routeKey.a, routeKey.b, min, max, median, missedTransfers.get(routeKey));
            transferPerformanceMap.add(routeTransferPerformance);
        }

        return transferPerformanceMap;
    }
Returns a list of transfer performance summaries (max, min, and median wait times between routes)
for each route pair at a stop for the specified date of service.
@param stop_id
@param date
@return